-
-
Save Uvacoder/e50c3018fcc99affbb9554b109a9bdb6 to your computer and use it in GitHub Desktop.
A Scriptable iOS widget that shows what‘s playing on Spotify
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
| //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