Vodafone Widget v2 Beta 1

Signed-off-by: Benny Samir Hierl <bennysamir@posteo.de>
This commit is contained in:
Benny Samir Hierl 2020-12-27 15:30:27 +01:00
parent 30f9564a06
commit 22357c4650
No known key found for this signature in database
GPG key ID: 69DE3C3C097DB7F7

View file

@ -3,9 +3,16 @@
// icon-color: red; icon-glyph: broadcast-tower; // icon-color: red; icon-glyph: broadcast-tower;
/************** /**************
Version 1.2.4 Version 2.0.0
Changelog: Changelog:
v2.0.0:
- Disable "Dark Mode Support"
- Medium Widget Support
- Switch between used and remaining volume
- Use MSISDN for Cache-Name
- Show amount for prepaid cards
- Show remaining days as progress-bar
v1.2.4: v1.2.4:
- use color.dynamic - use color.dynamic
v1.2.3: v1.2.3:
@ -41,18 +48,28 @@ Credits:
**************/ **************/
// How many minutes should the cache be valid // How many minutes should the cache be valid
let cacheMinutes = 60; const cacheMinutes = 60
// Set to false if the widget should display always in red
const darkModeSupport = true
// Switch between remaining and used contingent. If you want show the used contingent, then change the value from true to false
const showRemainingContingent = true
// To disable the progressbar for the remaining days, you have to change the value from true to false
const showRemainingDaysAsProgressbar = true
// Please add additional values to these list, in case that your contract/tarif isn't supported by these default values. // 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 containerList = ['Daten', 'D_EU_DATA', 'C_DIY_Data_National']
const codeList = ['-1', '45500', '40100'] const codeList = ['-1', '-5' ,'45500', '40100']
// Dev Settings
const debug = false
config.widgetFamily = config.widgetFamily || 'small'
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
let widgetInputRAW = args.widgetParameter; let widgetInputRAW = args.widgetParameter;
let widgetInput = null; let widgetInput = null;
let user, pass, number let user, pass, number
if (widgetInputRAW !== null) { if (widgetInputRAW !== null) {
[user, pass, number] = widgetInputRAW.toString().split("|"); [user, pass, number] = widgetInputRAW.toString().split("|");
@ -64,56 +81,115 @@ if (widgetInputRAW !== null) {
} }
} }
const backColor = Color.dynamic(new Color('D32D1F'), new Color('111111')); const h = 5
const backColor2 = Color.dynamic(new Color('76150C'), new Color('222222')); let width
const textColor = Color.dynamic(new Color('EDEDED'), new Color('EDEDED')); if (config.widgetFamily === 'small') {
const fillColor = Color.dynamic(new Color('EDEDED'), new Color('EDEDED')); width = 200
const strokeColor = Color.dynamic(new Color('B0B0B0'), new Color('121212')); } else {
width = 400
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) { let backColor = new Color('D32D1F')
return Math.cos((deg * Math.PI) / 180); let backColor2 = new Color('93291E')
let textColor = new Color('EDEDED')
let fillColor = new Color('EDEDED')
let strokeColor = new Color('B0B0B0')
if (darkModeSupport) {
backColor = Color.dynamic(backColor, new Color('111111'))
backColor2 = Color.dynamic(backColor2, new Color('222222'))
textColor = Color.dynamic(textColor, new Color('EDEDED'))
fillColor = Color.dynamic(fillColor, new Color('EDEDED'))
strokeColor = Color.dynamic(strokeColor, new Color('121212'))
} }
function drawArc(ctr, rad, w, deg) { function creatProgress(total, havegone) {
bgx = ctr.x - rad; const context = new DrawContext()
bgy = ctr.y - rad; context.size = new Size(width, h)
bgd = 2 * rad; context.opaque = false
bgr = new Rect(bgx, bgy, bgd, bgd); context.respectScreenScale = false
canvas.setFillColor(fillColor); // Background Path
canvas.setStrokeColor(strokeColor); context.setFillColor(fillColor)
canvas.setLineWidth(w); const path = new Path()
canvas.strokeEllipse(bgr); path.addRoundedRect(new Rect(0, 0, width, h), 3, 2)
context.addPath(path)
context.fillPath()
for (t = 0; t < deg; t++) { // Progress Path
rect_x = ctr.x + rad * sinDeg(t) - w / 2; context.setFillColor(strokeColor)
rect_y = ctr.y - rad * cosDeg(t) - w / 2; const path1 = new Path()
rect_r = new Rect(rect_x, rect_y, w, w); const path1width = (width * (havegone / total) > width) ? width : width * (havegone / total)
canvas.fillEllipse(rect_r); path1.addRoundedRect(new Rect(0, 0, path1width, h), 3, 2)
context.addPath(path1)
context.fillPath()
return context.getImage()
}
function getDiagram(percentage) {
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(fillColor)
canvas.setStrokeColor(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 sinDeg(deg) {
return Math.sin((deg * Math.PI) / 180)
}
function cosDeg(deg) {
return Math.cos((deg * Math.PI) / 180)
}
const canvas = new DrawContext()
const canvSize = 200
const canvTextSize = 36
const canvWidth = 10
const canvRadius = 80
canvas.opaque = false
canvas.size = new Size(canvSize, canvSize)
canvas.respectScreenScale = true
drawArc(
new Point(canvSize / 2, canvSize / 2),
canvRadius,
canvWidth,
Math.floor(percentage * 3.6)
)
const canvTextRect = new Rect(
0,
100 - canvTextSize / 2,
canvSize,
canvTextSize
)
canvas.setTextAlignedCenter()
canvas.setTextColor(textColor)
canvas.setFont(Font.boldSystemFont(canvTextSize))
canvas.drawTextInRect(`${percentage}%`, canvTextRect)
return canvas.getImage()
} }
function getTimeRemaining(endtime) { function getTimeRemaining(endtime) {
const total = Date.parse(endtime) - Date.parse(new Date()); const total = Date.parse(endtime) - Date.parse(new Date())
const seconds = Math.floor((total / 1000) % 60); const seconds = Math.floor((total / 1000) % 60)
const minutes = Math.floor((total / 1000 / 60) % 60); const minutes = Math.floor((total / 1000 / 60) % 60)
const hours = Math.floor((total / (1000 * 60 * 60)) % 24); const hours = Math.floor((total / (1000 * 60 * 60)) % 24)
const days = Math.floor(total / (1000 * 60 * 60 * 24)); const days = Math.floor(total / (1000 * 60 * 60 * 24))
return { return {
total, total,
@ -121,7 +197,7 @@ function getTimeRemaining(endtime) {
hours, hours,
minutes, minutes,
seconds seconds
}; }
} }
async function getSessionCookiesViaNetworkLogin() { async function getSessionCookiesViaNetworkLogin() {
@ -147,7 +223,7 @@ async function getSessionCookiesViaNetworkLogin() {
console.log("Login failed! Please check if Wifi is disabled.") console.log("Login failed! Please check if Wifi is disabled.")
throw new Error(`Login failed with HTTP-Status-Code ${req.response.statusCode}`) throw new Error(`Login failed with HTTP-Status-Code ${req.response.statusCode}`)
} }
}; }
async function getSessionCookiesViaMeinVodafoneLogin(u, p) { async function getSessionCookiesViaMeinVodafoneLogin(u, p) {
let req; let req;
@ -170,25 +246,25 @@ async function getSessionCookiesViaMeinVodafoneLogin(u, p) {
console.log("Login failed!") console.log("Login failed!")
throw new Error(`Login failed with HTTP-Status-Code ${req.response.statusCode}`) throw new Error(`Login failed with HTTP-Status-Code ${req.response.statusCode}`)
} }
}; }
async function getUsage(user, pass, number) { async function getUsage(user, pass, number) {
let cookies, msisdn; let cookies, msisdn
if (user && pass && number) { if (user && pass && number) {
console.log("Login via MeinVodafone") console.log("Login via MeinVodafone")
let { cookies: c } = await getSessionCookiesViaMeinVodafoneLogin(user, pass); let { cookies: c } = await getSessionCookiesViaMeinVodafoneLogin(user, pass)
cookies = c; cookies = c
msisdn = number; msisdn = number
} else { } else {
console.log("Login via Network") console.log("Login via Network")
let { cookies: c, msisdn: m } = await getSessionCookiesViaNetworkLogin(); let { cookies: c, msisdn: m } = await getSessionCookiesViaNetworkLogin()
cookies = c; cookies = c
msisdn = m; msisdn = m
} }
let CookieValues = cookies.map(function (v) { let CookieValues = cookies.map(function (v) {
return v.name + "=" + v.value return v.name + "=" + v.value
}) })
let req; 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 = new Request(`https://www.vodafone.de/api/enterprise-resources/core/bss/sub-nil/mobile/payment/service-usages/subscriptions/${msisdn}/unbilled-usage`)
req.headers = { req.headers = {
'x-vf-api': '1499082775305', 'x-vf-api': '1499082775305',
@ -198,72 +274,91 @@ async function getUsage(user, pass, number) {
} }
try { try {
let res = await req.loadJSON() let res = await req.loadJSON()
if(!res['serviceUsageVBO'] || !res['serviceUsageVBO']['usageAccounts'] || !res['serviceUsageVBO']['usageAccounts'][0]) {
if (debug) {
console.log(JSON.stringify(res, null, 2))
}
}
console.log("unbilled-usage loaded") console.log("unbilled-usage loaded")
let datenContainer = res['serviceUsageVBO']['usageAccounts'][0]['usageGroup'].find(function (v) { if (debug) {
console.log(JSON.stringify(res['serviceUsageVBO']['usageAccounts'][0], null, 2))
}
const marketCode = res['serviceUsageVBO']['usageAccounts'][0]['details']['marketCode']
const billDate = res['serviceUsageVBO']['usageAccounts'][0]['details']['billDate']
const amount = res['serviceUsageVBO']['usageAccounts'][0]['details']['amount']
let usage = []
// Get Main Container
let container = res['serviceUsageVBO']['usageAccounts'][0]['usageGroup'].filter(function (v) {
return containerList.includes(v.container) return containerList.includes(v.container)
}) })
if (datenContainer === undefined) { if (container.length === 0) {
const ErrorMsg = "Can't find usageGroup with supported Container: " + containerList.join(', ') + "."; const ErrorMsg = "Can't find usageGroup with supported Container: " + containerList.join(', ') + ".";
console.log(ErrorMsg) console.log(ErrorMsg)
const listOfContainerNamesInResponse = res['serviceUsageVBO']['usageAccounts'][0]['usageGroup'].map(function (v) { const listOfContainerNamesInResponse = res['serviceUsageVBO']['usageAccounts'][0]['usageGroup'].map(function (v) {
return v.container; 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(", ")) 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) 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) { for(let i = 0; i < container.length; i++) {
for (let j = 0; j < container[i]['usage'].length; j++) {
if (codeList.includes(container[i]['usage'][j]['code'])) {
usage.push(container[i]['usage'][j])
}
}
}
if (usage.length === 0) {
const ErrorMsg = "Can't find Usage with supported Codes: " + codeList.join(', ') + "."; const ErrorMsg = "Can't find Usage with supported Codes: " + codeList.join(', ') + ".";
console.log(ErrorMsg) console.log(ErrorMsg)
const listOfCodeInResponse = datenContainer.usage.map(function (v) { const listOfCodeInResponse = []
return `Code: "${v.code}" for "${v.description}"`; for(let i = 0; i < container.length; i++) {
}) for (let j = 0; j < container[i]['usage'].length; j++) {
listOfCodeInResponse.push(`Code: "${container[i]['usage'][j].code}" for "${container[i]['usage'][j].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(", ")) 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) throw new Error(ErrorMsg)
} }
let endDate = usage[0].endDate
let endDate = datenvolumen.endDate;
if (endDate == null) { if (endDate == null) {
endDate = res['serviceUsageVBO']['billDetails']['billCycleEndDate'] || null endDate = res['serviceUsageVBO']['billDetails']['billCycleEndDate'] || null
} }
return { return {
total: datenvolumen.total, billDate,
used: datenvolumen.used, endDate,
remaining: datenvolumen.remaining, amount,
endDate marketCode,
usage
} }
} catch (e) { } catch (e) {
console.log("Loading usage data failed") console.log("Loading usage data failed")
throw e throw e
} }
}; }
var today = new Date(); var today = new Date()
// Set up the file manager. // Set up the file manager.
const files = FileManager.local() const files = FileManager.local()
// Set up cache . // Set up cache
const cachePath = files.joinPath(files.documentsDirectory(), "widget-vodafone") const cacheNamePostfix = (number) ? number.substr(number.length - 4) : 'networkLogin'
const cachePath = files.joinPath(files.documentsDirectory(), "widget-vodafone-" + cacheNamePostfix)
const cacheExists = files.fileExists(cachePath) const cacheExists = files.fileExists(cachePath)
const cacheDate = cacheExists ? files.modificationDate(cachePath) : 0 const cacheDate = cacheExists ? files.modificationDate(cachePath) : 0
// Get Data // Get Data
let data; let data
let lastUpdate let lastUpdate
try { try {
// If cache exists and it's been less than 30 minutes since last request, use cached data. // If cache exists and it's been less than 30 minutes since last request, use cached data.
@ -301,7 +396,9 @@ let widget = new ListWidget();
widget.setPadding(10, 10, 10, 10) widget.setPadding(10, 10, 10, 10)
if (data !== undefined) { if (data !== undefined) {
console.log(data) if(debug) {
console.log(JSON.stringify(data, null, 2))
}
const gradient = new LinearGradient() const gradient = new LinearGradient()
gradient.locations = [0, 1] gradient.locations = [0, 1]
gradient.colors = [ gradient.colors = [
@ -316,62 +413,93 @@ if (data !== undefined) {
provider.font = Font.mediumSystemFont(12) provider.font = Font.mediumSystemFont(12)
provider.textColor = textColor provider.textColor = textColor
if (data.marketCode === 'MMO') {
widget.addSpacer(2)
const amount = widget.addText(`Guthaben: ${data.amount.replace('.', ',')}`)
amount.font = Font.systemFont(8)
amount.textColor = textColor
}
// Last Update // Last Update
firstLineStack.addSpacer() firstLineStack.addSpacer()
let lastUpdateText = firstLineStack.addDate(lastUpdate) let lastUpdateText = firstLineStack.addDate(lastUpdate)
lastUpdateText.font = Font.mediumSystemFont(8) lastUpdateText.font = Font.systemFont(8)
lastUpdateText.rightAlignText() lastUpdateText.rightAlignText()
lastUpdateText.applyTimeStyle() lastUpdateText.applyTimeStyle()
lastUpdateText.textColor = Color.lightGray() lastUpdateText.textColor = Color.lightGray()
widget.addSpacer() widget.addSpacer()
let remainingPercentage = (100 / data.total * data.remaining).toFixed(0); const stack = widget.addStack()
stack.layoutHorizontally()
drawArc( data.usage.slice(0, (config.widgetFamily === 'small' ? 1 : 2)).forEach((v) => {
new Point(canvSize / 2, canvSize / 2), const column = stack.addStack()
canvRadius, column.layoutVertically()
canvWidth, column.centerAlignContent()
Math.floor(remainingPercentage * 3.6)
);
const canvTextRect = new Rect( const percentage = (100 / v.total * (showRemainingContingent ? v.remaining : v.used)).toFixed(0);
0, const imageStack = column.addStack()
100 - canvTextSize / 2, imageStack.layoutHorizontally()
canvSize, imageStack.addSpacer()
canvTextSize imageStack.addImage(getDiagram(percentage));
); imageStack.addSpacer()
canvas.setTextAlignedCenter(); column.addSpacer(2)
canvas.setTextColor(textColor);
canvas.setFont(Font.boldSystemFont(canvTextSize));
canvas.drawTextInRect(`${remainingPercentage}%`, canvTextRect);
const canvImage = canvas.getImage(); // Total Values
let image = widget.addImage(canvImage); let totalValues;
image.centerAlignImage() if (v.unitOfMeasure !== 'MB') {
totalValues = `${(showRemainingContingent ? v.remaining : v.used)} ${v.unitOfMeasure} von ${v.total} ${v.unitOfMeasure}`
} else if (parseInt(v.total) < 1000) {
totalValues = `${(showRemainingContingent ? v.remaining : v.used)} MB von ${v.total} MB`
} else {
let GB = ((showRemainingContingent ? v.remaining : v.used) / 1024).toFixed(2)
let totalGB = (v.total / 1024).toFixed(2)
totalValues = `${GB} GB von ${totalGB} GB`
}
textStack = column.addStack()
textStack.layoutHorizontally()
textStack.addSpacer()
let diagramText = textStack.addText(totalValues)
diagramText.font = Font.mediumSystemFont(11)
diagramText.lineLimit = 1
diagramText.centerAlignText()
diagramText.textColor = textColor
textStack.addSpacer()
nameStack = column.addStack()
nameStack.layoutHorizontally()
nameStack.addSpacer()
let diagramName = nameStack.addText(v.name.replace('Inland & EU', '').trim())
diagramName.font = Font.systemFont(10)
diagramName.lineLimit = 1
diagramName.centerAlignText()
diagramName.textColor = textColor
nameStack.addSpacer()
})
widget.addSpacer() 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 = textColor
// Remaining Days // Remaining Days
if (data.endDate) { if (data.endDate) {
widget.addSpacer(5) widget.addSpacer(5)
let remainingDays = getTimeRemaining(data.endDate).days + 2 let remainingDays = getTimeRemaining(data.endDate).days + 2
if(data.billDate && showRemainingDaysAsProgressbar) {
const startDate = new Date(data.billDate)
const endDate = new Date(data.endDate)
const total = (endDate - startDate) / (1000 * 60 * 60 * 24)
const progressBarStack = widget.addStack()
progressBarStack.layoutHorizontally()
progressBarStack.addSpacer()
const progressBar = progressBarStack.addImage(creatProgress(total, total - remainingDays))
progressBarStack.addSpacer()
widget.addSpacer(4)
}
let remainingDaysText = widget.addText(`${remainingDays} Tage verbleibend`) let remainingDaysText = widget.addText(`${remainingDays} Tage verbleibend`)
remainingDaysText.font = Font.mediumSystemFont(8) remainingDaysText.font = Font.systemFont(8)
remainingDaysText.centerAlignText() remainingDaysText.centerAlignText()
remainingDaysText.textColor = textColor remainingDaysText.textColor = textColor
} }
@ -383,7 +511,12 @@ if (data !== undefined) {
} }
if (!config.runsInWidget) { if (!config.runsInWidget) {
await widget.presentSmall() switch (config.widgetFamily) {
case 'small': await widget.presentSmall(); break;
case 'medium': await widget.presentMedium(); break;
case 'large': await widget.presentLarge(); break;
}
} else { } else {
// Tell the system to show the widget. // Tell the system to show the widget.
Script.setWidget(widget) Script.setWidget(widget)