mirror of
https://github.com/ThisIsBenny/iOS-Widgets.git
synced 2025-04-19 23:37:41 +00:00
400 lines
12 KiB
JavaScript
400 lines
12 KiB
JavaScript
// Variables used by Scriptable.
|
||
// These must be at the very top of the file. Do not edit.
|
||
// icon-color: red; icon-glyph: broadcast-tower;
|
||
|
||
/**************
|
||
Version 1.1.0
|
||
|
||
Changelog:
|
||
v1.1.0:
|
||
- Login via MeinVodafone Login added
|
||
- Show Remaining Days
|
||
- MegaByte Support added for tariffs with <= 1 GB
|
||
v1.0.3:
|
||
- CallYa Support
|
||
- Write more useful information in the log, so that you can add support yourself if necessary
|
||
v1.0.2:
|
||
- Enhanced logging for CallYa troubeshooting
|
||
v1.0.1:
|
||
- Better Error handling
|
||
- Better logging
|
||
- Fallback Widget screen in case of an error
|
||
|
||
If you have problems or need help, ask for support here: https://github.com/ThisIsBenny/iOS-Widgets/issues
|
||
|
||
Credits:
|
||
- Sillium@GitHub (https://gist.github.com/Sillium/f904fb89444bc8dde12cfc07b8fa8728)
|
||
- Chaeimg@Github (https://github.com/chaeimg/battCircle)
|
||
**************/
|
||
|
||
// How many minutes should the cache be valid
|
||
let cacheMinutes = 60;
|
||
|
||
// Login via MeinVodafone Login.
|
||
const username = "";
|
||
const password = "";
|
||
const phonenumber = ""; // with leading 49 instead of 0. Example: 4917212345678
|
||
|
||
// Please add additional values to these list, in case that your contract/tarif isn't supported by these default values.
|
||
const containerList = ['Daten', 'D_EU_DATA']
|
||
const codeList = ['-1', '45500']
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
let backColor; //Widget background color
|
||
let backColor2; //Widget background color
|
||
let textColor; //Widget text color
|
||
let fillColor;
|
||
let strokeColor;
|
||
let useGradient = true
|
||
|
||
let widgetInputRAW = args.widgetParameter;
|
||
let widgetInput = null;
|
||
|
||
if (widgetInputRAW !== null) {
|
||
widgetInput = widgetInputRAW.toString().split("|");
|
||
}
|
||
|
||
// BackgroundColor|TextColor|CircleFillColor|CircleStrokeColor
|
||
if (widgetInput !== null && widgetInput.length == 4) {
|
||
backColor = widgetInput[0];
|
||
textColor = widgetInput[1];
|
||
fillColor = widgetInput[2];
|
||
strokeColor = widgetInput[3];
|
||
useGradient = false
|
||
} else if (Device.isUsingDarkAppearance()) {
|
||
backColor = '111111';
|
||
backColor2 = '222222';
|
||
textColor = 'EDEDED';
|
||
fillColor = 'EDEDED';
|
||
strokeColor = '121212';
|
||
} else {
|
||
backColor = 'D32D1F';
|
||
backColor2 = '76150C';
|
||
textColor = 'EDEDED';
|
||
fillColor = 'EDEDED';
|
||
strokeColor = 'B0B0B0';
|
||
}
|
||
|
||
const canvas = new DrawContext();
|
||
const canvSize = 200;
|
||
const canvTextSize = 36;
|
||
|
||
const canvWidth = 22;
|
||
const canvRadius = 80;
|
||
|
||
canvas.opaque = false
|
||
canvas.size = new Size(canvSize, canvSize);
|
||
canvas.respectScreenScale = true;
|
||
|
||
function sinDeg(deg) {
|
||
return Math.sin((deg * Math.PI) / 180);
|
||
}
|
||
|
||
function cosDeg(deg) {
|
||
return Math.cos((deg * Math.PI) / 180);
|
||
}
|
||
|
||
function drawArc(ctr, rad, w, deg) {
|
||
bgx = ctr.x - rad;
|
||
bgy = ctr.y - rad;
|
||
bgd = 2 * rad;
|
||
bgr = new Rect(bgx, bgy, bgd, bgd);
|
||
|
||
canvas.setFillColor(new Color(fillColor));
|
||
canvas.setStrokeColor(new Color(strokeColor));
|
||
canvas.setLineWidth(w);
|
||
canvas.strokeEllipse(bgr);
|
||
|
||
for (t = 0; t < deg; t++) {
|
||
rect_x = ctr.x + rad * sinDeg(t) - w / 2;
|
||
rect_y = ctr.y - rad * cosDeg(t) - w / 2;
|
||
rect_r = new Rect(rect_x, rect_y, w, w);
|
||
canvas.fillEllipse(rect_r);
|
||
}
|
||
}
|
||
|
||
function getTimeRemaining(endtime){
|
||
const total = Date.parse(endtime) - Date.parse(new Date());
|
||
const seconds = Math.floor( (total/1000) % 60 );
|
||
const minutes = Math.floor( (total/1000/60) % 60 );
|
||
const hours = Math.floor( (total/(1000*60*60)) % 24 );
|
||
const days = Math.floor( total/(1000*60*60*24) );
|
||
|
||
return {
|
||
total,
|
||
days,
|
||
hours,
|
||
minutes,
|
||
seconds
|
||
};
|
||
}
|
||
|
||
async function getSessionCookiesViaNetworkLogin() {
|
||
let req;
|
||
req = new Request("https://www.vodafone.de/mint/rest/session/start")
|
||
req.method = "POST";
|
||
req.headers = {
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'application/json'
|
||
}
|
||
|
||
req.body = JSON.stringify({
|
||
"authMethod": "AAA",
|
||
"byPIN": false,
|
||
"additionalParameters": {
|
||
"deviceType": "Smartphone"
|
||
}
|
||
})
|
||
try {
|
||
let res = await req.loadJSON()
|
||
return { cookies: req.response.cookies, msisdn: res.msisdn}
|
||
} catch (e) {
|
||
console.log("Login failed! Please check if Wifi is disabled.")
|
||
throw new Error(`Login failed with HTTP-Status-Code ${req.response.statusCode}`)
|
||
}
|
||
};
|
||
|
||
async function getSessionCookiesViaMeinVodafoneLogin() {
|
||
let req;
|
||
req = new Request("https://www.vodafone.de/mint/rest/session/start")
|
||
req.method = "POST";
|
||
req.headers = {
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'application/json'
|
||
}
|
||
|
||
req.body = JSON.stringify({
|
||
"clientType": "Portal",
|
||
"username": username,
|
||
"password": password,
|
||
})
|
||
try {
|
||
let res = await req.loadJSON()
|
||
return { cookies: req.response.cookies}
|
||
} catch (e) {
|
||
console.log("Login failed!")
|
||
throw new Error(`Login failed with HTTP-Status-Code ${req.response.statusCode}`)
|
||
}
|
||
};
|
||
|
||
async function getUsage() {
|
||
let cookies, msisdn;
|
||
if (username !== "" && password !== "" && msisdn !== "") {
|
||
console.log("Login via MeinVodafone")
|
||
let { cookies: c }= await getSessionCookiesViaMeinVodafoneLogin();
|
||
cookies = c;
|
||
msisdn = phonenumber;
|
||
} else {
|
||
console.log("Login via Network")
|
||
let { cookies: c, msisdn: m } = await getSessionCookiesViaNetworkLogin();
|
||
cookies = c;
|
||
msisdn = m;
|
||
}
|
||
let CookieValues = cookies.map(function(v){
|
||
return v.name + "=" + v.value
|
||
})
|
||
let req;
|
||
req = new Request(`https://www.vodafone.de/api/enterprise-resources/core/bss/sub-nil/mobile/payment/service-usages/subscriptions/${msisdn}/unbilled-usage`)
|
||
req.headers = {
|
||
'x-vf-api': '1499082775305',
|
||
'Referer': 'https://www.vodafone.de/meinvodafone/services/',
|
||
'Accept': 'application/json',
|
||
'Cookies': CookieValues.join(';')
|
||
}
|
||
try {
|
||
let res = await req.loadJSON()
|
||
console.log("unbilled-usage loaded")
|
||
let datenContainer = res['serviceUsageVBO']['usageAccounts'][0]['usageGroup'].find(function(v){
|
||
return containerList.includes(v.container)
|
||
})
|
||
|
||
if (datenContainer === undefined) {
|
||
const ErrorMsg = "Can't find usageGroup with supported Container: " + containerList.join(', ') + ".";
|
||
console.log(ErrorMsg)
|
||
|
||
const listOfContainerNamesInResponse = res['serviceUsageVBO']['usageAccounts'][0]['usageGroup'].map(function (v) {
|
||
return v.container;
|
||
})
|
||
console.log("Please check the following list to find the correct container name for your case and adjust the list of container names at the beginnging: " + listOfContainerNamesInResponse.join(", "))
|
||
throw new Error(ErrorMsg)
|
||
}
|
||
|
||
let datenvolumen;
|
||
if (datenContainer.usage.length == 1) {
|
||
datenvolumen = datenContainer.usage[0]
|
||
} else {
|
||
datenvolumen = datenContainer.usage.find(function (v) {
|
||
return codeList.includes(v.code)
|
||
})
|
||
}
|
||
|
||
if (datenvolumen === undefined) {
|
||
const ErrorMsg = "Can't find Usage with supported Codes: " + codeList.join(', ') + ".";
|
||
console.log(ErrorMsg)
|
||
|
||
const listOfCodeInResponse = datenContainer.usage.map(function(v) {
|
||
return `Code: "${v.code}" for "${v.description}"`;
|
||
})
|
||
console.log("Please check the following list to find the correct code for your case and adjust the list of codes at the beginnging: " + listOfCodeInResponse.join(", "))
|
||
throw new Error(ErrorMsg)
|
||
}
|
||
|
||
|
||
let endDate = datenvolumen.endDate;
|
||
if (endDate == null) {
|
||
endDate = res['serviceUsageVBO']['billDetails']['billCycleEndDate'] || null
|
||
}
|
||
|
||
return {
|
||
total: datenvolumen.total,
|
||
used: datenvolumen.used,
|
||
remaining: datenvolumen.remaining,
|
||
endDate
|
||
}
|
||
} catch (e) {
|
||
console.log("Loading usage data failed")
|
||
throw e
|
||
}
|
||
};
|
||
|
||
var today = new Date();
|
||
|
||
// Set up the file manager.
|
||
const files = FileManager.local()
|
||
|
||
// Set up cache .
|
||
const cachePath = files.joinPath(files.documentsDirectory(), "widget-vodafone")
|
||
const cacheExists = files.fileExists(cachePath)
|
||
const cacheDate = cacheExists ? files.modificationDate(cachePath) : 0
|
||
|
||
// Get Data
|
||
let data;
|
||
let lastUpdate
|
||
try {
|
||
// If cache exists and it's been less than 30 minutes since last request, use cached data.
|
||
if (cacheExists&& (today.getTime() - cacheDate.getTime()) < (cacheMinutes * 60 * 1000)) {
|
||
console.log("Get from Cache")
|
||
data = JSON.parse(files.readString(cachePath))
|
||
lastUpdate = cacheDate
|
||
} else {
|
||
console.log("Get from API")
|
||
data = await getUsage()
|
||
console.log("Write Data to Cache")
|
||
try {
|
||
files.writeString(cachePath, JSON.stringify(data))
|
||
} catch(e) {
|
||
console.log("Creating Cache failed!")
|
||
console.log(e)
|
||
}
|
||
|
||
lastUpdate = today
|
||
}
|
||
} catch (e) {
|
||
console.error(e)
|
||
if (cacheExists) {
|
||
console.log("Get from Cache")
|
||
data = JSON.parse(files.readString(cachePath))
|
||
lastUpdate = cacheDate
|
||
} else {
|
||
console.log("No fallback to cache possible. Due to missing cache.")
|
||
}
|
||
}
|
||
|
||
// Create Widget
|
||
let widget = new ListWidget();
|
||
|
||
widget.setPadding(10, 10, 10, 10)
|
||
|
||
if (data !== undefined) {
|
||
console.log(data)
|
||
if (useGradient) {
|
||
const gradient = new LinearGradient()
|
||
gradient.locations = [0, 1]
|
||
gradient.colors = [
|
||
new Color(backColor),
|
||
new Color(backColor2)
|
||
]
|
||
widget.backgroundGradient = gradient
|
||
} else {
|
||
widget.backgroundColor = new Color(backColor)
|
||
}
|
||
|
||
let firstLineStack = widget.addStack()
|
||
|
||
let provider = firstLineStack.addText("Vodafone")
|
||
provider.font = Font.mediumSystemFont(12)
|
||
provider.textColor = new Color(textColor)
|
||
|
||
// Last Update
|
||
firstLineStack.addSpacer()
|
||
let lastUpdateText = firstLineStack.addDate(lastUpdate)
|
||
lastUpdateText.font = Font.mediumSystemFont(8)
|
||
lastUpdateText.rightAlignText()
|
||
lastUpdateText.applyTimeStyle()
|
||
lastUpdateText.textColor = Color.lightGray()
|
||
|
||
widget.addSpacer()
|
||
|
||
let remainingPercentage = (100 / data.total * data.remaining).toFixed(0);
|
||
|
||
drawArc(
|
||
new Point(canvSize / 2, canvSize / 2),
|
||
canvRadius,
|
||
canvWidth,
|
||
Math.floor(remainingPercentage * 3.6)
|
||
);
|
||
|
||
const canvTextRect = new Rect(
|
||
0,
|
||
100 - canvTextSize / 2,
|
||
canvSize,
|
||
canvTextSize
|
||
);
|
||
canvas.setTextAlignedCenter();
|
||
canvas.setTextColor(new Color(textColor));
|
||
canvas.setFont(Font.boldSystemFont(canvTextSize));
|
||
canvas.drawTextInRect(`${remainingPercentage}%`, canvTextRect);
|
||
|
||
const canvImage = canvas.getImage();
|
||
let image = widget.addImage(canvImage);
|
||
image.centerAlignImage()
|
||
|
||
widget.addSpacer()
|
||
|
||
// Total Values
|
||
let totalValues;
|
||
if (parseInt(data.total) < 1000) {
|
||
totalValues = `${data.remaining} MB von ${data.total} MB`
|
||
} else {
|
||
let remainingGB = (data.remaining / 1024).toFixed(2)
|
||
let totalGB = (data.total / 1024).toFixed(0)
|
||
totalValues = `${remainingGB} GB von ${totalGB} GB`
|
||
}
|
||
let totalValuesText = widget.addText(totalValues)
|
||
totalValuesText.font = Font.mediumSystemFont(12)
|
||
totalValuesText.centerAlignText()
|
||
totalValuesText.textColor = new Color(textColor)
|
||
|
||
// Remaining Days
|
||
if (data.endDate) {
|
||
widget.addSpacer(5)
|
||
let remainingDays = getTimeRemaining(data.endDate).days + 1
|
||
let remainingDaysText = widget.addText(`${remainingDays} Tage verleibend`)
|
||
remainingDaysText.font = Font.mediumSystemFont(8)
|
||
remainingDaysText.centerAlignText()
|
||
remainingDaysText.textColor = new Color(textColor)
|
||
}
|
||
|
||
} else {
|
||
let fallbackText = widget.addText("Es ist ein Fehler aufgetreten! Bitte prüfen Sie die Logs direkt in der App.")
|
||
fallbackText.font = Font.mediumSystemFont(12)
|
||
fallbackText.textColor = new Color(textColor)
|
||
}
|
||
|
||
if(!config.runsInWidget) {
|
||
await widget.presentSmall()
|
||
} else {
|
||
// Tell the system to show the widget.
|
||
Script.setWidget(widget)
|
||
Script.complete()
|
||
}
|