Hi,
Here's a module I programmed for controlling volume and basic Spotify playback functions with Flic Twist, in case someone else has any use for it. To use it, you'll have to register for a Spotify developer account and create a new app from their dashboard, in order to get access tokens etc.
@flichub @Emil: Is it possible to somehow get this added to the existing public Spotify "provider"? Seems a bit tedious/difficult for users without any programming experience to go down this particular rabbit hole.
// main.js
const flicapp = require('flicapp');
const http = require('http');
const datastore = require('datastore');
datastore.get('clientID', (err, key) => {
const clientID = key // store this first: (datastore.put('clientID', 'YOUR CLIENT ID')
datastore.get('clientSecret', (err, key) => {
const clientSecret = key // store this first: (datastore.put('clientSecret', 'YOUR CLIENT SECRET')
function percent(volume, decimalPlaces = 0) {
const percentage = (volume * 100).toFixed(decimalPlaces);
return `${percentage}`;
}
function spotifyStatus() {
datastore.get('accessToken', (err, key) => {
const accessToken = key
if (key !== null) {
const options = {
url: 'https://api.spotify.com/v1/me/player',
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + accessToken
}
};
const req = http.makeRequest(options, (error, result) => {
if (result.statusCode == 401) {
refreshToken();
}
if (result.statusCode == 204) {
var status = "No active sessions."
console.log(status);
}
if (result.statusCode == 200) {
var jresp = JSON.parse(result.content);
var status = jresp["is_playing"];
if (status === true) {
spotifyPause();
}
if (status === false) {
spotifyPlay();
}
}
})
}
})
};
function spotifyPlay() {
datastore.get('accessToken', (err, key) => {
const accessToken = key
if (key !== null) {
const options = {
url: 'https://api.spotify.com/v1/me/player/play',
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + accessToken
}
};
const req = http.makeRequest(options, (error, result) => {
if (result.statusCode == 401) {
refreshToken();
}
})
}
})
};
function spotifyPause() {
datastore.get('accessToken', (err, key) => {
const accessToken = key
if (key !== null) {
const options = {
url: 'https://api.spotify.com/v1/me/player/pause',
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + accessToken
}
};
const req = http.makeRequest(options, (error, result) => {
if (result.statusCode == 401) {
refreshToken();
}
})
}
})
};
function spotifyNext() {
datastore.get('accessToken', (err, key) => {
const accessToken = key
if (key !== null) {
const options = {
url: 'https://api.spotify.com/v1/me/player/next',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + accessToken
}
};
const req = http.makeRequest(options, (error, result) => {
if (result.statusCode == 401) {
refreshToken();
}
})
}
})
};
function spotifyVolume(volume) {
datastore.get('accessToken', (err, key) => {
const accessToken = key
if (key !== null) {
const options = {
url: 'https://api.spotify.com/v1/me/player/volume?volume_percent=' + volume,
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + accessToken
}
};
const req = http.makeRequest(options, (error, result) => {
if (result.statusCode == 401) {
refreshToken();
}
if (result.statusCode == 404) {
console.log("No active sessions.")
}
})
}
})
};
function refreshToken() {
datastore.get('refreshToken', (err, key) => {
const refreshToken = key
if (key !== null) {
const options = {
url: 'https://accounts.spotify.com/api/token',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
content: "refresh_token=" + refreshToken + "&scope=user-read-playback-state%2C+user-modify-playback-state%2C+user-follow-read%2C+user-library-read%2C+streaming%2C+user-read-playback-position%2C+user-top-read%2C+user-read-currently-playing%2C+&client_id=" + clientID + "&client_secret=" + clientSecret + "&grant_type=refresh_token"
};
const req = http.makeRequest(options, (error, result) => {
if (!error && result.statusCode === 200) {
var jresp = JSON.parse(result.content);
datastore.put('accessToken', jresp["access_token"])
spotifyStatus();
}
})
}
})
};
flicapp.on("actionMessage", function(message) {
if (message == 'playtoggle') {
spotifyStatus();
}
});
flicapp.on("actionMessage", function(message) {
if (message == 'next') {
spotifyNext();
}
});
flicapp.on("virtualDeviceUpdate", function(metaData, values) {
if (values.volume && metaData.virtualDeviceId == "spotify") {
var volume = percent(values.volume)
spotifyVolume(volume);
flicapp.virtualDeviceUpdateState("Speaker", "spotify", {
volume: values.volume
});
}
});
}); // clientID var
}); // clientSecret var
As you probably can see, I'm not exactly a Javascript wizard, but it works
Enjoy.
Andreas


🤍
, I don't see any good reasons for not documenting it just as extensively as the Flic 2, and making (all of) the functionality availble so people can use it for what they want. That was why I got a Flic button in the first place, 9 years ago. Because it inspired creative solutions and new areas of use. This inspires turning on and off the light.
️




