Created
October 2, 2020 14:42
-
-
Save marco79cgn/509ad40296cc5b205d15283b23ed9d38 to your computer and use it in GitHub Desktop.
A Scriptable widget that shows what's new on Spotify
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
// insert your Spotify client id and secret here | |
const clientId = "xxx" | |
const clientSecret = "xxx" | |
// the Spotify country ISO code | |
const spotifyCountry = "DE" | |
let widget = await createWidget() | |
Script.setWidget(widget) | |
Script.complete() | |
widget.presentSmall() | |
async function createWidget() { | |
let widget = new ListWidget() | |
let spotifyIcon = await getImage("spotify-icon.png") | |
widget.backgroundColor = new Color("1e2040") | |
let newReleases = await searchNewReleasesAtSpotify() | |
const lastFridayDate = getLastFridayOf(new Date()) | |
const filteredItems = newReleases.albums.items.filter(x => | |
x.release_date === lastFridayDate | |
); | |
let randomNumber = getRandomNumber(1, filteredItems.length) - 1 | |
widget.setPadding(20, 12, 8, 8) | |
// cover art | |
const coverUrl = filteredItems[randomNumber].images[0].url | |
let coverImage = await loadImage(coverUrl) | |
let row = widget.addStack() | |
let stack = row.addStack() | |
stack.layoutHorizontally() | |
stack.size = new Size(105, 105) | |
let cover = stack.addImage(coverImage) | |
cover.cornerRadius = 6 | |
cover.borderColor = new Color("#1DB954") | |
cover.borderWidth = 3 | |
stack.addSpacer(6) | |
let stack2 = row.addStack() | |
stack2.layoutVertically() | |
stack2.size = new Size(30, 70) | |
let spotifyIconImage = stack2.addImage(spotifyIcon) | |
stack2.addSpacer(4) | |
let newIcon = await getImage("new-icon.png") | |
let newOnSpotifyIcon = stack2.addImage(newIcon) | |
widget.url = filteredItems[randomNumber].uri | |
// add title and artist | |
let cleanTitle = filteredItems[randomNumber].name.split(" (")[0] | |
cleanTitle = cleanTitle.split(" - ")[0] | |
const artist = filteredItems[randomNumber].artists[0].name | |
let titleTxt = widget.addText(cleanTitle) | |
titleTxt.font = Font.semiboldSystemFont(11) | |
titleTxt.textColor = Color.white() | |
titleTxt.lineLimit = 1 | |
widget.addSpacer(2) | |
let artistTxt = widget.addText(artist) | |
artistTxt.font = Font.boldSystemFont(11) | |
artistTxt.textColor = new Color("#1DB954") | |
artistTxt.lineLimit = 1 | |
widget.addSpacer() | |
return widget | |
} | |
// get images from local filestore or download them once | |
async function getImage(image) { | |
let fm = FileManager.local() | |
let dir = fm.documentsDirectory() | |
let path = fm.joinPath(dir, image) | |
if (fm.fileExists(path)) { | |
return fm.readImage(path) | |
} else { | |
// download once | |
let imageUrl | |
switch (image) { | |
case 'spotify-icon.png': | |
imageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Spotify_logo_without_text.svg/240px-Spotify_logo_without_text.svg.png" | |
break | |
case 'new-icon.png': | |
imageUrl = "https://cdn.iconscout.com/icon/free/png-128/24547.png" | |
break | |
default: | |
console.log(`Sorry, couldn't find ${image}.`); | |
} | |
let iconImage = await loadImage(imageUrl) | |
fm.writeImage(path, iconImage) | |
return iconImage | |
} | |
} | |
// helper function to download an image from a given url | |
async function loadImage(imgUrl) { | |
const req = new Request(imgUrl) | |
return await req.loadImage() | |
} | |
// search for the cover art on Spotify | |
async function searchNewReleasesAtSpotify() { | |
let searchToken = await getCachedSpotifyToken(false) | |
let searchUrl = "https://api.spotify.com/v1/browse/new-releases?" + "limit=20&offset=0&country=" + spotifyCountry | |
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 | |
} | |
// 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) { | |
return contents.toRawString() | |
} else { | |
let token = await getSpotifySearchToken() | |
fm.writeString(path, token) | |
return token | |
} | |
} | |
// gets 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 | |
} | |
// random number, min and max included | |
function getRandomNumber(min, max) { | |
return Math.floor(Math.random() * (max - min + 1) + min) | |
} | |
// returns the date of the latest Spotify music friday | |
function getLastFridayOf(date) { | |
if (date.toDateString().startsWith("Fri")) { | |
return date.toISOString().split("T")[0] | |
} else { | |
const t = date.getDate() + (6 - date.getDay() - 1) - (date.getDay() == 6 ? 0 : 7); | |
const lastFriday = new Date(); | |
lastFriday.setDate(t); | |
return lastFriday.toISOString().split("T")[0] | |
} | |
} |
Hey, that looks really good - great work!
Unfortunately I don't have a Spotify account, only Amazon Music. Is it possible to rewrite the script for Amazon Music? Unfortunately I am not a programmer, but I try to work my way in a little bit...
Thanks for the feedback!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Intro
iOS 14 Custom Widget made with the help of the Scriptable app. It shuffles through new Spotify releases ("New Music Friday") by using the Spotify Search API. Upon tapping on the widget it opens the item in Spotify.
Requirements
Join the beta here
Please create a client to get your client id and client_secret credentials. They are needed for searching new releases.
Insert them at the top of the script.
Thanks
A big Thank you to @simonbs for making great apps like Scriptable, DataJar or Jayson.