Last active
December 13, 2023 01:56
-
-
Save malesfth/d5fb2eb36aab4e726727c14ba32f9f8b to your computer and use it in GitHub Desktop.
iOS Scriptable Widget for Pi-hole
This file contains 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
// Parameters: | |
// {"url":"https://pihole","token": "x"} | |
// Optional key in parameters: "theme": system|light|dark | |
let piholeURL = "" // set the URL here for debug | |
let apiToken = "" // set the api token here for debug | |
let wTheme = "" // set the theme for debug | |
if (config.runsInWidget) { | |
const widgetParams = (args.widgetParameter != null ? JSON.parse(args.widgetParameter) : null) | |
if (widgetParams==null) { | |
throw new Error("Please long press the widget and add the parameters.") | |
} else if (!widgetParams.hasOwnProperty("url") || !widgetParams.hasOwnProperty("token")) { | |
throw new Error("Wrong parameters.") | |
} | |
piholeURL = widgetParams.url | |
apiToken = widgetParams.token | |
if (widgetParams.hasOwnProperty("theme")) { | |
wTheme = widgetParams.theme | |
} | |
} | |
let wBackground = new LinearGradient() | |
let wColor = new Color("#ffffff") | |
setTheme() | |
let piholeStats = await getStats() | |
let wSize = config.widgetFamily || "large" //set size of widget for debug | |
let widget = await createWidget() || null | |
if (!config.runsInWidget) { | |
if (wSize=="large") { await widget.presentLarge() } | |
else if (wSize=="medium") { await widget.presentMedium() } | |
else { await widget.presentSmall() } | |
} | |
Script.setWidget(widget) | |
Script.complete() | |
async function createWidget() { | |
let w = new ListWidget() | |
w.backgroundGradient = wBackground | |
w.addSpacer() | |
w.setPadding(5, 15, 0, (wSize=="small" ? 0 : 10)) | |
let state = (piholeStats!=null ? (piholeStats.status=="enabled" ? true : false) : null) | |
let icn = null | |
if (state==true) { | |
icn = SFSymbol.named((state ? "checkmark.shield.fill" : "xmark.shield.fill")) | |
} else { | |
icn = SFSymbol.named("xmark.circle.fill") | |
state = false | |
} | |
let topStack = w.addStack() | |
topStack.layoutHorizontally() | |
topStack.setPadding(5, 0, 0, 10) | |
let content = topStack.addImage(icn.image) | |
content.tintColor = (state ? Color.green() : Color.red()) | |
content.imageSize = new Size(16,16) | |
topStack.addSpacer(5) | |
content = topStack.addText("Pi-hole") | |
content.font = Font.blackSystemFont(16) | |
content.textColor = wColor | |
if (state==true && wSize != "small") { | |
topStack.addSpacer() | |
topStack.addText(" ") // same line with distance to title | |
addUpdateItem(topStack, (piholeStats.core_current==piholeStats.core_latest ? true : false), "Pi" + ((wSize=="small") ? "" : "-hole")) | |
topStack.addText(" ") | |
addUpdateItem(topStack, (piholeStats.web_current==piholeStats.web_latest ? true : false), "WebUI") | |
topStack.addText(" ") | |
addUpdateItem(topStack, (piholeStats.FTL_current==piholeStats.FTL_latest ? true : false), "FTL") | |
} | |
w.addSpacer(8) | |
if (piholeStats==null) { | |
content = w.addText("No connection") | |
content.font = Font.thinSystemFont(14) | |
content.textColor = wColor | |
w.addSpacer() | |
return w | |
} | |
w.url = piholeURL + "/admin/" | |
addItem(w, "Total Queries", piholeStats.dns_queries_all_types) | |
layoutStack = w.addStack() | |
layoutStack.setPadding(5, 0, 0, 10) | |
layoutStack.centerAlignContent() | |
addItem(w, "Queries Blocked", piholeStats.ads_blocked_today) | |
layoutStack = w.addStack() | |
layoutStack.setPadding(5, 0, 0, 10) | |
layoutStack.centerAlignContent() | |
addItem(w, "Percent Blocked", Number.parseFloat(piholeStats.ads_percentage_today).toFixed(1).replace(".", ",") + "%") | |
layoutStack = w.addStack() | |
layoutStack.setPadding(5, 0, 0, 10) | |
layoutStack.centerAlignContent() | |
addItem(w, "Domains on Blocklist", piholeStats.domains_being_blocked) | |
if (wSize=="large") { | |
addItem(w, "Unique Domains", piholeStats.unique_domains) | |
layoutStack = w.addStack() | |
layoutStack.setPadding(5, 0, 0, 10) | |
addItem(w, "Cached Queries", piholeStats.queries_cached) | |
layoutStack = w.addStack() | |
layoutStack.setPadding(5, 0, 0, 10) | |
addItem(w, "Queries Forwarded", piholeStats.queries_forwarded) | |
layoutStack = w.addStack() | |
layoutStack.setPadding(5, 0, 0, 10) | |
addItem(w, "Clients Seen / Unique", piholeStats.clients_ever_seen + " / " + piholeStats.unique_clients) | |
} | |
w.addSpacer() | |
return w | |
} | |
function addItem(w, strHeadline, strData) { | |
let fontSizeHeadline = 12 | |
let fontSizeString = 9 | |
switch (wSize) { | |
case "large": | |
fontSizeHeadline = 18 | |
fontSizeString = 14 | |
break; | |
case "medium": | |
fontSizeHeadline = 14 | |
fontSizeString = 11 | |
break; | |
} | |
let layoutStack = w.addStack() | |
layoutStack.setPadding(3, 0, 0, 10) | |
layoutStack.centerAlignContent() | |
content = layoutStack.addText(strHeadline) | |
content.font = Font.mediumSystemFont(fontSizeHeadline) | |
content.textColor = wColor | |
layoutStack.addSpacer() | |
content = layoutStack.addText(strData.toString()) | |
content.font = Font.mediumSystemFont(fontSizeString) | |
content.textColor = wColor | |
} | |
function addUpdateItem(stack, status, text) { | |
let icn = SFSymbol.named((status ? "checkmark.circle.fill" : "exclamationmark.triangle.fill")) | |
let content = stack.addImage(icn.image) | |
content.tintColor = (status ? Color.green() : Color.red()) | |
content.imageSize = new Size(14,14) | |
content = stack.addText(((wSize!="small") ? " " : "" ) + text) | |
content.font = Font.semiboldMonospacedSystemFont(12) | |
content.textColor = wColor | |
} | |
function setTheme() { | |
if (wTheme=="system") { | |
if (Device.isUsingDarkAppearance()) { | |
wTheme = "dark" | |
} else { | |
wTheme = "light" | |
} | |
} | |
wBackground.locations = [0, 1] | |
if (wTheme=="dark") { | |
wBackground.colors = [ | |
new Color("#384d54"), | |
new Color("#384d54") | |
] | |
wColor = new Color("#ffffff") | |
} else { | |
wBackground.colors = [ | |
new Color("#ffffffe6"), //ffffffe6 | |
new Color("#ffffffe6") | |
] | |
wColor = new Color("#000000") | |
} | |
} | |
async function getStats() { | |
try { | |
let req = new Request(piholeURL + "/admin/api.php?versions&summary&auth="+apiToken) | |
let json = await req.loadJSON() | |
if (json.length === 0) { return null } | |
return json | |
} catch(err) { | |
console.error(err) | |
return null | |
} | |
} |
THX
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
With a .replace function.
Below is the modified script:
// Parameters:
// {"url":"https://pihole","token": "x"}
// Optional key in parameters: "theme": system|light|dark
let piholeURL = "http://192.xxx.x.x" // set the URL here for debug
let apiToken = "xxxxxxx" // set the api token here for debug
let wTheme = "" // set the theme for debug
if (config.runsInWidget) {
const widgetParams = (args.widgetParameter != null ? JSON.parse(args.widgetParameter) : null)
if (widgetParams==null) {
throw new Error("Please long press the widget and add the parameters.")
} else if (!widgetParams.hasOwnProperty("url")) {
throw new Error("Wrong parameters.")
}
}
let wBackground = new LinearGradient()
let wColor = new Color("#ffffff")
setTheme()
let piholeStats = await getStats()
let adminPage = await getAdminPage()
let isLatestVersion = await getUpdateStats(adminPage)
let wSize = config.widgetFamily || "large" //set size of widget for debug
let widget = await createWidget() || null
if (!config.runsInWidget) {
if (wSize=="large") { await widget.presentLarge() }
else if (wSize=="medium") { await widget.presentMedium() }
else { await widget.presentSmall() }
}
Script.setWidget(widget)
Script.complete()
async function createWidget() {
let w = new ListWidget()
w.backgroundGradient = wBackground
w.addSpacer()
w.setPadding(5, 15, 0, (wSize=="small" ? 0 : 10))
}
function addItem(w, strHeadline, strData) {
let fontSizeHeadline = 12
let fontSizeString = 9
switch (wSize) {
case "large":
fontSizeHeadline = 18
fontSizeString = 14
break;
case "medium":
fontSizeHeadline = 14
fontSizeString = 11
break;
}
}
function addUpdateItem(stack, status, text) {
let icn = SFSymbol.named((status ? "checkmark.circle.fill" : "exclamationmark.triangle.fill"))
let content = stack.addImage(icn.image)
content.tintColor = (status ? Color.green() : Color.red())
content.imageSize = new Size(14,14)
content = stack.addText(((wSize!="small") ? " " : "" ) + text)
content.font = Font.semiboldMonospacedSystemFont(12)
content.textColor = wColor
}
function setTheme() {
if (wTheme=="system") {
if (Device.isUsingDarkAppearance()) {
wTheme = "dark"
} else {
wTheme = "light"
}
}
wBackground.locations = [0, 1]
if (wTheme=="dark") {
wBackground.colors = [
new Color("#384d54"),
new Color("#384d54")
]
wColor = new Color("#ffffff")
} else {
wBackground.colors = [
new Color("#ffffffe6"), //ffffffe6
new Color("#ffffffe6")
]
wColor = new Color("#000000")
}
}
async function getUpdateStats (webPage) {
let latestVersions = [true, true, true]
try {
let view = new WebView()
await view.loadHTML(webPage);
let data = await view.evaluateJavaScript(`
let retoure = [true, true, true]
let a = Array.from(document.querySelectorAll('div.version-info div ul li'))
}
async function getStats() {
try {
let req = new Request(piholeURL + "/admin/api.php?summary&auth="+apiToken)
let json = await req.loadJSON()
if (json.length === 0) { json == null }
return json
} catch(err) {
console.error(err)
return null
}
}
async function getAdminPage() {
try {
let req = new Request(piholeURL + "/admin/")
let adminHTMLPage = await req.loadString()
return adminHTMLPage
} catch(err) {
console.log(err)
return null
}
}