iOS-Widgets/VodafoneDE/VodafoneDE.js
Benny Samir Hierl 1a138ccf26
fix issue with remaining days
Signed-off-by: Benny Samir Hierl <bennysamir@posteo.de>
2020-11-02 19:32:58 +01:00

401 lines
12 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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.2.2
Changelog:
v1.2.2:
- Fix remaining Day issue
v1.2.1:
- Code '40100' added for CallYa Tariff
v1.2.0:
- Option to set colors by widget parameters removed
- Option to set MeinVodafone credentails via widget parameters added
v1.1.1:
- new CallYa Tariff added: C_DIY_Data_National
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;
// 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', 'C_DIY_Data_National']
const codeList = ['-1', '45500', '40100']
////////////////////////////////////////////////////////////////////////////////
let backColor; //Widget background color
let backColor2; //Widget background color
let textColor; //Widget text color
let fillColor;
let strokeColor;
let widgetInputRAW = args.widgetParameter;
let widgetInput = null;
let user, pass, number
if (widgetInputRAW !== null) {
[user, pass, number] = widgetInputRAW.toString().split("|");
if(!user || !pass || !number) {
throw new Error("Invalid Widget parameter. Expected format: username|password|phonenumber")
}
if(/^49[\d]{5,}/.test(number) === false) {
throw new Error("Invalid phonenumber format. Expected format: 491721234567")
}
}
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(u, p) {
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": u,
"password": p,
})
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(user, pass, number) {
let cookies, msisdn;
if (user && pass && number) {
console.log("Login via MeinVodafone")
let { cookies: c }= await getSessionCookiesViaMeinVodafoneLogin(user, pass);
cookies = c;
msisdn = number;
} 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(user, pass, number)
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)
const gradient = new LinearGradient()
gradient.locations = [0, 1]
gradient.colors = [
new Color(backColor),
new Color(backColor2)
]
widget.backgroundGradient = gradient
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 + 2
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()
}