Skip to content

Instantly share code, notes, and snippets.

@Uvacoder
Forked from 29SimonB/spotify-player-widget.js
Created June 16, 2024 22:32
Show Gist options
  • Select an option

  • Save Uvacoder/e50c3018fcc99affbb9554b109a9bdb6 to your computer and use it in GitHub Desktop.

Select an option

Save Uvacoder/e50c3018fcc99affbb9554b109a9bdb6 to your computer and use it in GitHub Desktop.
A Scriptable iOS widget that shows what‘s playing on Spotify
//Source: Spotify Web API
let spotifyCredentials
if (!config.runsInWidget) {
switch (config.widgetFamily) {
case 'small': await widget.presentSmall(); break;
case 'medium': await widget.presentMedium(); break;
case 'large': await widget.presentLarge(); break;
}
} else {
if(config.widgetFamily == "small") {
let widget = await createSmallWidget()
Script.setWidget(widget)
}
if(config.widgetFamily == "medium") {
let widget = await createMediumWidget()
Script.setWidget(widget)
}
}
Script.complete()
async function createSmallWidget() {
let list = new ListWidget()
list.backgroundColor = new Color("#191414")
let spotifyIcon = await getImage("spotify-icon.png")
let pauseIcon = await getImage("pause-icon.png")
let playIcon = await getImage("play-icon.png")
let squareIcon = await getImage("square-icon.png")
// load spotify credentials from iCloud Drive
spotifyCredentials = await loadSpotifyCredentials()
if(spotifyCredentials != null) {
list.url = "spotify://"
let nowPlaying = await loadNowPlaying()
if(nowPlaying != null) {
list.setPadding(20,12, 8, 8)
let cleanTitle = nowPlaying.item.name.split(" (")[0]
cleanTitle = cleanTitle.split(" - ")[0]
const artist = nowPlaying.item.artists[0].name
// cover art
const coverUrl = nowPlaying.item.album.images[0].url
let coverImage = await loadImage(coverUrl)
let stack = list.addStack()
stack.layoutHorizontally()
stack.size = new Size(130,105)
let cover = stack.addImage(coverImage)
cover.cornerRadius = 10
cover.borderColor = new Color("#1DB954")
cover.borderWidth = 3
stack.addSpacer(10)
let stack1 = stack.addStack()
stack1.size = new Size(25,100)
stack1.layoutVertically()
let logoStack = stack1.addStack()
logoStack.size = new Size(25,25)
let spotifyIconImage = logoStack.addImage(spotifyIcon)
stack1.addSpacer(55)
let playerStack = stack1.addStack()
playerStack.size = new Size(25,15)
let pauseIconImage = playerStack.addImage(pauseIcon)
stack1.addSpacer(5)
// add title and artist
let titleTxt = list.addText(cleanTitle)
titleTxt.font = Font.semiboldSystemFont(11)
titleTxt.textColor = new Color("#1DB954")
titleTxt.lineLimit = 1
list.addSpacer(2)
let artistTxt = list.addText(artist)
artistTxt.font = Font.boldSystemFont(11)
artistTxt.textColor = new Color("#9e9e9e")
artistTxt.lineLimit = 1
list.addSpacer(10)
} else {
// Spotify playback stopped
list.setPadding(20,12, 8, 8)
// cover art
let stack = list.addStack()
stack.layoutHorizontally()
stack.size = new Size(130,105)
let cover = stack.addImage(squareIcon)
cover.cornerRadius = 10
cover.borderColor = new Color("#1DB954")
cover.borderWidth = 3
stack.addSpacer(10)
//add spotify icon
let stack1 = stack.addStack()
stack1.size = new Size(25,100)
stack1.layoutVertically()
let spotifyIconImage = stack1.addImage(spotifyIcon)
stack1.addSpacer(55)
let stack2 = stack1.addStack()
stack2.size = new Size(25,15)
let playIconImage = stack2.addImage(playIcon)
stack1.addSpacer(5)
// add title and artist
let titleTxt = list.addText("Spotify Player")
titleTxt.font = Font.semiboldSystemFont(11)
titleTxt.textColor = new Color("#1DB954")
titleTxt.lineLimit = 1
list.addSpacer(2)
let artistTxt = list.addText("Click to open")
artistTxt.font = Font.boldSystemFont(11)
artistTxt.textColor = new Color("#9e9e9e")
artistTxt.lineLimit = 1
list.addSpacer(10)
}
} else {
// no credentials found
let errorStack = list.addStack()
errorStack.size = new Size(130,130)
errorStack.layoutVertically()
let logoStack = errorStack.addStack()
logoStack.size = new Size(130,30)
logoStack.addSpacer()
let spotifyImage = logoStack.addImage(spotifyIcon)
spotifyImage.imageSize = new Size(25,25)
spotifyImage.rightAlignImage()
errorStack.addSpacer()
let textStack = errorStack.addStack()
textStack.size = new Size(130,80)
let ts = textStack.addText("Couldn't find your spotify credentials in iCloud Drive. \n\nPlease tap me for setup instructions.")
ts.textColor = Color.white()
ts.font = Font.boldSystemFont(11)
errorStack.addSpacer()
console.log("Could not find Spotify credentials!")
list.url = "https://gist.github.com/SimonBoer/a24b6a16aee17b1279fbb8dc6e436f95#gistcomment-3593498"
}
return list
}
async function createMediumWidget() {
let list = new ListWidget()
list.backgroundColor = new Color("#191414")
let spotifyIcon = await getImage("spotify-icon.png")
let pauseIcon = await getImage("pause-icon.png")
let playIcon = await getImage("play-icon.png")
let squareIcon = await getImage("square-icon.png")
let shuffleIcon = await getImage("shuffle-icon.png")
let repeatIcon = await getImage("repeat-icon.png")
// load spotify credentials from iCloud Drive
spotifyCredentials = await loadSpotifyCredentials()
if(spotifyCredentials != null) {
list.url = "spotify://"
let nowPlaying = await loadNowPlaying()
if(nowPlaying != null) {
list.setPadding(20,10,5,5)
let cleanTitle = nowPlaying.item.name.split(" (")[0]
cleanTitle = cleanTitle.split(" - ")[0]
const artist = nowPlaying.item.artists[0].name
// cover art
const coverUrl = nowPlaying.item.album.images[0].url
let coverImage = await loadImage(coverUrl)
let row = list.addStack()
row.size = new Size(310,145)
let cover = row.addImage(coverImage)
cover.cornerRadius = 10
cover.borderColor = new Color("#1DB954")
cover.borderWidth = 3
cover.imageSize = new Size(130,130)
row.addSpacer(10)
let stack = row.addStack()
stack.layoutHorizontally()
stack.size = new Size(170,130)
let stack1 = stack.addStack()
stack1.layoutVertically()
stack1.addSpacer()
// add title and artist
let titleTxt = stack1.addText(cleanTitle)
titleTxt.font = Font.semiboldSystemFont(11)
titleTxt.textColor = new Color("#1DB954")
titleTxt.lineLimit = 1
stack1.addSpacer(2)
let artistTxt = stack1.addText(artist)
artistTxt.font = Font.boldSystemFont(11)
artistTxt.textColor = new Color("#9e9e9e")
artistTxt.lineLimit = 1
stack1.addSpacer(55)
//add player icons
let playerStack = stack1.addStack()
playerStack.layoutHorizontally()
playerStack.addSpacer(10)
let shuffleIconImage = playerStack.addImage(shuffleIcon)
if(nowPlaying.shuffle_state == true) {
shuffleIconImage.imageOpacity = 1.0
} else {
shuffleIconImage.imageOpacity = 0.3
}
playerStack.addSpacer()
let pauseIconImage = playerStack.addImage(pauseIcon)
pauseIconImage.leftAlignImage()
playerStack.addSpacer()
let repeatIconImage = playerStack.addImage(repeatIcon)
if(nowPlaying.repeat_state === "off") {
repeatIconImage.imageOpacity = 0.3
} else {
repeatIconImage.imageOpacity = 1.0
}
stack1.addSpacer(20)
stack.addSpacer()
//add spotify icon
let stack2 = stack.addStack()
stack2.size = new Size(30,105)
stack2.layoutVertically()
let logoStack = stack2.addStack()
logoStack.size = new Size(25,25)
let spotifyIconImage = logoStack.addImage(spotifyIcon)
stack2.addSpacer()
} else {
// Spotify playback stopped
list.setPadding(20,10,5,5)
// cover art
let row = list.addStack()
row.size = new Size(310,145)
let cover = row.addImage(squareIcon)
cover.cornerRadius = 10
cover.borderColor = new Color("#1DB954")
cover.borderWidth = 3
cover.imageSize = new Size(130,130)
row.addSpacer(10)
let stack = row.addStack()
stack.layoutHorizontally()
stack.size = new Size(170,130)
let stack1 = stack.addStack()
stack1.layoutVertically()
stack1.addSpacer()
// add title and artist
let titleTxt = stack1.addText("Spotify Player")
titleTxt.font = Font.semiboldSystemFont(11)
titleTxt.textColor = new Color("#1DB954")
titleTxt.lineLimit = 1
stack1.addSpacer(2)
let artistTxt = stack1.addText("Click to open")
artistTxt.font = Font.boldSystemFont(11)
artistTxt.textColor = new Color("#9e9e9e")
artistTxt.lineLimit = 1
stack1.addSpacer(55)
//add player icons
let playerStack = stack1.addStack()
playerStack.layoutHorizontally()
playerStack.addSpacer(10)
let shuffleIconImage = playerStack.addImage(shuffleIcon)
playerStack.addSpacer()
let playIconImage = playerStack.addImage(playIcon)
playerStack.addSpacer()
let repeatIconImage = playerStack.addImage(repeatIcon)
stack1.addSpacer(20)
stack.addSpacer()
//add spotify icon
let stack2 = stack.addStack()
stack2.size = new Size(30,105)
stack2.layoutVertically()
let logoStack = stack2.addStack()
logoStack.size = new Size(25,25)
let spotifyIconImage = logoStack.addImage(spotifyIcon)
stack2.addSpacer()
}
} else {
// no credentials found
let errorStack = list.addStack()
errorStack.size = new Size(310,145)
errorStack.layoutHorizontally()
let textStack = errorStack.addStack()
textStack.size = new Size(275,130)
textStack.layoutVertically()
let ts = textStack.addText("Couldn't find your spotify credentials in iCloud Drive. \n\nPlease tap me for setup instructions.")
ts.textColor = Color.white()
ts.font = Font.boldSystemFont(11)
ts.leftAlignText()
let logoStack = errorStack.addStack()
logoStack.layoutVertically()
logoStack.size = new Size(25,130)
let spotifyImage = logoStack.addImage(spotifyIcon)
spotifyImage.imageSize = new Size(25,25)
console.log("Could not find Spotify credentials!")
list.url = "https://gist.github.com/SimonBoer/a24b6a16aee17b1279fbb8dc6e436f95#gistcomment-3593498"
}
return list
}
// get nowPlaying via Spotify Web API
async function loadNowPlaying() {
const req = new Request("https://api.spotify.com/v1/me/player")
req.headers = { "Authorization": "Bearer " + spotifyCredentials.accessToken, "Content-Type": "application/json"}
let npResult = await req.load()
if(req.response.statusCode == 401) {
// access token expired, trying to refresh
let success = await refreshSpotifyAccessToken()
if(success) {
return await loadNowPlaying()
} else {
return null
}
} else if (req.response.statusCode == 204) {
// no playback
return null
} else if (req.response.statusCode == 200) {
npResult = JSON.parse(npResult.toRawString())
}
return npResult
}
// load and validate spotify credentials from iCloud Drive
async function loadSpotifyCredentials() {
let fm = FileManager.iCloud()
let dir = fm.documentsDirectory()
let path = fm.joinPath(dir, "spotify-credentials.json")
let spotifyCredentials
if(fm.fileExists(path)) {
await fm.downloadFileFromiCloud(path)
let spotifyCredentialsFile = Data.fromFile(path)
spotifyCredentials = JSON.parse(spotifyCredentialsFile.toRawString())
if (isNotEmpty(spotifyCredentials.clientId)
&& isNotEmpty(spotifyCredentials.clientSecret)
&& isNotEmpty(spotifyCredentials.accessToken)
&& isNotEmpty(spotifyCredentials.refreshToken)) {
return spotifyCredentials
}
}
return null
}
// helper function to check not empty strings
function isNotEmpty(stringToCheck) {
if (stringToCheck != null && stringToCheck.length > 0) {
return true
} else {
return false
}
}
// The Spotify access token expired so we get a new one by using the refresh token (Authorization Flow)
async function refreshSpotifyAccessToken() {
if(spotifyCredentials != null) {
let req = new Request("https://accounts.spotify.com/api/token")
req.method = "POST"
req.headers = { "Content-Type": "application/x-www-form-urlencoded" }
req.body = "grant_type=refresh_token&refresh_token=" + spotifyCredentials.refreshToken + "&client_id=" + spotifyCredentials.clientId + "&client_secret=" + spotifyCredentials.clientSecret
let result = await req.loadJSON()
spotifyCredentials.accessToken = result.access_token
let fm = FileManager.iCloud()
let dir = fm.documentsDirectory()
let path = fm.joinPath(dir, "spotify-credentials.json")
fm.write(path, Data.fromString(JSON.stringify(spotifyCredentials)))
return true
}
return false
}
// get images from local filestore or download them once
async function getImage(image) {
let fm = FileManager.iCloud()
let dir = fm.documentsDirectory()
let path = fm.joinPath(dir, image)
if(fm.fileExists(path)) {
await fm.downloadFileFromiCloud(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 'shuffle-icon.png':
imageUrl = "https://www.iconsdb.com/icons/download/white/shuffle-128.png"
break
case 'repeat-icon.png':
imageUrl = "https://www.iconsdb.com/icons/download/white/repeat-128.png"
break
case 'pause-icon.png':
imageUrl = "https://www.iconsdb.com/icons/download/white/pause-128.png"
break
case 'play-icon.png':
imageUrl = "https://www.iconsdb.com/icons/download/white/play-6-128.png"
break
case 'square-icon.png':
imageUrl = "https://www.iconsdb.com/icons/download/white/square-256.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()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment