-
-
Save gwthompson/0f06771d6c3146517e3b65ae95472233 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
// widget created by @marco79 (Twitter) user/marco79 (RoutineHub) u/marco79 (Reddit) | |
// insert your Spotify client id and secret here | |
const clientId = "xxxxxx" | |
const clientSecret = "xxxxxx" | |
const spotifyPlaylistName = "Retrowelle Playlist" | |
// insert your IFTTT key and webhook url | |
const iftttUrl = "https://maker.ifttt.com/trigger/add_track_to_spotify/with/key/" | |
const iftttKey = "xxxxxx" | |
const logoUrl = "https://www.retrowelle.com/wordpress/wp-content/uploads/2019/03/RETROWELLE-Kreis_WP-Icon.png" | |
let nowPlaying = await loadNowPlaying() | |
let widget = await createWidget(nowPlaying) | |
Script.setWidget(widget) | |
Script.complete() | |
widget.presentSmall() | |
async function createWidget(nowPlaying) { | |
let widget = new ListWidget() | |
let artistTitle = nowPlaying.title + ";" + nowPlaying.artist.name | |
// load last song from iCloud Drive | |
let fm = FileManager.iCloud() | |
let dir = fm.documentsDirectory() | |
let path = fm.joinPath(dir, "retrowelle-lastsong.txt") | |
let lastSong = Data.fromFile(path) | |
let coverPath = fm.joinPath(dir, "retrowelle-cover.jpg") | |
let uri = "" | |
let coverUrl = "" | |
let coverImage | |
if (lastSong == null || (lastSong != null && lastSong.toRawString().indexOf(artistTitle) == -1)) { | |
// new song | |
// Spotify search api query | |
let result = await searchCoverAtSpotify(nowPlaying.title, nowPlaying.artist.name, true) | |
if (gotResultFromSpotify(result)) { | |
let item = result.tracks.items[0] | |
coverUrl = item.album.images[1].url | |
uri = item.uri | |
coverImage = await loadImage(coverUrl) | |
fm.writeImage(coverPath, coverImage) | |
} else { | |
// query spotify again with just one simplified search string | |
result = await searchCoverAtSpotify(nowPlaying.title, nowPlaying.artist.name, false) | |
if (gotResultFromSpotify(result)) { | |
let item = result.tracks.items[0] | |
coverUrl = item.album.images[1].url | |
uri = item.uri | |
coverImage = await loadImage(coverUrl) | |
fm.writeImage(coverPath, coverImage) | |
} | |
} | |
} else { | |
// old song | |
let pos = lastSong.toRawString().lastIndexOf(';') + 1; | |
uri = lastSong.toRawString().substring(pos, lastSong.toRawString().length) | |
coverImage = fm.readImage(coverPath) | |
} | |
// set cover art background or fallback logo | |
if (coverImage != null) { | |
widget.backgroundImage = coverImage | |
} else { | |
coverImage = await loadImage(logoUrl) | |
widget.backgroundImage = coverImage | |
} | |
if (uri != null && uri.length > 0) { | |
widget.url = uri | |
} | |
// set gradient background with transparency | |
let startColor = new Color("#1c1c1c00") | |
let endColor = new Color("#1c1c1c92") | |
let gradient = new LinearGradient() | |
gradient.colors = [startColor, endColor] | |
gradient.locations = [0.0, 1] | |
widget.backgroundGradient = gradient | |
widget.backgroundColor = new Color("1c1c1c") | |
let updatedAt = new Date().toLocaleTimeString("de-DE", { timeZone: "CET", hour: '2-digit', minute: '2-digit' }) | |
let ts = widget.addText(`${updatedAt} Uhr`) | |
ts.textColor = Color.white() | |
ts.font = Font.boldSystemFont(10) | |
ts.leftAlignText() | |
widget.addSpacer(2) | |
// add title and artist | |
let titleTxt = widget.addText(nowPlaying.title) | |
titleTxt.font = Font.boldSystemFont(12) | |
titleTxt.textColor = Color.white() | |
widget.addSpacer(2) | |
let artistTxt = widget.addText(nowPlaying.artist.name) | |
artistTxt.font = Font.boldSystemFont(11) | |
artistTxt.textColor = Color.yellow() | |
widget.setPadding(8, 10, 12, 10) | |
// add track to Spotify playlist using IFTTT webhook | |
if (args.widgetParameter === "playlist" || config.runsInApp) { | |
if (lastSong != null) { | |
if (lastSong.toRawString().indexOf(artistTitle) == -1) { | |
addToSpotifyPlaylist(nowPlaying.title, nowPlaying.artist.name) | |
fm.writeString(path, artistTitle + ";" + uri) | |
} else { | |
console.log("Song is already part of the playlist") | |
} | |
} else { | |
await addToSpotifyPlaylist(nowPlaying.title, nowPlaying.artist.name) | |
fm.writeString(path, artistTitle + ";" + uri) | |
} | |
} | |
return widget | |
} | |
// helper function to load and parse a restful json api | |
async function loadNowPlaying() { | |
const req = new Request("https://api.laut.fm/station/retrowelle/last_songs") | |
const json = await req.loadJSON() | |
return json[0] | |
} | |
// helper function to download an image from a given url | |
async function loadImage(imgUrl) { | |
const req = new Request(imgUrl) | |
return await req.loadImage() | |
} | |
// obtains a spotify search token | |
async function getSpotifySearchToken() { | |
let url = "https://accounts.spotify.com/api/token"; | |
let req = new Request(url) | |
req.method = "POST" | |
req.body = "grant_type=client_credentials" | |
let authHeader = "Basic " + btoa(clientId + ":" + clientSecret) | |
req.headers = { "Authorization": authHeader, "Content-Type": "application/x-www-form-urlencoded" } | |
let token = await req.loadJSON() | |
return token.access_token | |
} | |
// search for the cover art on Spotify | |
async function searchCoverAtSpotify(title, artist, strict) { | |
let searchString | |
let searchToken = await getCachedSpotifyToken(false) | |
if (strict === true) { | |
searchString = encodeURIComponent("track:" + title + " artist:" + artist) | |
} else { | |
searchString = encodeURIComponent(artist + " " + title) | |
} | |
let searchUrl = "https://api.spotify.com/v1/search?q=" + searchString + "&type=track&market=DE&limit=1" | |
req = new Request(searchUrl) | |
req.headers = { "Authorization": "Bearer " + searchToken, "Content-Type": "application/json", "Accept": "application/json" } | |
let result = await req.loadJSON() | |
// check if token expired | |
if (req.response.statusCode == 401) { | |
searchToken = await getCachedSpotifyToken(true) | |
req.headers = { "Authorization": "Bearer " + searchToken, "Content-Type": "application/json", "Accept": "application/json" } | |
result = await req.loadJSON() | |
} | |
return result | |
} | |
// add track to Spotify playlist using IFTTT webhook | |
async function addToSpotifyPlaylist(title, artist) { | |
let cleanTitle = title.split(" (") | |
let req = new Request(iftttUrl + iftttKey) | |
req.method = "POST" | |
req.headers = { "Content-Type": "application/x-www-form-urlencoded" } | |
req.body = "value1=" + spotifyPlaylistName + "&value2=" + cleanTitle[0] + "&value3=" + artist | |
let result = await req.loadString() | |
} | |
// obtain spotify api search token - either cached or new | |
async function getCachedSpotifyToken(forceRefresh) { | |
// load json from iCloud Drive | |
let fm = FileManager.iCloud() | |
let dir = fm.documentsDirectory() | |
let path = fm.joinPath(dir, "spotify-token.txt") | |
let contents = Data.fromFile(path) | |
if (contents != null && contents.toRawString().length > 0 && !forceRefresh) { | |
console.log("previous token: " + contents.toRawString()) | |
return contents.toRawString() | |
} else { | |
console.log("Getting new token from Spotify.") | |
let token = await getSpotifySearchToken() | |
fm.writeString(path, token) | |
return token | |
} | |
} | |
// check whether spotify api search returned a result | |
function gotResultFromSpotify(result) { | |
if (result != null && result.tracks != null && result.tracks.items != null && result.tracks.items.length == 1) { | |
return true | |
} else { | |
return false | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment