Skip to content

Instantly share code, notes, and snippets.

@andreasRedeker
Last active July 26, 2023 13:47
Show Gist options
  • Save andreasRedeker/7fa4f1ea16c4ab66b238bfb9e971e96c to your computer and use it in GitHub Desktop.
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)
// 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();
}
@TrafalgaT
Copy link

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 ?

@andreasRedeker
Copy link
Author

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

@TrafalgaT
Copy link

Vielen Dank für das Update !

@Jminter2K
Copy link

Jminter2K commented Mar 18, 2022

Hi. Bei mir bleibt es ewig auf "Daten werden geladen". MUSS ich die Koordinaten angeben? Oder woran könnte es haken? Danke.

@andreasRedeker
Copy link
Author

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).

@monza258
Copy link

monza258 commented Jun 1, 2022

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