Last active
July 26, 2023 13:47
-
-
Save andreasRedeker/7fa4f1ea16c4ab66b238bfb9e971e96c to your computer and use it in GitHub Desktop.
A Scriptable widget, that shows you the cheapest gas station near you (for germany only)
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
// Variables used by Scriptable. | |
// These must be at the very top of the file. Do not edit. | |
// icon-color: deep-blue; icon-glyph: gas-pump; | |
// share-sheet-inputs: plain-text; | |
// Script by Andreas Redeker <hello@andreasredeker.de> | |
// Get your own API key from https://creativecommons.tankerkoenig.de/ | |
const apiKey = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" | |
const radius = 3; | |
const sort = "price"; | |
// set to true to use google maps instead of apple maps | |
const useGoogleMaps = false; | |
let fuelType = "e5"; | |
// set latitude and longitude for a fixed location | |
// example: let latitude = 52.516; let longitude = 13.376; | |
let latitude; | |
let longitude; | |
const fuelTypes = { | |
e5: "Super E5", | |
e10: "Super E10", | |
diesel: "Diesel", | |
}; | |
const primaryColor = Color.blue(); | |
const carIcon = SFSymbol.named("car.fill"); | |
if (args.widgetParameter && paramsValid(args.widgetParameter)) { | |
fuelType = args.widgetParameter; | |
} | |
const reqUrl = (location) => | |
`https://creativecommons.tankerkoenig.de/json/list.php?lat=${location.latitude.toFixed( | |
3 | |
)}&lng=${location.longitude.toFixed( | |
3 | |
)}&rad=${radius}&sort=${sort}&type=${fuelType}&apikey=${apiKey}`; | |
const station = await getPriceData(); | |
let widget = await createWidget(); | |
if (!config.runsInWidget) { | |
await widget.presentSmall(); | |
} | |
Script.setWidget(widget); | |
Script.complete(); | |
async function createWidget(items) { | |
let widget = new ListWidget(); | |
// cache data for at least 5 minutes | |
widget.refreshAfterDate = new Date(Date.now() + 300000); | |
if (station) { | |
let stationName; | |
if (station.brand) { | |
stationName = widget.addText(capitalize(station.brand)); | |
} else { | |
stationName = widget.addText(capitalize(station.name)); | |
} | |
stationName.font = Font.boldSystemFont(16); | |
stationName.textColor = primaryColor; | |
stationName.minimumScaleFactor = 0.5; | |
let street = widget.addText(capitalize(station.street)); | |
street.font = Font.mediumSystemFont(10); | |
street.textColor = Color.gray(); | |
let place = widget.addText(capitalize(station.place)); | |
place.font = Font.mediumSystemFont(10); | |
place.textColor = Color.gray(); | |
widget.url = useGoogleMaps ? getGoogleMapsUrl(station) : getAppleMapsUrl(station); | |
widget.addSpacer(2); | |
const row = widget.addStack(); | |
car = row.addImage(carIcon.image); | |
car.tintColor = Color.gray(); | |
car.imageSize = new Size(12, 12); | |
row.addSpacer(4); | |
const isOpen = station.isOpen ? "geöffnet" : "geschlossen"; | |
let dist = row.addText(station?.dist + " km • " + isOpen); | |
dist.font = Font.mediumSystemFont(10); | |
dist.textColor = Color.gray(); | |
widget.addSpacer(); | |
const gasType = widget.addText(fuelTypes[fuelType]); | |
gasType.font = Font.mediumSystemFont(12); | |
gasType.textColor = Color.gray(); | |
const priceStack = widget.addStack(); | |
let price = priceStack.addText(station.price.toLocaleString().slice(0, -1)); | |
price.font = Font.boldSystemFont(32); | |
price.textColor = primaryColor; | |
let eur = priceStack.addText(" €"); | |
eur.font = Font.boldSystemFont(32); | |
eur.textColor = primaryColor; | |
} else if (apiKey === "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") { | |
car = widget.addImage(carIcon.image); | |
car.tintColor = primaryColor; | |
car.imageSize = new Size(32, 32); | |
car.centerAlignImage(); | |
widget.addSpacer(8); | |
const missingApiKey = widget.addText("Bitte Tankerkönig API Key einfügen"); | |
missingApiKey.font = Font.mediumSystemFont(12); | |
missingApiKey.textColor = Color.gray(); | |
missingApiKey.centerAlignText(); | |
} else { | |
car = widget.addImage(carIcon.image); | |
car.tintColor = primaryColor; | |
car.imageSize = new Size(32, 32); | |
car.centerAlignImage(); | |
widget.addSpacer(8); | |
const loading = widget.addText("Daten werden geladen"); | |
loading.font = Font.mediumSystemFont(12); | |
loading.textColor = Color.gray(); | |
loading.centerAlignText(); | |
} | |
return widget; | |
} | |
async function getPriceData() { | |
try { | |
const location = await getLocation(); | |
if (location) { | |
const data = await new Request(reqUrl(location)).loadJSON(); | |
if (data.ok) { | |
if (data.stations.length > 0) { | |
let openStations = data.stations.filter( | |
(s) => s.isOpen == true && s.price != null | |
); | |
if (openStations.length > 0) { | |
const station = openStations[0]; | |
return { | |
name: station.name, | |
brand: station.brand, | |
street: station.street + " " + station.houseNumber, | |
place: station.postCode + " " + station.place, | |
price: station.price, | |
dist: station.dist, | |
isOpen: station.isOpen, | |
lat: station.lat, | |
lng: station.lng, | |
}; | |
} else { | |
console.log(`no stations found in radius ${radius} with price`); | |
return null; | |
} | |
} else { | |
console.log(`no stations found in radius ${radius}`); | |
return null; | |
} | |
} else { | |
console.log("data not ok"); | |
return null; | |
} | |
} else { | |
console.log("no location found"); | |
return null; | |
} | |
} catch (e) { | |
console.log(e); | |
return null; | |
} | |
} | |
async function getLocation() { | |
if (latitude && longitude) { | |
return { latitude: latitude, longitude: longitude }; | |
} else { | |
try { | |
Location.setAccuracyToKilometer(); | |
return await Location.current(); | |
} catch (e) { | |
return null; | |
} | |
} | |
} | |
function paramsValid(input) { | |
return input in fuelTypes; | |
} | |
function capitalize(string) { | |
return string | |
.toLowerCase() | |
.replace(/\w\S*/g, (w) => w.replace(/^\w/, (c) => c.toUpperCase())); | |
} | |
function getGoogleMapsUrl(station) { | |
let destination = station.lat + "," + station.lng; | |
let url = `https://www.google.com/maps/dir/?api=1&destination=${destination}&travelmode=car`; | |
return url.toString(); | |
} | |
function getAppleMapsUrl(station) { | |
let destination = station.lat + ',' + station.lng | |
let url = `http://maps.apple.com/?q=${destination}` | |
return url.toString(); | |
} |
Das Script hat heute ein Update erhalten:
- Koordinaten können im Code für einen fixen Standort gesetzt werden (Zeile 18 & 19)
- Apple Maps ist nun der Standarddienst für die Route zur Tankstelle, im Code kann alternativ Google Maps aktiviert werden (Zeile 13)
- Fehler behoben, der auftrat wenn die von der API bereitgestellten Tankstelle keinen Preis (null) hatte
Vielen Dank für das Update !
Hi. Bei mir bleibt es ewig auf "Daten werden geladen". MUSS ich die Koordinaten angeben? Oder woran könnte es haken? Danke.
Hi, die Koordinaten sind optional, standardmäßig werden die anhand deines Standortes ermittelt.
Es könnte sein, dass in deiner Umgebung keine passende Tankstelle gefunden worden ist, versuche mal in Zeile 9 den Radius zu erhöhen (max. 25 km).
Hi @andreasRedeker,
Tolles Script von dir. Rein optisch hat mich damals dieses Script aus dem Netz angesprochen. Eventuell hast du Lust deins etwas anzupassen da in dem Script welches ich verwende auch der Durchschnittspreis errechnet wird und der Benzinpreis auch besser angezeigt wird. Würde mich freuen. ;-)
let callDataAmount = 40
let fm = FileManager.iCloud();
let p = await args.widgetParameter
let widgetSize = config.widgetFamily
if(p == 'local'){
fm = FileManager.local();
}
let dir = fm.documentsDirectory()
let historyFolderPath = fm.joinPath(dir, "FuelPriceNearestHistory")
let list = fm.listContents(historyFolderPath)
log("length: " + list.length)
let historyFilePath = fm.joinPath(historyFolderPath, "FuelPriceNearest" + (list.length - 1) + ".json")
let path = fm.joinPath(historyFolderPath, "FuelPriceNearest" + (list.length) + ".json")
let ic = true
let apiKey = 'XXXXXX-XXXXX-XXXX-XXXX-XXXXXXXX' // get API-Key from https://creativecommons.tankerkoenig.de/
let fixedLocation = false // set to true if you want a fixed location
let radius = 5 // radius in km, set it higher if the script throws an error, it's possible that there is no gas station near your location
const apiURL = (location, radius, apiKey) => `https://creativecommons.tankerkoenig.de/json/list.php?lat=${location.latitude.toFixed(3)}&lng=${location.longitude.toFixed(3)}&rad=${radius}&sort=dist&type=all&apikey=${apiKey}`
let station = await loadStation(apiKey, radius, fixedLocation)
log(dir)
let widget = await createWidget(station)
Script.setWidget(widget)
Script.complete()
function map(lat, lng, name) {
name = name.replace(" ", "%20")
name = name.replace(" ", "%20")
name = name.replace(" ", "%20")
name = name.replace(" ", "%20")
name = name.replace(" ", "%20")
var mapURL = "https://maps.apple.com/?t=h&z=12&ll=" + lat + "," + lng + "&q=" + name
return mapURL
}
function lineSep(){
//generate line separator
const context =new DrawContext()
let width = 93,h=0.00000000001
context.size=new Size(width, h)
context.opaque=false
context.respectScreenScale=true
const path = new Path()
path.move(new Point(18,h))
path.addLine(new Point(width,h))
context.addPath(path)
context.setStrokeColor(Color.blue())
context.strokePath()
return context.getImage()
}
async function getFromApi(){
let location
if (fixedLocation) {
location = myLocation
} else {
location = await Location.current()
}
const data = await new Request(apiURL(location, radius, apiKey)).loadJSON()
return data
}
function getFromFile(){
if (fm.fileExists(historyFilePath)){
data = JSON.parse(fm.readString(historyFilePath));
return data;
}
}
async function loadStation(apiKey, radius, fixedLocation) {
try{
data = await getFromApi();
}catch {
data = await getFromFile();
ic = false
}
log(ic)
log(data)
return data
}
function formatValue(value) {
let lastDigit = '⁹'
let price = value.toString().slice(0, -1)
return price + lastDigit
}
async function createWidget(data) {
var attr = data.stations[0]
log(attr.e10)
log("dataLength: " + data.stations.length)
data.stations.forEach(cheapest)
function cheapest(item, index) {
if ((item.e10 != null) && (item.e10 < attr.e10)){
attr = item
}
}
log(attr.e10)
log("address:\n" + attr.street + attr.houseNumber + attr.postCode + attr.place)
const widget = new ListWidget()
widget.url = await map(attr.lat, attr.lng, attr.name)
log(widget.url)
// header:
var header_stack = widget.addStack();
var symbol = SFSymbol.named('car.fill').image;
var symbol_image = header_stack.addImage(symbol);
symbol_image.imageSize = new Size(15, 15);
symbol_image.tintColor = Color.blue()
header_stack.addSpacer(3);
var title = header_stack.addText("Sprit Preise");
title.font = Font.boldRoundedSystemFont(13)
title.textColor = Color.blue()
console.log("title:\n" + title.text + "\n")
// line:
let lineImg = lineSep()
let line = widget.addImage(lineImg)
line.resizable=false
line.imageOpacity=1
widget.addSpacer(3)
// price:
var line2Stack = widget.addStack()
var line2 = line2Stack.addText(formatValue(attr.e10))
line2.font = Font.boldRoundedSystemFont(36)
var euroStack = line2Stack.addStack()
euroStack.layoutVertically()
euroStack.addSpacer(12)
var euro = euroStack.addText("€")
euro.font = Font.boldRoundedSystemFont(24)
if (fm.fileExists(historyFilePath) == false) {
fm.writeString(historyFilePath, JSON.stringify(data));
}
const dataPrev = JSON.parse(fm.readString(historyFilePath));
let currentPrice = Number(attr.e10)
var previousPriceAttr = dataPrev.stations[0]
dataPrev.stations.forEach(prevStation)
function prevStation(item, index) {
if ((item.e10 != null) && (String(item.lat + "_" + item.lng) == String(attr.lat + "_" + attr.lng))){
previousPriceAttr = item
}
}
let previousPrice = previousPriceAttr.e10
log(previousPrice)
var change = 0
let priceList = new Array()
var all = 0
var c = 0
list.forEach(myFunction)
function myFunction(item, index){
if (index < callDataAmount) {
let itemPath = fm.joinPath(historyFolderPath, "FuelPriceNearest" + (list.length - index - 1) + ".json")
const itemData = JSON.parse(fm.readString(itemPath))
if(itemData != null) {
itemData.stations.forEach(itemStation)
function itemStation(i, index) {
if ((i.e10 != null) && (String(i.lat + "_" + i.lng) == String(attr.lat + "_" + attr.lng))){
priceList[c] = i.e10
c = c + 1
}
}
}
}
}
priceList.forEach(allF)
function allF(item, index) {
all = all + Number(item)
}
log("all: " + all)
change = all / (priceList.length)
log("change: " + change)
let avrg = String(Math.round((change + Number.EPSILON) * 100) / 100)
let avrgC = avrg.replace(".","")
if (avrgC.length < 3) {
if (avrg.includes("1.")) {
avrg = avrg + "0"
if (avrgC.length == 1) {
avrg = avrg + "0"
}
}else if (avrg.includes("0.")) {
avrg = "0." + avrgC + "0"
}
}
log("avrg: " + avrg)
change = Number(avrg)
if (change != 0) {
if (change == currentPrice) {
line2.textColor = Color.orange()
euro.textColor = Color.orange()
}else if (change > currentPrice) {
line2.textColor = Color.green()
euro.textColor = Color.green()
}else if (change < currentPrice) {
line2.textColor = Color.red()
euro.textColor = Color.red()
}
}
if (list.length > callDataAmount) {
let amount = list.length - callDataAmount
list.forEach(deleteFunc)
function deleteFunc(item, index) {
if (index < amount) {
let itemPath = fm.joinPath(historyFolderPath, "FuelPriceNearest" + (amount - index - 1) + ".json")
// if (fm.fileExists(itemPath)) {
// fm.remove(itemPath)
// }
}
}
log(list.length)
}
if (currentPrice != previousPrice) {
fm.writeString(path, JSON.stringify(data))
}
var line3text = "Super E10"
if (avrg > 0) {
line3text = "Super E10 ⌀" + avrg
}
var line3 = widget.addText(line3text)
line3.font = Font.boldRoundedSystemFont(14)
line3.textColor = Color.blue()
widget.addSpacer(0)
let stationStack = widget.addStack()
stationStack.layoutHorizontally()
stationStack.setPadding(0, -4, 0, 0)
let stationOpen = stationStack.addText("|")
stationOpen.font = Font.boldRoundedSystemFont(35)
stationOpen.textColor = Color.red()
if (attr.isOpen == true) {
stationOpen.textColor = Color.green()
}
let nameStack = stationStack.addStack()
nameStack.layoutVertically()
nameStack.addSpacer(5)
log(attr.brand)
let stationName = nameStack.addText(attr.brand + " → " + attr.dist + " km")
stationName.font = Font.boldRoundedSystemFont(10)
let place = nameStack.addText(attr.place)
place.font = Font.boldRoundedSystemFont(10)
let street = nameStack.addText(attr.street)
street.font = Font.boldRoundedSystemFont(10)
return widget
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi, wäre es möglich anstatt Google Maps auch Apple Maps zu nutzen ? Wenn ja wie würde der Code angepasst werden müssen ?