Added Spotify tracking. Fixed filters when none given.

This commit is contained in:
Tom 2024-12-04 19:01:40 +00:00
parent ad624172a6
commit 79b27b8e32
7 changed files with 175 additions and 5 deletions

3
.gitignore vendored
View File

@ -1,4 +1,5 @@
node_modules/ node_modules/
logs/ logs/
config/* config/*
!config/configuration.js !config/configuration.js
credentials.*

View File

@ -22,6 +22,11 @@ const configuration = {
*/ */
}, },
}, },
spotify: {
client_id: null,
client_secret: null,
redirect_uri: null
},
web: { web: {
host: null, host: null,
port: null port: null
@ -43,6 +48,9 @@ if (config.has("scrobble.minimum.duration"))
if (config.has("scrobble.minimum.percent")) if (config.has("scrobble.minimum.percent"))
configuration.scrobble.minimum.percent = config.get("scrobble.minimum.percent"); configuration.scrobble.minimum.percent = config.get("scrobble.minimum.percent");
if (config.has("spotify"))
configuration.spotify = config.get("spotify");
if (config.has("web.host")) if (config.has("web.host"))
configuration.web.host = config.get("web.host"); configuration.web.host = config.get("web.host");
if (config.has("web.port")) if (config.has("web.port"))

48
controllers/home.js Normal file
View File

@ -0,0 +1,48 @@
const axios = require("axios");
const config = require("../config/configuration");
const fs = require("fs/promises");
const logger = require("../services/logging");
const querystring = require('node:querystring');
function authorizeSpotify(req, res) {
res.redirect("https://accounts.spotify.com/authorize?" + querystring.stringify({
response_type: "code",
client_id: config.spotify.client_id,
scope: "user-read-playback-state user-read-currently-playing",
redirect_uri: config.spotify.redirect_uri,
}));
}
async function callback(req, res) {
const code = req.query.code;
try {
const response = await axios.post("https://accounts.spotify.com/api/token",
{
code: code,
redirect_uri: config.spotify.redirect_uri,
grant_type: "authorization_code"
},
{
headers: {
"Authorization": "Basic " + new Buffer.from(config.spotify.client_id + ':' + config.spotify.client_secret).toString('base64'),
"Content-Type": "application/x-www-form-urlencoded"
}
}
);
const data = response.data;
data["expires_at"] = Date.now() + data["expires_in"] * 1000;
await fs.writeFile("credentials.spotify.json", JSON.stringify(data));
res.redirect("/");
} catch (ex) {
logger.error(ex, "Failed to get Spotify oauth.");
res.send({ 'error': "Something went wrong with spotify's oauth flow" });
}
}
module.exports = {
authorizeSpotify,
callback
}

View File

@ -1,7 +1,16 @@
const home = require("../controllers/home");
const router = require("express").Router(); const router = require("express").Router();
router.get("/", async (req, res) => { router.get("/", async (req, res) => {
res.send("welcome to an empty page."); res.send("welcome to an empty page.");
}); });
router.get("/auth/spotify", (req, res) => {
home.authorizeSpotify(req, res);
});
router.get("/callback", (req, res) => {
home.callback(req, res);
});
module.exports = router; module.exports = router;

View File

@ -2,7 +2,6 @@ const pino = require("pino");
const logger = pino(pino.destination({ dest: 'logs/.log', sync: false })); const logger = pino(pino.destination({ dest: 'logs/.log', sync: false }));
const environment = process.env.NODE_ENV || 'development'; const environment = process.env.NODE_ENV || 'development';
console.log(process.env.NODE_ENV);
if (environment == "production") { if (environment == "production") {
logger.level = 30 logger.level = 30
} else { } else {

View File

@ -1,6 +1,7 @@
const plex = require("./plex"); const plex = require("./plex");
const logger = require("./logging") const logger = require("./logging")
const config = require("../config/configuration"); const config = require("../config/configuration");
const spotify = require("./spotify");
const lastPlaying = {}; const lastPlaying = {};
const lastScrobbleTimes = {}; const lastScrobbleTimes = {};
@ -9,6 +10,12 @@ async function poll() {
const now = Date.now(); const now = Date.now();
const playing = []; const playing = [];
await spotify.loadCredentials();
await spotify.refreshTokenIfNeeded();
const spotifyTrack = await spotify.getCurrentlyPlaying();
if (spotifyTrack != null)
playing.push(spotifyTrack);
try { try {
const data = await plex.getCurrentlyPlaying(); const data = await plex.getCurrentlyPlaying();
playing.push.apply(playing, data); playing.push.apply(playing, data);
@ -46,8 +53,8 @@ async function poll() {
} }
function applyFilter(track, filters) { function applyFilter(track, filters) {
if (!filters) if (!filters || filters.length == 0)
return; return true;
for (let filter of filters) { for (let filter of filters) {
if (filter.library && !filter.library.some(l => l == track.library)) if (filter.library && !filter.library.some(l => l == track.library))
@ -67,7 +74,7 @@ function applyFilter(track, filters) {
function checkIfCanScrobble(current, previous, now) { function checkIfCanScrobble(current, previous, now) {
if (!previous) if (!previous)
return; return false;
let filters = []; let filters = [];
if (previous.source == 'plex') if (previous.source == 'plex')

98
services/spotify.js Normal file
View File

@ -0,0 +1,98 @@
const axios = require("axios");
const config = require("../config/configuration")
const logger = require("./logging");
const fs = require("fs/promises");
const fss = require("fs")
let token = null;
let cache = {}
async function refreshTokenIfNeeded() {
if (!token || token["expires_at"] && token["expires_at"] - Date.now() > 900) {
return false;
}
try {
const response = await axios.post("https://accounts.spotify.com/api/token",
{
client_id: config.spotify.client_id,
refresh_token: token["refresh_token"],
grant_type: "refresh_token"
},
{
headers: {
"Authorization": "Basic " + new Buffer.from(config.spotify.client_id + ':' + config.spotify.client_secret).toString('base64'),
"Content-Type": "application/x-www-form-urlencoded"
}
}
);
const data = response.data;
data["expires_at"] = Date.now() + data["expires_in"] * 1000;
if (!data["refresh_token"]) {
data["refresh_token"] = token["refresh_token"];
}
await fs.writeFile("credentials.spotify.json", JSON.stringify(data));
token = data;
logger.info("Updated access token for Spotify.");
return true;
} catch (ex) {
logger.error(ex, "Failed to get Spotify oauth.");
return false;
}
}
async function loadCredentials() {
if (!fss.existsSync("credentials.spotify.json")) {
logger.info("No Spotify credentials found.");
return;
}
const content = await fs.readFile("credentials.spotify.json", "utf-8");
token = JSON.parse(content);
}
async function getCurrentlyPlaying(cached = false) {
if (cached) {
return cache['spotify']
}
try {
const response = await axios.get("https://api.spotify.com/v1/me/player/currently-playing",
{
headers: {
"Authorization": "Bearer " + token["access_token"]
}
}
);
if (!response.data) {
cache['spotify'] = null;
return null;
}
const media = response.data.item;
cache['spotify'] = {
"track": media.name,
"album": media.album.name,
"artist": media.artists.map(a => a.name).join(', '),
"year": media.parentYear,
"duration": media.duration_ms,
"playtime": response.data.progress_ms,
"mediaKey": media.id,
"sessionKey": "spotify",
"state": response.data.is_playing ? "playing" : "paused",
"source": "spotify"
};
return cache['spotify'];
} catch (ex) {
logger.error(ex, "Failed to get currently playing data from Spotify.");
return null;
}
}
module.exports = {
refreshTokenIfNeeded,
loadCredentials,
getCurrentlyPlaying
}