Created
July 3, 2025 12:02
-
-
Save laufhannes/45b75eae04dc0b2f7a7971d48e0b6836 to your computer and use it in GitHub Desktop.
Scriptable widget for Runalyze statistics
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-blue; icon-glyph: magic; | |
// For API details, see https://runalyze.com/doc/api/supporter#/Current%20Statistics/api_v1statisticscurrent_get | |
// Read access for current statistics requires Supporter or Premium level | |
// You need to set your API token as widget parameter or in line 62 | |
async function getImage(image, vImageURL) { | |
let fm = FileManager.iCloud() | |
let dir = fm.documentsDirectory() | |
if (fm.isDirectory(dir) == false) { | |
fm.createDirectory(dir, true) | |
} | |
let path = fm.joinPath(dir, image) | |
if (fm.fileExists(path)) { | |
await fm.downloadFileFromiCloud(path) | |
return fm.readImage(path) | |
} else { | |
let iconImage = await loadImage(vImageURL) | |
fm.writeImage(path, iconImage) | |
return iconImage | |
} | |
} | |
async function loadImage(imgUrl) { | |
const req = new Request(imgUrl) | |
return await req.loadImage() | |
} | |
function addFText2Stack(stackLeft, stackRight, stringLeft, stringRight) { | |
let textFont = new Font("GillSans-SemiBold", 14) | |
const text = stackLeft.addText(stringLeft); | |
const textRight = stackRight.addText(stringRight); | |
text.font = textFont; | |
textRight.font = textFont; | |
} | |
function buildWidgets(data){ | |
let mainText = new Font("GillSans-SemiBold", 14) | |
let widgetLeftStack = dataStack.addStack() | |
widgetLeftStack.layoutVertically() | |
dataStack.addSpacer(20) | |
let widgetRightStack = dataStack.addStack() | |
widgetRightStack.layoutVertically() | |
addFText2Stack(widgetLeftStack, widgetRightStack, "Effective VO2max", data.effectiveVO2max.toLocaleString(undefined, {maximumFractionDigits: 2})); | |
addFText2Stack(widgetLeftStack, widgetRightStack, "Marathon Shape", `${Math.round(100 * data.marathonShape)} %`); | |
addFText2Stack(widgetLeftStack, widgetRightStack, "Workload Ratio A:C", (data.fatigue / data.fitness).toLocaleString(undefined, {maximumFractionDigits: 2})); | |
addFText2Stack(widgetLeftStack, widgetRightStack, "HRV Baseline", `${Math.round(data.hrvBaseline)} (${Math.round(data.hrvNormalRange[0])}, ${Math.round(data.hrvNormalRange[1])})`); | |
widget.presentMedium() | |
} | |
//////////////////////////////////////////////////// | |
let apiToken = args.widgetParameter | |
if ( apiToken == null ) { apiToken = "XXX" } // Insert your own API token | |
let req = new Request('https://runalyze.com/api/v1/statistics/current'); | |
req.headers = {accept: 'application/json', token: apiToken}; | |
let response = await req.loadJSON(); | |
let widget = new ListWidget() | |
let widgetMainStack = widget.addStack() | |
widgetMainStack.layoutVertically() | |
widgetMainStack.centerAlignContent() | |
let logoStack = widgetMainStack.addStack(); | |
let dataStack = widgetMainStack.addStack(); | |
let isDarkMode = Device.isUsingDarkAppearance() | |
if (isDarkMode) { | |
logoImg = await getImage("runalyze-swoosh-white.png", "https://c4.runalyze.com/build/images/runalyze-header-swoosh.bf84fe43.png") //White letters | |
} else { | |
logoImg = await getImage("runalyze-swoosh.png", "https://c4.runalyze.com/assets/images/runalyze-swoosh.png") //Black letters | |
} | |
logoStack.size = new Size(220, 40) | |
let theLogo = logoStack.addImage(logoImg) | |
logoStack.centerAlignContent() | |
buildWidgets(response) | |
Script.complete() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment