Created
August 4, 2024 11:29
-
-
Save fer0n/f9b64e71be2f53293587aa20ece4c33b to your computer and use it in GitHub Desktop.
Splitwise Widget for iOS via Scriptable
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
let apiToken = ""; | |
try { | |
const lookup = await getLookup(); | |
apiToken = lookup.apiToken; | |
await showWidgetFor(lookup.participant.id); | |
} catch (e) { | |
if (e.message === "runInApp") { | |
displayRunInAppWidget(); | |
return; | |
} | |
logError(e); | |
throw e; | |
} | |
async function showWidgetFor(userId) { | |
const friend = await getFriend(userId); | |
console.log(friend); | |
let widget = createWidget(friend); | |
displayWidget(widget); | |
} | |
function displayWidget(widget) { | |
if (config.runsInWidget) { | |
Script.setWidget(widget); | |
} else { | |
widget.presentSmall(); | |
} | |
Script.complete(); | |
} | |
function displayRunInAppWidget() { | |
const widget = new ListWidget(); | |
widget.addText("Run this script inside the App once to set it up"); | |
displayWidget(widget); | |
} | |
function createWidget(friend) { | |
const list = new ListWidget(); | |
let startColor = new Color("#191919"); | |
let endColor = new Color("#191919"); | |
let gradient = new LinearGradient(); | |
gradient.colors = [startColor, endColor]; | |
gradient.locations = [0.1, 1]; | |
list.backgroundGradient = gradient; | |
list.addSpacer(3); | |
let titleStack = list.addStack(); | |
const header = titleStack.addText(friend.name.toUpperCase()); | |
header.font = Font.boldSystemFont(15); | |
header.textColor = Color.white(); | |
titleStack.addSpacer(); | |
const subheader = list.addDate(new Date()); | |
subheader.applyRelativeStyle(); | |
subheader.font = Font.mediumSystemFont(13); | |
subheader.textColor = Color.gray(); | |
list.addSpacer(40); | |
const label = list.addText(friend.balance); | |
label.font = Font.blackSystemFont(43); | |
label.rightAlignText(); | |
label.minimumScaleFactor = 0.8; | |
label.textColor = Color.white(); | |
return list; | |
} | |
async function startConfig() { | |
if (config.runsInWidget) { | |
stopExecution("runInApp"); | |
} | |
apiToken = await getApiToken(); | |
const user = await getUser(); | |
const friends = await getFriends(); | |
const selectedFriend = await selectFriend(friends); | |
const data = { | |
user: user, | |
participant: selectedFriend, | |
apiToken: apiToken, | |
}; | |
const stringData = JSON.stringify(data); | |
saveToFile(stringData); | |
return data; | |
} | |
function getFileManagerPath() { | |
const fm = FileManager.iCloud(); | |
const dir = fm.joinPath(fm.documentsDirectory(), "splitwise-config.json"); | |
return [fm, dir]; | |
} | |
function saveToFile(data) { | |
const [fm, dir] = getFileManagerPath(); | |
fm.writeString(dir, data); | |
} | |
async function getLookup() { | |
const [fm, dir] = getFileManagerPath(); | |
if (fm.fileExists(dir)) { | |
return JSON.parse(fm.readString(dir)); | |
} else { | |
return await startConfig(); | |
} | |
} | |
async function getApiToken() { | |
if (apiToken) { | |
return apiToken; | |
} | |
await makeSureUserHasApiToken(); | |
const alert = new Alert(); | |
alert.title = "Enter your Splitwise API Token"; | |
alert.addTextField("API Token", ""); | |
alert.addAction("OK"); | |
alert.addCancelAction("Cancel"); | |
const buttonIndex = await alert.present(); | |
if (buttonIndex === -1 || alert.textFieldValue(0) === "") { | |
stopExecution("No API Token entered"); | |
} | |
return alert.textFieldValue(0); | |
} | |
async function makeSureUserHasApiToken() { | |
const alert = new Alert(); | |
alert.title = "Do you already have a Splitwise API Token?"; | |
alert.addAction("I have one"); | |
alert.addAction("Get API Token now"); | |
alert.addCancelAction("Cancel"); | |
const buttonIndex = await alert.present(); | |
if (buttonIndex === -1 || alert.textFieldValue(0) === "") { | |
stopExecution("No API Token entered"); | |
} | |
if (buttonIndex === 1) { | |
const infoAlert = new Alert(); | |
infoAlert.title = "Get your API Token"; | |
infoAlert.message = | |
"1. Go to https://secure.splitwise.com/apps/new\n2. Enter a name for the app\n3. Copy the API Token\nReturn to this app\n4. Paste it here"; | |
infoAlert.addAction("I have my API Token"); | |
infoAlert.addAction("Open link"); | |
infoAlert.addCancelAction("Cancel"); | |
const infoAlertIndex = await infoAlert.present(); | |
if (infoAlertIndex === -1) { | |
stopExecution("No API Token entered"); | |
} else if (infoAlertIndex === 1) { | |
Safari.open("https://secure.splitwise.com/apps/new"); | |
} | |
} | |
} | |
async function getFriend(id) { | |
const req = new Request( | |
`https://secure.splitwise.com/api/v3.0/get_friend/${id})` | |
); | |
req.method = "get"; | |
req.headers = { | |
Authorization: `Bearer ${apiToken}`, | |
"Content-Type": "application/json", | |
}; | |
const res = await req.loadJSON(); | |
const f = res.friend; | |
if (!f) { | |
stopExecution("Friend not found"); | |
} | |
const friend = { | |
name: f.first_name, | |
id: f.id, | |
balance: f.balance?.[0]?.amount || "0", | |
updatedAt: new Date(f.updated_at), | |
}; | |
return friend; | |
} | |
async function getFriends() { | |
const req = new Request("https://secure.splitwise.com/api/v3.0/get_friends"); | |
req.method = "get"; | |
req.headers = { | |
Authorization: `Bearer ${apiToken}`, | |
"Content-Type": "application/json", | |
}; | |
const res = await req.loadJSON(); | |
const friends = res.friends.map((f) => ({ | |
name: f.first_name, | |
fullName: f.last_name ? `${f.first_name} ${f.last_name}` : f.first_name, | |
id: f.id, | |
})); | |
return friends; | |
} | |
async function selectFriend(friends) { | |
const alert = new Alert(); | |
alert.title = "Select a friend"; | |
friends.forEach((f) => { | |
alert.addAction(f.fullName); | |
}); | |
alert.addCancelAction("Cancel"); | |
const selectedIndex = await alert.presentSheet(); | |
if (selectedIndex === -1) { | |
stopExecution("No friend selected"); | |
} | |
return friends[selectedIndex]; | |
} | |
async function getUser() { | |
const req = new Request( | |
"https://secure.splitwise.com/api/v3.0/get_current_user" | |
); | |
req.method = "get"; | |
req.headers = { | |
Authorization: `Bearer ${apiToken}`, | |
"Content-Type": "application/json", | |
}; | |
const user = await req.loadJSON(); | |
return { | |
id: user.user.id, | |
name: user.user.first_name, | |
}; | |
} | |
function stopExecution(message) { | |
Script.setShortcutOutput(message); | |
Script.complete(); | |
throw new Error(message); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Instructions