Skip to content

Instantly share code, notes, and snippets.

@marco79cgn
Last active February 21, 2024 03:26
Show Gist options
  • Save marco79cgn/dbd9a2b837469d89a36fc5cf9c0436a6 to your computer and use it in GitHub Desktop.
Save marco79cgn/dbd9a2b837469d89a36fc5cf9c0436a6 to your computer and use it in GitHub Desktop.
A scriptable iOS widget which displays the program and recent songs of "radioeins vom rbb"
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: orange; icon-glyph: volume-up;
// name: radioeins-widget.js
// description: A scriptable widget which displays the program and recent songs of radioeins vom rbb
// author: Marco Dengel
// email: marco79cgn@gmail.com
// Insert your Spotify credentials here!
const clientId = "******"
const clientSecret = "******"
const today = dateToYMD(new Date())
let mediathekData;
try {
mediathekData = await new Request(
"http://rbb-cache-1.konsole-labs.com/radioeins/playlist/get/program-day.php?idStation=3&d=" + today
).loadJSON();
} catch (e) {
const errorWidget = createErrorWidget();
if (!config.runsInWidget) {
await errorWidget.presentLarge();
} else {
Script.setWidget(errorWidget);
}
Script.complete();
}
const widget = await createWidget();
widget.url = "https://dispatcher.rndfnk.com/rbb/radioeins/live/mp3/mid"
if (!config.runsInWidget) {
await widget.presentLarge();
} else {
Script.setWidget(widget);
}
Script.complete();
async function createWidget() {
let listWidget = new ListWidget();
listWidget.setPadding(20, 20, 0, 20)
listWidget.backgroundColor = new Color("#2b2b2b")
listWidget = await createHeaderImage(listWidget);
listWidget.addSpacer(1);
const schedule = await getSchedule()
const currentShow = listWidget.addStack()
const playNowImage = currentShow.addImage(await getImage('playnow.png'));
playNowImage.imageSize = new Size(46,46);
currentShow.addSpacer(8)
const currentShowInfo = currentShow.addStack()
currentShowInfo.layoutVertically()
currentShowInfo.addSpacer(5)
const nowLive = currentShowInfo.addText("Jetzt live: " + schedule.broadcast.time + " Uhr")
nowLive.textColor = Color.white()
nowLive.font = Font.boldMonospacedSystemFont(10);
const showTitle = currentShowInfo.addText(schedule.broadcast.title)
showTitle.textColor = Color.white()
showTitle.font = Font.boldMonospacedSystemFont(12);
const host = currentShowInfo.addText("mit " + schedule.broadcast.moderation[0].name)
host.textColor = Color.white()
host.font = Font.boldMonospacedSystemFont(11);
host.textOpacity = 0.8
listWidget.addSpacer(2)
const line2 = listWidget.addText("_________________________________________________________")
line2.font = Font.boldMonospacedSystemFont(8);
line2.textColor = Color.white()
line2.centerAlignText()
listWidget.addSpacer(2)
// Get the last three songs
let foundSongs = 0
for (let i = 0; i < mediathekData.length; i++) {
var currentItem = mediathekData[i]
console.log(currentItem)
if(currentItem.cl.toLowerCase() === 'music') {
listWidget.addSpacer(6)
foundSongs += 1
let airtime = currentItem.ts
let artist = currentItem.a
let title = currentItem.t
listWidget = await createPlaylistItem(listWidget, airtime, artist, title);
}
if(foundSongs > 2) {
break
}
}
// next show
const line = listWidget.addText("_________________________________________________________")
line.font = Font.boldMonospacedSystemFont(8);
line.textColor = Color.white()
line.centerAlignText()
listWidget.addSpacer(6)
const nextShow = listWidget.addText("Nächste Sendung: " + schedule.next.time + " Uhr")
nextShow.textColor = Color.white()
nextShow.font = Font.semiboldMonospacedSystemFont(11);
nextShow.centerAlignText()
const nextShowDetails = listWidget.addText(schedule.next.title + " mit " + schedule.next.moderation[0].name)
nextShowDetails.textColor = Color.white();
nextShowDetails.font = Font.semiboldMonospacedSystemFont(11)
nextShowDetails.centerAlignText()
const logo = listWidget.addImage(await getImage('radioeins-logo.png'))
logo.imageSize = new Size(80, 40)
logo.centerAlignImage()
return listWidget;
}
async function createPlaylistItem(listWidget, airtime, artist, title) {
let date = new Date(airtime * 1000);
let coverImage
let uri
let coverUrl = ""
// Spotify search api query
let result = await searchCoverAtSpotify(title, artist, true)
if (gotResultFromSpotify(result)) {
let item = result.tracks.items[0]
coverUrl = item.album.images[1].url
uri = item.uri
coverImage = await loadImage(coverUrl)
} else {
// query spotify again with just one simplified search string
result = await searchCoverAtSpotify(title, artist, false)
if (gotResultFromSpotify(result)) {
let item = result.tracks.items[0]
coverUrl = item.album.images[1].url
uri = item.uri
coverImage = await loadImage(coverUrl)
}
}
if(coverImage == null) {
coverImage = await getImage("radioeins-default-cover.png")
}
const playlistItem = listWidget.addStack();
const cover = playlistItem.addImage(coverImage);
cover.cornerRadius = 5;
cover.imageSize = new Size(48,48)
if(uri != null && uri.length > 0) {
playlistItem.url = uri
} else {
playlistItem.url = "spotify://"
}
playlistItem.addSpacer(6);
const songInfo = playlistItem.addStack();
songInfo.layoutVertically();
songInfo.addSpacer(2)
const songTitle = songInfo.addText(title);
songTitle.textColor = Color.white()
songTitle.font = Font.boldMonospacedSystemFont(12);
songInfo.addSpacer(2)
const songArtist = songInfo.addText(artist);
songArtist.textColor = new Color("#ff9f00")
songArtist.font = Font.semiboldMonospacedSystemFont(12)
songArtist.lineLimit = 1
songInfo.addSpacer(2)
if (date) {
const airDate = songInfo.addText(formatDate(date));
airDate.font = Font.semiboldMonospacedSystemFont(11);
airDate.textOpacity = 0.7;
airDate.textColor = Color.white()
}
return listWidget;
}
async function createHeaderImage(listWidget) {
const headerImage = listWidget.addImage(
await getImage('erwachsene-logo.png')
);
headerImage.imageSize = new Size(100, 16);
headerImage.rightAlignImage();
headerImage.applyFillingContentMode();
return listWidget;
}
function createErrorWidget() {
const errorWidget = new ListWidget();
const bgGradient = new LinearGradient();
bgGradient.locations = [0, 1];
bgGradient.colors = [new Color('#2D65AE'), new Color('#19274C')];
errorWidget.backgroundGradient = bgGradient;
const title = errorWidget.addText('radioeins vom rbb');
title.font = Font.headline();
title.centerAlignText();
title.textColor = Color.white()
errorWidget.addSpacer(10);
const errorText = errorWidget.addText(
'Es besteht momentan keine Verbindung zum Internet.'
);
errorText.font = Font.semiboldMonospacedSystemFont(16);
errorText.textColor = Color.orange();
errorText.centerAlignText()
return errorWidget;
}
async function loadImage(url) {
return await new Request(url).loadImage();
}
function formatDate(dateObject) {
return `${leadingZero(
dateObject.getHours()
)}:${leadingZero(dateObject.getMinutes())} Uhr`;
}
function leadingZero(input) {
return ('0' + input).slice(-2);
}
// random number, min and max included
function getRandomNumber(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min)
}
async function getImage(image) {
let fm = FileManager.iCloud()
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 'radioeins-logo.png':
imageUrl = "https://i.imgur.com/gs96xQ5.png"
break
case 'erwachsene-logo.png':
imageUrl = "https://i.imgur.com/vfnREHs.png"
break
case 'playnow.png':
imageUrl = "https://i.imgur.com/neKo5Cj.png"
break
case 'radioeins-default-cover.png':
imageUrl = "https://static.mytuner.mobi/media/tvos_radios/6pnVM8VR46.png"
break
default:
console.log(`Sorry, couldn't find ${image}.`);
}
let iconImage = await loadImage(imageUrl)
fm.writeImage(path, iconImage)
return iconImage
}
}
async function getSchedule() {
let url = "https://www.radioeins.de/content/rbb/rad/vorlagen/radiostartmodule-helper.broadcast.jsn";
let req = new Request(url)
let schedule = await req.loadJSON()
return schedule
}
// obtains 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
}
// search for the cover art on Spotify
async function searchCoverAtSpotify(title, artist, strict) {
let searchString
let searchToken = await getCachedSpotifyToken(false)
if (strict === true) {
searchString = encodeURIComponent("track:" + title + " artist:" + artist)
} else {
searchString = encodeURIComponent(artist + " " + title)
}
let searchUrl = "https://api.spotify.com/v1/search?q=" + searchString + "&type=track&market=DE&limit=1"
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 {
console.log("Getting new token from Spotify.")
let token = await getSpotifySearchToken()
fm.writeString(path, token)
return token
}
}
// check whether spotify api search returned a result
function gotResultFromSpotify(result) {
if (result != null && result.tracks != null && result.tracks.items != null && result.tracks.items.length == 1) {
return true
} else {
return false
}
}
function dateToYMD(date) {
var d = date.getDate();
var m = date.getMonth() + 1; //Month from 0 to 11
var y = date.getFullYear();
return '' + y + '-' + (m<=9 ? '0' + m : m) + '-' + (d <= 9 ? '0' + d : d);
}
@marco79cgn
Copy link
Author

marco79cgn commented Jul 29, 2023

@jocheno
Danke für das Feedback. Ich habe es geändert.
Zusätzlich habe ich auch das Design nochmal angepasst, damit lange Sendungstitel nichts kaputt machen.

Am Besten nochmal das ganze Skript oben kopieren. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment