Last active
October 31, 2020 12:02
-
-
Save fcrespo82/2a06ff0b9f3492ea14e7ff7f5181c26c to your computer and use it in GitHub Desktop.
Schedule WhatsApp messages
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Variables used by Scriptable. | |
// These must be at the very top of the file. Do not edit. | |
// icon-color: deep-green; icon-glyph: comment-alt; | |
// If you need to send message from another app customize the url here | |
function getMessageURL(item) { | |
return `https://wa.me/${item.number}/?text=${encodeURIComponent(item.message)}` | |
} | |
async function createTable(data) { | |
const table = new UITable() | |
table.showSeparators = true | |
populateTable(table, data) | |
await table.present(true) | |
} | |
function populateTable(table, data, removeAllFirst) { | |
if (removeAllFirst) { | |
table.removeAllRows() | |
} | |
const addScheduleRow = new UITableRow() | |
const scheduleButton = addScheduleRow.addButton("Add new scheduled message") | |
scheduleButton.centerAligned() | |
scheduleButton.onTap = async () => { | |
const schedule = await newSchedule() | |
data.push(schedule) | |
populateTable(table, data, true) | |
saveScheduleMessages(data) | |
table.reload() | |
} | |
table.addRow(addScheduleRow) | |
for (i in data) { | |
let item = data[i] | |
let row = new UITableRow() | |
let date = new Date() | |
date.setTime(item.date) | |
if (date < Date.now()) { | |
row.backgroundColor = new Color("aaffaaff") | |
} | |
let textCell = row.addText(item.name, item.message) | |
textCell.widthWeight = 80 | |
let sendButton = row.addButton("📤") | |
sendButton.widthWeight = 10 | |
sendButton.onTap = () => { | |
let url = getMessageURL(item) | |
Safari.open(url) | |
} | |
let removeButton = row.addButton("❌") | |
removeButton.widthWeight = 10 | |
removeButton.onTap = async () => { | |
const index = data.indexOf(item) | |
data.splice(index, 1) | |
populateTable(table, data, true) | |
let notifications = await Notification.allPending() | |
notifications.re | |
saveScheduleMessages(data) | |
table.reload() | |
} | |
table.addRow(row) | |
} | |
} | |
async function chooseContact() { | |
return new Promise(async (resolve, reject) => { | |
let containers = await ContactsContainer.all() | |
let contacts = await Contact.all(containers) | |
contacts = contacts.sort((a, b) => { | |
let compA = a.organizationName | |
let compB = b.organizationName | |
if (a.familyName.trim() != "") { | |
compA = a.familyName | |
} | |
if (b.familyName.trim() != "") { | |
compB = b.familyName | |
} | |
if (a.givenName.trim() != "") { | |
compA = a.givenName | |
} | |
if (b.givenName.trim() != "") { | |
compB = b.givenName | |
} | |
return compA.trim().toLowerCase().localeCompare(compB.trim().toLowerCase()) | |
}) | |
let tableRows = [] | |
contacts.forEach((contact) => { | |
let name = `${contact.organizationName.trim()} - ` | |
if (contact.givenName.trim() != "") { | |
name = `${contact.givenName.trim()} ${contact.familyName.trim()}`.trim() | |
} | |
contact.phoneNumbers.forEach(async (phoneNumber) => { | |
tableRows.push({ | |
name: name, | |
number: phoneNumber.value.replace(/\D/g, '') | |
}) | |
}) | |
}) | |
const table = new UITable() | |
table.showSeparators = true | |
tableRows.forEach((contact) => { | |
const row = new UITableRow() | |
row.height = 50 | |
let cell = row.addText(contact.name, contact.number) | |
cell.subtitleColor = Color.gray() | |
table.addRow(row) | |
row.onSelect = async (index) => { | |
tableRows[index].number = await cleanUpNumber(tableRows[index].number) | |
resolve(tableRows[index]) | |
} | |
}) | |
table.present(true) | |
}) | |
} | |
async function newSchedule() { | |
const contact = await chooseContact() | |
const picker = new DatePicker() | |
let minimumDate = new Date() | |
// At least 2 minutes delay | |
minimumDate.setTime(minimumDate.getTime() + (1000 * 120)) | |
minimumDate.setTime(minimumDate.getTime() + (1000 * 30)) | |
picker.minimumDate = minimumDate | |
const date = await picker.pickDateAndTime() | |
let getText = new Alert(); | |
getText.addTextField("message"); | |
getText.title = `Enter message to send to ${contact.name}`; | |
getText.addAction("OK"); | |
await getText.present(); | |
const message = getText.textFieldValue(0); | |
const item = { ...contact, date: date.getTime(), message } | |
await createNotification(item) | |
return item | |
} | |
function getIdentifier(item) { | |
return `br.com.crespo.schedule-whatsapp.${item.id}` | |
} | |
async function createNotification(item) { | |
const notification = new Notification() | |
notification.identifier = getIdentifier(item) | |
notification.sound = "default" | |
notification.title = "Send message" | |
notification.body = `to ${item.name}` | |
notification.openURL = getMessageURL(item) | |
notification.addAction("Send", getMessageURL(item)) | |
let triggerDate = new Date() | |
triggerDate.setTime(item.date) | |
notification.setTriggerDate(triggerDate) | |
await notification.schedule() | |
} | |
async function cleanUpNumber(number) { | |
let getText = new Alert(); | |
getText.addTextField("message", number.replace(/\D/g, '')); | |
getText.title = `Fix number?`; | |
getText.message = "WhatsApp needs a number in full international number Use 15551234567 instead of +001-(555)1234567" | |
getText.addAction("OK"); | |
await getText.present(); | |
return getText.textFieldValue(0) | |
} | |
// Widget related functions | |
async function createWidget(data) { | |
const widget = new ListWidget() | |
await populateWidget(widget, data) | |
const newSchedule = widget.addStack() | |
newSchedule.addSpacer() | |
const text = newSchedule.addText("Schedule new") | |
text.url = "scriptable:///run?scriptName=Schedule%20WhatsApp" | |
widget.presentMedium() | |
Script.setWidget(widget) | |
return widget | |
} | |
function populateWidget(widget, data) { | |
const maxItems = 2 | |
let itemsIncluded = 0 | |
for (let i in data) { | |
let stack = widget.addStack() | |
stack.layoutVertically() | |
let item = data[i] | |
let text = stack.addText(`${item.name} - ${item.number}`) | |
let date = new Date() | |
date.setTime(item.date) | |
if (date < Date.now()) { | |
continue | |
} | |
const dateFormatter = new DateFormatter() | |
dateFormatter.dateFormat = "dd/MM/yyyy HH:mm:ss" | |
let dateItem = stack.addText(dateFormatter.string(date)) | |
text.url = getMessageURL(item) | |
widget.addSpacer() | |
itemsIncluded += 1 | |
if (itemsIncluded === maxItems) { | |
break | |
} | |
} | |
} | |
// File related functions | |
async function loadScheduleMessages() { | |
const fm = FileManager.iCloud() | |
const path = getFilePath() | |
if (fm.fileExists(path)) { | |
await fm.downloadFileFromiCloud(path) | |
const text = fm.readString(path) | |
return JSON.parse(text) | |
} | |
} | |
function saveScheduleMessages(data) { | |
const fm = FileManager.iCloud() | |
const path = getFilePath() | |
fm.writeString(path, JSON.stringify(data, null, 4)) | |
} | |
function getFilePath() { | |
let fm = FileManager.iCloud() | |
let dirPath = fm.joinPath( | |
fm.documentsDirectory(), | |
"br.com.crespo.schedule-whatsapp") | |
if (!fm.fileExists(dirPath)) { | |
fm.createDirectory(dirPath) | |
fm.writeString(fm.joinPath(dirPath, "messages.json"), JSON.stringify([], null, 4)) | |
} | |
return fm.joinPath(dirPath, "messages.json") | |
} | |
let data = await loadScheduleMessages() | |
if (config.runsInApp) { | |
createTable(data) | |
} else if (config.runsInWidget) { | |
createWidget(data) | |
} else if (config.runsWithSiri) { | |
createWidget(data) | |
} | |
Script.complete() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment