Skip to content

Instantly share code, notes, and snippets.

@fer0n
Created August 4, 2024 11:29
Show Gist options
  • Save fer0n/f9b64e71be2f53293587aa20ece4c33b to your computer and use it in GitHub Desktop.
Save fer0n/f9b64e71be2f53293587aa20ece4c33b to your computer and use it in GitHub Desktop.
Splitwise Widget for iOS via Scriptable
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);
}
@fer0n
Copy link
Author

fer0n commented Aug 4, 2024

Instructions

  • Download Scriptable
  • Add this script
  • Run it in the app once, follow the setup instructions
  • Add a small Scriptable widget, edit it and select the script

Unwatched logo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment