Last active
May 17, 2021 22:02
-
-
Save wickenico/433d5bbbc1c77b01e4e2fea0cc8f5335 to your computer and use it in GitHub Desktop.
A Scriptable widget that shows a crypto currency course in fiat currency based on coinbase
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
/* -------------------------------------------------------------- | |
Script: crypto-ticker-widget | |
Author: Nico Wickersheim | |
Version: 2.0.0 | |
Description: | |
Displays the current course of a cryptoin any fiat currency based on | |
the data of coinbase API. | |
Changelog: | |
1.0.0: Initialization | |
2.0.0: Fallback to Bitfinex, Error Catching, Design updates | |
2.0.1: Special Handling for IOTA/MIOTA | |
-------------------------------------------------------------- */ | |
let params = null; | |
// Parameter takeover from input | |
if (args.widgetParameter == null) { | |
params = ["DOGE", "EUR", "300"]; // Default input without parameters | |
} else { | |
params = args.widgetParameter.split(",") | |
console.log(params) | |
} | |
if (params[2] == null) { | |
params[2] = 1; | |
} | |
// Fetch Coinbase API json object | |
const url = 'https://api.coinbase.com/v2/prices/' + params[0] + '-' + params[1] + '/spot' | |
const req = new Request(url) | |
const res = await req.loadJSON() | |
let base = ""; | |
let currency = "" | |
let amount = ""; | |
let marketName = ""; | |
let USDamount = ""; | |
let coinbaseReqFailed = JSON.stringify(res).toLowerCase().includes("errors") | |
// Check if the api response contains an error message | |
if (coinbaseReqFailed == false) { | |
base = res.data.base; | |
currency = res.data.currency; | |
amount = res.data.amount; | |
marketName = "Coinbase"; | |
// Fetch Coinbase API json object as USD for comapre with latest course (only available in USD) | |
const USDurl = 'https://api.coinbase.com/v2/prices/' + params[0] + '-USD/spot' | |
const USDreq = new Request(USDurl) | |
const USDres = await USDreq.loadJSON() | |
if (coinbaseReqFailed == false || marketName != "") { | |
USDamount = USDres.data.amount; | |
USDamount = parseFloat(USDamount).toFixed(2) | |
} else { | |
base = params[0] | |
currency = params[1] | |
amount = res.errors[0].message; | |
} | |
} | |
// Second try with anoher coinbase api | |
if (coinbaseReqFailed == true) { | |
const url2 = 'https://api.coinbase.com/v2/exchange-rates?currency=' + params[0] | |
const req2 = new Request(url2) | |
const res2 = await req2.loadJSON() | |
coinbaseReqFailed = JSON.stringify(res2).toLowerCase().includes("errors") | |
if (coinbaseReqFailed == false) { | |
base = res2.data.currency | |
currency = params[1].toUpperCase() | |
amount = res2.data.rates[params[1]] | |
marketName = "Coinbase"; | |
// Get value in USD for selecting ticker symbol later | |
USDamount = res2.data.rates.USD; | |
} else { | |
base = params[0] | |
currency = params[1] | |
amount = res.errors[0].message; | |
} | |
} | |
// Fallback to Bitfinex if Coinbase Req failed | |
if (coinbaseReqFailed == true) { | |
const bitfinexUrl = 'https://api-pub.bitfinex.com/v2/tickers?symbols=t' + params[0] + params[1] | |
const bitfinexReq = new Request(bitfinexUrl) | |
const bitfinexRes = await bitfinexReq.loadJSON() | |
if (JSON.stringify(bitfinexRes) != "[]") { | |
amount = (bitfinexRes[Object.keys(bitfinexRes)[0]][9]).toString() | |
marketName = "Bitfinex" | |
} | |
} | |
if (params[0] == "IOT") { | |
base = "MIOTA"; | |
} | |
// Image fetching | |
let img = {}; | |
let i = {}; | |
// Fetch belonging image to crypto symbol | |
if (coinbaseReqFailed == false || marketName != "") { | |
i = new Request('https://cdn.jsdelivr.net/gh/atomiclabs/cryptocurrency-icons@9ab8d6934b83a4aa8ae5e8711609a70ca0ab1b2b/128/white/' + base.toLowerCase() + '.png') | |
} | |
try { | |
img = await i.loadImage() | |
} catch (e) { | |
// Fetch default independent image | |
i = new Request('https://image.flaticon.com/icons/png/512/107/107587.png') | |
img = await i.loadImage() | |
} | |
// Fetch further information to crpyto from Coinpaprika | |
let name = ""; | |
let rank = ""; | |
if (coinbaseReqFailed == false || marketName != "") { | |
const nameUrl = 'https://api.coinpaprika.com/v1/search?q=' + base.toLowerCase() + '&c=currencies&limit=1' | |
const nameReq = new Request(nameUrl) | |
const resName = await nameReq.loadJSON() | |
try { | |
name = resName.currencies[0].name; | |
rank = resName.currencies[0].rank; | |
} catch (e) { | |
name = "Not found"; | |
rank = 0; | |
} | |
} | |
let upticker = SFSymbol.named("chevron.up"); | |
let downticker = SFSymbol.named("chevron.down"); | |
let latest = ""; | |
let resLatest = ""; | |
if ((coinbaseReqFailed == false || marketName != "") && name != "Not found") { | |
let replaceName = name.replaceAll(" ", "-"); | |
const latestUrl = 'https://api.coinpaprika.com/v1/coins/' + base.toLowerCase() + '-' + replaceName.toLowerCase() + '/ohlcv/latest/' | |
const latestReq = new Request(latestUrl) | |
resLatest = await latestReq.loadJSON() | |
if (coinbaseReqFailed == false && JSON.stringify(resLatest) != "[]" || marketName != "") { | |
latest = resLatest[0].close; | |
latest = parseFloat(latest).toFixed(2); | |
} | |
} | |
let widget = createWidget(base, amount, currency, img, name, rank) | |
if (config.runsInWidget) { | |
// create and show widget | |
Script.setWidget(widget) | |
Script.complete() | |
} | |
else { | |
widget.presentSmall() | |
} | |
function createWidget(base, amount, currency, img, name, rank) { | |
let w = new ListWidget() | |
w.backgroundColor = new Color("#1A1A1A") | |
// Place image on the left top | |
let imageStack = w.addStack(); | |
imageStack.setPadding(8, 25, 0, 10); | |
imageStack.layoutHorizontally(); | |
imageStack.centerAlignContent(); | |
let image = imageStack.addImage(img) | |
image.imageSize = new Size(45, 45) | |
image.centerAlignImage() | |
imageStack.addSpacer(12); | |
let imageTextStack = imageStack.addStack(); | |
imageTextStack.layoutVertically(); | |
imageTextStack.addSpacer(0); | |
// Symbol of crypto token | |
let baseStack = imageTextStack.addStack() | |
baseStack.layoutHorizontally(); | |
if (marketName != "") { | |
let baseText = baseStack.addText(base) | |
baseText.textColor = Color.white() | |
if (baseText.length < 5) { | |
baseText.font = Font.systemFont(18) | |
} else { | |
baseText.font = Font.systemFont(14) | |
} | |
} | |
let tickerStack = baseStack.addStack(); | |
tickerStack.layoutHorizontally(); | |
// Stack for ticker image: if course yesterday is lower than today show red ticker | |
// if course yesterday | |
if (JSON.stringify(resLatest).toLowerCase().includes("errors") == false && JSON.stringify(resLatest) != "[]" && | |
coinbaseReqFailed == false || marketName != "") { | |
let ticker = null; | |
if (USDamount < latest) { | |
ticker = tickerStack.addImage(downticker.image); | |
ticker.tintColor = Color.red(); | |
} else { | |
ticker = tickerStack.addImage(upticker.image); | |
ticker.tintColor = Color.green(); | |
} | |
ticker.imageSize = new Size(12, 12) | |
} | |
// Rank of crypto token | |
let rankText = ""; | |
if (coinbaseReqFailed == false || marketName != "") { | |
rankText = imageTextStack.addText("Rank: " + rank) | |
} | |
rankText.textColor = Color.white() | |
rankText.font = Font.systemFont(12) | |
let marketText = imageTextStack.addText(marketName) | |
marketText.textColor = Color.gray() | |
marketText.font = Font.mediumSystemFont(9) | |
w.addSpacer(8) | |
// Full name of crypto token | |
let staticText = w.addText(name) | |
staticText.textColor = Color.white() | |
staticText.font = Font.systemFont(13) | |
staticText.centerAlignText() | |
if (params[2] != 1) { | |
w.addSpacer(2) | |
let specialAmount = w.addText(params[2] + " " + base + " =") | |
specialAmount.textColor = Color.gray() | |
specialAmount.font = Font.mediumSystemFont(9) | |
specialAmount.centerAlignText() | |
} | |
w.addSpacer(8) | |
// Round amount to 2 decimal positions | |
let amountTxt = ""; | |
if (coinbaseReqFailed == false || marketName != "") { | |
// Cut numbers over 10 Million and show just with ending 'M' | |
amount = (amount / 100) * (params[2] * 100) | |
if (parseFloat(amount) >= 10000000) { | |
amount = (parseFloat(amount) / 1000000).toFixed(2).replace(/\.0$/, ''); | |
amount += "M"; | |
} else if (parseFloat(amount) <= 0.01) { | |
amount = parseFloat(amount).toFixed(5) | |
} else { | |
amount = parseFloat(amount).toFixed(2); | |
} | |
amountTxt = w.addText(amount + ' ' + currency) | |
amountTxt.textColor = Color.orange() | |
} else { | |
amountTxt = w.addText(amount); // Write error message in case as amount | |
amountTxt.textColor = Color.red() | |
} | |
amountTxt.centerAlignText() | |
amountTxt.font = Font.systemFont(16) | |
w.addSpacer(8) | |
// Bottom date text | |
let currentDate = new Date(); | |
let lastDate = w.addDate(currentDate); | |
lastDate.textColor = Color.gray() | |
lastDate.font = Font.mediumSystemFont(10) | |
lastDate.centerAlignText(); | |
w.setPadding(0, 0, 0, 0) | |
return w | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment