Skip to content

Instantly share code, notes, and snippets.

@wolflu05
Last active July 2, 2024 14:58
Show Gist options
  • Save wolflu05/05ee63670e095fbe21420645f4b31d5a to your computer and use it in GitHub Desktop.
Save wolflu05/05ee63670e095fbe21420645f4b31d5a to your computer and use it in GitHub Desktop.

Telekom Widget

This is an iOS scriptable widget which shows the mobile data usage.

image

Features

  • show percentage and actual usage
  • show a status bar with an extrapolated bar to see how much you could use to have the remaining days in month each day the same data available
  • show remaining time
  • show last updated and connected/not connected
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: purple; icon-glyph: magic;
const API_URL = "https://pass.telekom.de/api/service/generic/v1/status";
const LANGUAGE = "de";
async function createWidget() {
// get usage data
const { error, data, connection } = await getUsageData();
// calculate data
const now = new Date();
const lastUpdated = new Date(data.date);
const firstDayOfMonth = new Date(lastUpdated.getFullYear(), lastUpdated.getMonth(), -1, 0, 0, 0, 0);
const lastDayOfMonth = new Date(lastUpdated.getFullYear(), lastUpdated.getMonth() + 1, 0, 0, 0, 0, 0);
const passedSeconds = lastUpdated - firstDayOfMonth;
const totalSeconds = lastDayOfMonth - firstDayOfMonth;
const extrapolatedPercentage = passedSeconds / totalSeconds;
const usedPercentage = data.usedVolume / data.initialVolume;
let usedColor = "#4BB543";
if (usedPercentage > extrapolatedPercentage) usedColor = "#ff6600";
if (usedPercentage >= 0.75) usedColor = "#ff6600";
if (usedPercentage >= 0.9) usedColor = "#ff0000";
// ---------- construct widget ----------
const widget = new ListWidget();
widget.backgroundColor = new Color("#DD0273");
// ---------- logo headline ----------
const headlineRow = widget.addStack();
headlineRow.centerAlignContent();
// logo
const telekomImageRAW = await getImage("telekom.png", "https://i.imgur.com/wKKfJdwt.png");
const telekomImageStack = headlineRow.addImage(telekomImageRAW);
telekomImageStack.imageSize = new Size(30, 30);
headlineRow.addSpacer(5);
// headline
const headlineTextStack = headlineRow.addStack();
headlineTextStack.layoutVertically();
const headlineTextLabel = headlineTextStack.addText("Mobile Daten");
headlineTextLabel.leftAlignText();
headlineTextLabel.font = Font.mediumSystemFont(12);
headlineTextLabel.textColor = new Color("#ffffff");
widget.addSpacer(2);
// ---------- Percent ----------
const percentageRow = widget.addStack();
percentageRow.addSpacer();
const percentageText = percentageRow.addText(`${data.usedPercentage}%`);
percentageText.centerAlignText();
percentageText.font = Font.heavySystemFont(36);
percentageText.textColor = new Color("#ffffff");
percentageRow.addSpacer();
widget.addSpacer(2);
// ---------- Status Bar ----------
const statusRow = widget.addStack();
statusRow.size = new Size(130,13);
const sctx = new DrawContext();
sctx.opaque = false;
sctx.size = new Size(500, 50);
const addBarToCtx = (percentage, color) => {
const path = new Path();
path.addRoundedRect(new Rect(1,1,500 * percentage,50),25,25);
sctx.addPath(path);
sctx.setFillColor(color);
sctx.fillPath();
}
addBarToCtx(1, new Color("#cccccc"));
addBarToCtx(extrapolatedPercentage, new Color("#888888"));
addBarToCtx(usedPercentage, new Color(usedColor));
const statusBarImage = sctx.getImage();
const statusBar = statusRow.addImage(statusBarImage);
statusBar.centerAlignImage();
widget.addSpacer(1);
// ---------- Total volume indicator ----------
const totalIndicatorRow = widget.addStack();
totalIndicatorRow.layoutVertically();
const totalIndicatorText = totalIndicatorRow.addText(`${data.usedVolumeStr}/${data.initialVolumeStr}`);
totalIndicatorText.font = Font.boldRoundedSystemFont(13);
const remainingTimeIndicatorText = totalIndicatorRow.addText(`verbl. für ${data.remainingTimeStr}`);
remainingTimeIndicatorText.font = Font.lightRoundedSystemFont(11);
widget.addSpacer(4);
// ---------- Last update time indicator ----------
const lastUpdatedRow = widget.addStack();
lastUpdatedRow.centerAlignContent();
lastUpdatedRow.addSpacer();
const radioSymbol = SFSymbol.named(connection ? "antenna.radiowaves.left.and.right" : "antenna.radiowaves.left.and.right.slash");
radioSymbol.applyFont(Font.systemFont(11));
const radioSymbolImage = lastUpdatedRow.addImage(radioSymbol.image);
radioSymbolImage.resizable = false;
radioSymbolImage.tintColor = new Color("#ffffff");
lastUpdatedRow.addSpacer(4);
let outText = "";
const lastUpdatedDayDiff = dayDiff(now, lastUpdated);
const lastUpdatedTime = `${`${lastUpdated.getHours()}`.padStart(2,"0")}:${`${lastUpdated.getMinutes()}`.padStart(2,"0")}`;
if (lastUpdatedDayDiff === 0) outText = `${lastUpdatedTime}`;
else if (lastUpdatedDayDiff === -1) outText = `gestern ${lastUpdatedTime}`;
else outText = `vor ${Math.abs(lastUpdatedDayDiff)} Tagen`;
const lastUpdatedText = lastUpdatedRow.addText(outText);
lastUpdatedText.font = Font.lightRoundedSystemFont(11);
lastUpdatedRow.addSpacer();
return widget;
}
async function getUsageData() {
const fm = FileManager.local();
const dir = fm.documentsDirectory();
const path = fm.joinPath(dir, "scriptable-telekom.json");
try {
const req = new Request(API_URL);
req.headers = {
// API only answers for mobile Safari
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Mobile/15E148 Safari/604.1"
};
const data = await req.loadJSON();
data.date = Date.now();
fm.writeString(path, JSON.stringify(data, null, 2));
return { error: null, data, connection: true };
} catch (err) {
const data = JSON.parse(fm.readString(path), null);
if (!data || !data.usedPercentage) {
return { error: "Please disable WiFi for initial execution." };
}
return { error: null, data, connection: false };
}
}
async function getImage(key, url) {
const fm = FileManager.local();
const dir = fm.documentsDirectory();
const path = fm.joinPath(dir, key);
if(fm.fileExists(path)) {
return fm.readImage(path);
}
const imageReq = new Request(url);
const image = await imageReq.loadImage();
fm.writeImage(path, image);
return image;
}
function dayDiff(date1, date2) {
return Math.ceil((date2 - date1) / (1000 * 60 * 60 * 24));
}
function isInSameMonth(date1, date2) {
return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth();
}
function createErrorWidget(text) {
const errorWidget = new ListWidget();
console.error(text);
}
const widget = await createWidget().catch((e) => console.error(e));
if(config.runsInWidget) {
Script.setWidget(widget);
} else {
widget.presentSmall();
}
Script.complete();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment