Last active
December 20, 2024 06:23
-
-
Save heptal/40583dafe384d72997f60b8e1c962a90 to your computer and use it in GitHub Desktop.
Recent Discord channel/DM messages shown in iOS14 widget - built with Scriptable.app
This file contains 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
// set up keychain and token | |
const namespace = "dscrd.wdgt" | |
const token = `${namespace}.usertoken` | |
if (!Keychain.contains(token) || !Keychain.get(token)) { | |
let alert = new Alert() | |
alert.title = "Enter Discord User Token" | |
alert.addTextField("<discord_token>") | |
await alert.presentAlert() | |
Keychain.set(token, alert.textFieldValue(0) || "") | |
} | |
// discord API fetch | |
const CDN = "https://cdn.discordapp.com" | |
const BASE = "https://canary.discordapp.com/api/v6/" | |
const api = async (path, verb = "GET", opts = {}) => { | |
let req = new Request(BASE + path); | |
req.method = verb; | |
req.headers = { | |
'authorization': Keychain.get(token) | |
} | |
return await req.loadJSON() | |
} | |
const me = await api(`users/@me`) | |
const $G = `${namespace}.${me.id}.guild` | |
const $C = `${namespace}.${me.id}.channel` | |
// data resources | |
const getGuilds = () => api(`users/@me/guilds`) | |
const getDMs = () => api(`users/@me/channels`) | |
const getChannel = id => api(`channels/${id}`) | |
const getGuild = id => api(`guilds/${id}`) | |
const getChannels = id => api(`guilds/${id}/channels`) | |
const getMessages = id => api(`channels/${id}/messages`) | |
// guild and user images | |
const banner = g => `${CDN}/banners/${g.id}/${g.banner}.png` | |
const icon = g => `${CDN}/icons/${g.id}/${g.icon}.png` | |
const avatar = u => u.avatar ? | |
`${CDN}/avatars/${u.id}/${u.avatar}?size=64` : | |
`${CDN}/embed/avatars/${u.discriminator % 5}.png` | |
// pick a DM convo or guild channel | |
async function picker() { | |
let table = new UITable() | |
let makerow = (title, url) => { | |
let row = new UITableRow() | |
row.backgroundColor = new Color("#36393f") | |
row.height = 48 | |
row.cellSpacing = 8 | |
row.addImageAtURL(url).widthWeight = 15 | |
let txt = row.addText(title) | |
txt.widthWeight = 85 | |
txt.titleColor = Color.white() | |
txt.titleFont = Font.boldRoundedSystemFont(18) | |
return row | |
} | |
let dms = makerow("DM Convos") | |
dms.onSelect = () => Keychain.set($G, "") | |
table.addRow(dms) | |
for (let guild of (await getGuilds())) { | |
let row = makerow(guild.name, icon(guild)) | |
row.onSelect = () => Keychain.set($G, guild.id) | |
table.addRow(row) | |
} | |
return table.present(true).then(async function () { | |
table.removeAllRows() | |
let channels = (Keychain.get($G) ? | |
(await getChannels(Keychain.get($G))) | |
.filter(c => c.type === 0) : | |
(await getDMs()).filter(dm => dm.type === 1) | |
.map(dm => ({ | |
...dm, | |
name: dm.recipients[0].username, | |
av: avatar(dm.recipients[0]) | |
})) | |
) | |
.filter(c => !!c.last_message_id) | |
.sort((a, b) => b.last_message_id.localeCompare(a.last_message_id)) | |
for (let c of channels) { | |
let row = makerow(c.name, c.av) | |
row.onSelect = () => Keychain.set($C, c.id) | |
table.addRow(row) | |
} | |
return table.present(true).then(function () { | |
console.log("guild id:\t" + Keychain.get($G)) | |
console.log("channel id:\t" + Keychain.get($C)) | |
}) | |
}) | |
} | |
// build widget for chosen channel | |
async function createWidget() { | |
let wdgt = new ListWidget() | |
let df = new DateFormatter() | |
df.dateFormat = "hh:mm" | |
let truncate = false | |
let shadow = el => { | |
el.shadowColor = Color.black() | |
el.shadowRadius = 4 | |
el.shadowOffset = new Point(1, 2) | |
} | |
let gradient = new LinearGradient() | |
gradient.locations = [0, 1] | |
gradient.colors = [new Color("66666699"), new Color("99336699")] | |
wdgt.backgroundGradient = gradient | |
if (!!Keychain.get($G)) { | |
let gld = await getGuild(Keychain.get($G)) | |
let bgUrl = gld.banner ? banner(gld) : icon(gld) | |
let bgImage = new Request(bgUrl) | |
wdgt.backgroundImage = await bgImage.loadImage() | |
} | |
let items = await getMessages(Keychain.get($C)) | |
if (!Array.isArray(items)) { | |
let txt = wdgt.addText(JSON.stringify(items)) | |
txt.textColor = Color.white() | |
txt.centerAlignText() | |
wdgt.backgroundColor = Color.red() | |
return wdgt | |
} | |
items = items.reverse().slice(-7) | |
console.log(JSON.stringify(items, null, 2)) | |
for (let item of items) { | |
let { | |
author, | |
content, | |
timestamp | |
} = item | |
let ts = df.string(new Date(Date.parse(timestamp))) | |
let stack = wdgt.addStack() | |
// profile pic | |
let avRequest = new Request(avatar(author)) | |
let avImage = await avRequest.loadImage() | |
let pfp = stack.addImage(avImage) | |
pfp.imageSize = new Size(18, 18) | |
pfp.cornerRadius = 10 | |
pfp.leftAlignImage() | |
shadow(pfp) | |
stack.addSpacer(5) | |
let ms = stack.addStack() | |
ms.layoutVertically() | |
if (truncate) { | |
let lines = Math.ceil(content.length / 60) | |
ms.size = new Size(0, 20 * lines) | |
} | |
// username | |
let user = ms.addText(author.username) | |
user.font = Font.mediumMonospacedSystemFont(8) | |
user.textColor = Color.cyan() | |
user.leftAlignText() | |
shadow(user) | |
// message | |
let msg = ms.addText(content) | |
msg.font = Font.heavyRoundedSystemFont(10) | |
msg.textColor = Color.white() | |
msg.lineLimit = truncate ? 3 : 0 | |
msg.leftAlignText() | |
shadow(msg) | |
stack.addSpacer() | |
// timestamp | |
let d = stack.addText(ts) | |
d.font = Font.mediumMonospacedSystemFont(9) | |
d.textColor = Color.white() | |
d.rightAlignText() | |
stack.topAlignContent() | |
} | |
return wdgt | |
} | |
async function refresh() { | |
await createWidget().then(wdgt => { | |
config.runsInApp && wdgt.presentMedium() | |
Script.setWidget(wdgt) | |
Script.complete() | |
}) | |
} | |
if (config.runsInWidget) { | |
await refresh() | |
} else { | |
let alert = new Alert() | |
alert.addAction("Select Displayed Chat") | |
alert.addAction("Open Discord.app") | |
alert.addCancelAction("Cancel") | |
switch (await alert.presentAlert()) { | |
case 0: | |
await picker() | |
await refresh() | |
break; | |
case 1: | |
Safari.open("https://discord.gg") | |
break; | |
} | |
Script.complete() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment