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]
}
}
@marco79cgn
Copy link
Author

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

  • iOS 14
  • Scriptable version 1.5.1 (164 beta or higher)
    Join the beta here
  • Spotify Web Developer API
    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.

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