Skip to content

Instantly share code, notes, and snippets.

@marco79cgn
Created October 2, 2020 14:42
Show Gist options
  • Save marco79cgn/509ad40296cc5b205d15283b23ed9d38 to your computer and use it in GitHub Desktop.
Save marco79cgn/509ad40296cc5b205d15283b23ed9d38 to your computer and use it in GitHub Desktop.
A Scriptable widget that shows what's new on Spotify
// 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]
}
}
Copy link

ghost commented Oct 31, 2020

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