I thought I woulld share a few example scripts that aren’t yet in the app either because I haven’t pollished them or because they aren’t yet scripts that performs a meaningful task but rather just a showcase of APIs.
Some of them are scripts I use daily and some of them are just for fun. Maybe you’ll find some of them useful as inspiration for your own scripts.
Pollen
Fetches the latest pollen counts from the Danish weather provider DMI.
APIs: Request, XMLParser, UITable, QuickLook
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: deep-green; icon-glyph: sun-2;
// Shows the pollen count for today.
const url = "http://www.dmi.dk/vejr/services/pollen-rss/"
const r = new Request(url)
let resp = await r.load()
const targetCityName = "København"
let elementName = ""
let currentValue = null
let items = []
let currentItem = null
const xmlParser = new XMLParser(resp)
xmlParser.didStartElement = name => {
currentValue = ""
if (name == "item") {
currentItem = {}
}
}
xmlParser.didEndElement = name => {
const hasItem = currentItem != null
if (hasItem && name == "title") {
currentItem["title"] = currentValue
}
if (hasItem && name == "description") {
currentItem["description"] = currentValue
}
if (name == "item") {
items.push(currentItem)
currentItem = {}
}
}
xmlParser.foundCharacters = str => {
currentValue += str
}
xmlParser.didEndDocument = () => {
const cph = items.filter(i => {
return i.title == "København"
})[0]
const lines = cph.description
.trim()
.split("\n")
.map(l => l.trim().replace(";", ""))
.filter(l => l.length > 0)
let values = lines.join("\n")
let rows = lines.map(mapRow)
let table = new UITable()
for (row of rows) {
table.addRow(row)
}
let focusedType = "Græs"
let regex = new RegExp(focusedType + ": ([0-9]+|-)")
let match = values.match(regex)
let textToSpeak = null
if (match) {
let pollenCount = match[1]
if (match == "-") {
textToSpeak = "There are no grass pollen"
} else {
textToSpeak = "The pollen count for grass is " + pollenCount
}
} else {
textToSpeak = "Pollen count for grass is unavailable"
}
QuickLook.present(table)
if (config.runsWithSiri) {
Speech.speak(textToSpeak)
}
}
xmlParser.parse()
function mapRow(value) {
let comps = value.split(":")
let row = new UITableRow()
row.addText(mapName(comps[0].trim()))
row.addText(mapValue(comps[1].trim()))
return row
}
function mapName(name) {
if (name == "Birk") {
return "Birch"
} else if (name == "Bynke") {
return "Mugwort"
} else if (name == "El") {
return "Alder"
} else if (name == "Elm") {
return "Elm"
} else if (name == "Græs") {
return "Grass"
} else if (name == "Hassel") {
return "Hazel"
} else {
return name
}
}
function mapValue(value) {
if (value == "Høj") {
return "High"
} else if (value == "Middel") {
return "Moderate"
} else if (value == "Lav") {
return "Low"
} else {
return value
}
}
Postpone Event
Shows future events take place today and promps user to select one of them and then promps to select a time interval to postpone the event, e.g. 15 minutes.
APIs: Calendar, Alert
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: deep-blue; icon-glyph: calendar;
// Fetches upcoming events today and prompts to select an event. Then prompts user to select a number of minutes to postpone the selected event.
let calendarName = "Work"
let cal = await Calendar.forEventsByTitle(calendarName)
let events = await CalendarEvent.today([cal])
events = events.filter(event => {
return event.startDate > new Date()
})
// Check if we found any events
if (events.length == 0) {
let alert = new Alert()
alert.message = "There are no more events today."
alert.present()
return
}
// Prompt to choose which event to postpone
let alert = new Alert()
for (event of events) {
let hours = event.startDate.getHours()
let mins = event.startDate.getMinutes()
let title = ""
+ zeroPrefix(hours)
+ ":"
+ zeroPrefix(mins)
+ ": "
+ event.title
alert.addAction(title)
}
alert.addCancelAction("Cancel")
let eventIdx = await alert.presentSheet()
if (eventIdx == -1) {
return
}
// Prompt to choose minutes to offset
let minutes = [ 15, 30, 45, 60 ]
alert = new Alert()
for (offset of minutes) {
alert.addAction(offset + " minutes")
}
alert.addCancelAction("Cancel")
let minsIdx = await alert.presentSheet()
if (minsIdx == -1) {
return
}
// Update date with offset and save
let offset = minutes[minsIdx] * 60 * 1000
let event = events[eventIdx]
let startTime = event.startDate.getTime()
let endTime = event.endDate.getTime()
event.startDate = new Date(startTime + offset)
event.endDate = new Date(endTime + offset)
event.save()
// Format hours and minutes
function zeroPrefix(num) {
return (num < 10 ? "0" : "") + num
}
Charles response time
Takes an exported session file from the Charles Proxy app as input and shows the response times for the requests in the sessions. The user can select to view the response times as a bar chart or in plain text.
APIs: FileManager, Alert, DrawContext, QuickLook
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// always-run-in-app: true; icon-color: blue;
// icon-glyph: clock-1; share-sheet-inputs: plain-text, file-url, url;
// Shows response times from the Charles iOS app.
const fileURL = args.fileURLs[0]
const fm = FileManager.local()
const rawJSON = fm.read(fileURL)
const json = JSON.parse(rawJSON)
// Extract response times from Charles session
let responseTimesForPath = {}
for (let transaction of json) {
if (!("path" in transaction)) {
continue
}
const path = transaction["path"]
if (path == null) {
continue
}
if (!("times" in transaction)) {
continue
}
const times = transaction["times"]
if (!("responseBegin" in times) || !("end" in times)) {
continue
}
const responseBeginTime = Date.parse(times["responseBegin"])
const endTime = Date.parse(times["end"])
const responseTime = endTime - responseBeginTime
let responseTimes = responseTimesForPath[path] || []
responseTimes.push(responseTime)
responseTimesForPath[path] = responseTimes
}
// Compute average times
let averageResponseTimesForPath = {}
const reducer = (accumulator, currentValue) => accumulator + currentValue
for (let path in responseTimesForPath) {
const responseTimes = responseTimesForPath[path]
averageResponseTimesForPath[path] = Math.round(responseTimes.reduce(reducer, 0) / responseTimes.length)
}
// Sort paths by average response time, greatest to lowest
let allPaths = Object.keys(averageResponseTimesForPath)
let allAverageResponseTimes = Object.values(averageResponseTimesForPath)
allPaths.sort((a, b) => {
const aResponseTime = averageResponseTimesForPath[a]
const bResponseTime = averageResponseTimesForPath[b]
if (aResponseTime < bResponseTime) {
return 1
} else if (aResponseTime > bResponseTime) {
return -1
} else {
return 0
}
})
const alert = new Alert()
alert.addAction("Plain text")
alert.addAction("Graph")
alert.addCancelAction("Cancel")
alert.presentSheet().then(idx => {
if (idx == 0) {
// Create pretty string with response times
let timesString = ""
for (let i = 0; i < allPaths.length; i++) {
const path = allPaths[i]
timesString += path + ": " + averageResponseTimesForPath[path] + " ms"
if (i < allPaths.length - 1) {
timesString += "\n"
}
}
QuickLook.present(timesString)
} else if (idx == 1) {
// Draw graph
const maxBarWidth = 400
const barHeight = 20
const barSpacing = 30
const inset = 20
const totalWidth = maxBarWidth + inset * 2
const totalHeight = (barHeight + barSpacing) * allPaths.length + inset * 2
const maxAverageResponseTime = Math.max.apply(Math, allAverageResponseTimes)
const c = new DrawContext()
c.respectScreenScale = true
c.size = new Size(totalWidth, totalHeight)
c.setFillColor(Color.white())
c.fillRect(new Rect(0, 0, totalWidth, totalHeight))
for (let i = 0; i < allPaths.length; i++) {
const path = allPaths[i]
const responseTime = averageResponseTimesForPath[path]
const textResponseTime = responseTime + "ms"
const percentage = responseTime / maxAverageResponseTime
const xPos = inset
const yPos = inset + (barHeight + barSpacing) * i
const barWidth = Math.round(maxBarWidth * percentage)
const rect = new Rect(xPos, yPos, barWidth, barHeight)
const responseTimeRect = new Rect(xPos, yPos + 2, barWidth - 7, barHeight - 2)
c.setFillColor(Color.black())
c.fillRect(rect)
c.setTextAlignedLeft()
c.setTextColor(Color.black())
c.drawText(path, new Point(xPos, yPos + barHeight + 2))
c.setTextColor(Color.white())
c.setTextAlignedRight()
c.drawTextInRect(textResponseTime, responseTimeRect)
}
const image = c.getImage()
QuickLook.present(image)
}
}).catch(err => {
console.logError(err)
})
Draw
Uses the DrawContext API to draw the word “Hi”.
APIs: DrawContext, QuickLook
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: orange; icon-glyph: pencil;
const lineWidth = 4
const horSpacing = 60
const verSpacing = 20
const size = new Size(200, 200)
const c = new DrawContext()
c.size = size
c.respectScreenScale = true
// c.beginDrawing()
c.setStrokeColor(Color.black())
c.setLineWidth(lineWidth)
// Set background color
c.setFillColor(new Color("#f5a623"))
// c.setFillColor(Color.red())
c.fill(new Rect(0, 0, size.width, size.height))
// Create H
const hPath = new Path()
hPath.move(new Point(horSpacing, verSpacing))
hPath.addLine(new Point(horSpacing, size.height - verSpacing))
hPath.move(new Point(horSpacing, verSpacing + (size.height - verSpacing * 2) / 2))
hPath.addLine(new Point(100, verSpacing + (size.height - verSpacing * 2) / 2))
hPath.addLine(new Point(100, size.height - verSpacing))
hPath.move(new Point(100, verSpacing + (size.height - verSpacing * 2) / 2))
hPath.addLine(new Point(100, verSpacing))
// Draw H
c.addPath(hPath)
c.setStrokeColor(Color.black())
c.setLineWidth(4)
c.strokePath()
// Create I
const iPath = new Path()
iPath.move(new Point(size.width - horSpacing, 40))
iPath.addLine(new Point(size.width - horSpacing, size.height - verSpacing))
// Draw I
c.addPath(iPath)
c.strokePath()
// Draw I dot
c.setFillColor(Color.black())
c.fillEllipse(new Rect(size.width - horSpacing - lineWidth, verSpacing, lineWidth * 2, lineWidth * 2))
const img = c.getImage()
// c.endDrawing()
QuickLook.present(img)
FileManager
Small example of working with files. Stores a text file in iCloud and reads it back.
APIs: FileManager
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: deep-blue; icon-glyph: magic-wand;
let fm = FileManager.iCloud()
let dir = fm.documentsDirectory()
let path = fm.joinPath(dir, "myfile.txt")
fm.write(path, "Hello world")
let text = fm.read(path)
QuickLook.present(text)