From 4747050b60b2a6c35253123c8bcd106ed478c677 Mon Sep 17 00:00:00 2001 From: Sem Visscher Date: Fri, 19 Mar 2021 21:32:33 +0100 Subject: [PATCH 1/8] Added working lastfm support currently only played songs are added --- config/defaults.js | 6 ++ package.json | 3 + plugins/discord/back.js | 5 +- plugins/last-fm/back.js | 135 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 plugins/last-fm/back.js diff --git a/config/defaults.js b/config/defaults.js index 6238e1147e..b81d1d2e65 100644 --- a/config/defaults.js +++ b/config/defaults.js @@ -35,6 +35,12 @@ const defaultConfig = { ffmpegArgs: [], // e.g. ["-b:a", "192k"] for an audio bitrate of 192kb/s downloadFolder: undefined, // Custom download folder (absolute path) }, + "last-fm": { + enabled: false, + api_root: "http://ws.audioscrobbler.com/2.0/", + api_key: "04d76faaac8726e60988e14c105d421a", // api key registered by @semvis123 + secret: "a5d2a36fdf64819290f6982481eaffa2", + } }, }; diff --git a/package.json b/package.json index f5524a375d..df4725e1e7 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "@ffmpeg/core": "^0.8.5", "@ffmpeg/ffmpeg": "^0.9.7", "YoutubeNonStop": "git://github.com/lawfx/YoutubeNonStop.git#v0.8.1", + "axios": "^0.21.1", "browser-id3-writer": "^4.4.0", "discord-rpc": "^3.2.0", "downloads-folder": "^3.0.1", @@ -76,7 +77,9 @@ "electron-unhandled": "^3.0.2", "electron-updater": "^4.3.6", "filenamify": "^4.2.0", + "md5": "^2.3.0", "node-fetch": "^2.6.1", + "open": "^8.0.3", "ytdl-core": "^4.4.5" }, "devDependencies": { diff --git a/plugins/discord/back.js b/plugins/discord/back.js index 5c53193b06..1d7e8e212b 100644 --- a/plugins/discord/back.js +++ b/plugins/discord/back.js @@ -45,9 +45,6 @@ module.exports = (win) => { }); // Startup the rpc client - rpc.login({ - clientId, - }) - .catch(console.error); + rpc.login({ clientId }).catch(console.error); }); }; diff --git a/plugins/last-fm/back.js b/plugins/last-fm/back.js new file mode 100644 index 0000000000..9a88d4fb71 --- /dev/null +++ b/plugins/last-fm/back.js @@ -0,0 +1,135 @@ +const fetch = require('node-fetch'); +const md5 = require('md5'); +const open = require("open"); +const axios = require('axios'); +const { setOptions } = require('../../config/plugins'); +const getSongInfo = require('../../providers/song-info'); + +const createFormData = (params) => { + // creates the body for in the post request + let formData = new URLSearchParams(); + for (key in params) { + formData.append(key, params[key]); + } + return formData; +} +const createQueryString = (params, api_sig) => { + // creates a querystring + const queryData = [] + params.api_sig = api_sig + for (key in params) { + queryData.push(`${key}=${params[key]}`) + } + return '?'+queryData.join('&') +} + +const createApiSig = (params, secret) => { + // this function creates the api signature, see: https://www.last.fm/api/authspec + let keys = []; + for (key in params){ + keys.push(key); + } + keys.sort(); + let sig = ''; + for (key of keys) { + if (String(key) === 'format') + continue + sig += `${key}${params[key]}` + } + sig += secret + sig = md5(sig) + return sig +} + +const createToken = async ({api_key, api_root, secret}) => { + // creates an auth token + data = { + method: 'auth.gettoken', + api_key: api_key, + format: 'json' + } + let api_sig = createApiSig(data, secret); + let response = await fetch(`${api_root}${createQueryString(data, api_sig)}`); + response = await response.json(); + return response?.token; +} + +const authenticate = async (config) => { + // asks user for authentication + config.token = await createToken(config); + setOptions('last-fm', config); + open(`https://www.last.fm/api/auth/?api_key=${config.api_key}&token=${config.token}`); + return config +} + +const getAndSetSessionKey = async (config) => { + // get and set the session key + data = { + api_key: config.api_key, + format: 'json', + method: 'auth.getsession', + token: config.token, + } + api_sig = createApiSig(data, config.secret); + res = await fetch(`${config.api_root}${createQueryString(data, api_sig)}`); + res = await res.json(); + if (res.error) + await authenticate(config); + config.session_key = res?.session?.key; + setOptions('last-fm', config); + return config +} + + +const addScrobble = async (songInfo, config) => { + // this adds one scrobbled song + if (!config.session_key) + await getAndSetSessionKey(config); + data = { + track: songInfo.title, + artist: songInfo.artist, + api_key: config.api_key, + sk: config.session_key, + format: 'json', + method: 'track.scrobble', + timestamp: ~~((Date.now() - songInfo.elapsedSeconds)/1000), + duration: songInfo.songDuration, + } + data.api_sig = createApiSig(data, config.secret) + axios.post('https://ws.audioscrobbler.com/2.0/', createFormData(data)) + .then(res => res.data.scrobbles) + .catch(res => { + if (res.response.data.error == 9){ + // session key is invalid + config.session_key = undefined; + setOptions('last-fm', config); + authenticate(config); + } + }); +} + +// this will store the timeout that will trigger addScrobble +let scrobbleTimer = undefined; + +const lastfm = async (win, config) => { + const registerCallback = getSongInfo(win); + + if (!config.session_key) { + // not authenticated + config = await getAndSetSessionKey(config); + } + + registerCallback((songInfo)=> { + clearTimeout(scrobbleTimer); + if (!songInfo.isPaused) { + let scrobbleTime = Math.min(Math.ceil(songInfo.songDuration/2), 4*60); + if (scrobbleTime > songInfo.elapsedSeconds) { + // scrobble still needs to happen + timeToWait = (scrobbleTime-songInfo.elapsedSeconds)*1000; + scrobbleTimer = setTimeout(addScrobble, timeToWait, songInfo, config); + } + } + }) +} + +module.exports = lastfm; \ No newline at end of file From 42e3d48cafa1e1d32199d5e0012909e11b5ff8bc Mon Sep 17 00:00:00 2001 From: Sem Visscher Date: Fri, 19 Mar 2021 22:00:00 +0100 Subject: [PATCH 2/8] Added now playing to last-fm --- plugins/last-fm/back.js | 60 +++++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/plugins/last-fm/back.js b/plugins/last-fm/back.js index 9a88d4fb71..49eaaa5121 100644 --- a/plugins/last-fm/back.js +++ b/plugins/last-fm/back.js @@ -15,12 +15,12 @@ const createFormData = (params) => { } const createQueryString = (params, api_sig) => { // creates a querystring - const queryData = [] - params.api_sig = api_sig + const queryData = []; + params.api_sig = api_sig; for (key in params) { - queryData.push(`${key}=${params[key]}`) + queryData.push(`${key}=${params[key]}`); } - return '?'+queryData.join('&') + return '?'+queryData.join('&'); } const createApiSig = (params, secret) => { @@ -34,11 +34,11 @@ const createApiSig = (params, secret) => { for (key of keys) { if (String(key) === 'format') continue - sig += `${key}${params[key]}` + sig += `${key}${params[key]}`; } - sig += secret - sig = md5(sig) - return sig + sig += secret; + sig = md5(sig); + return sig; } const createToken = async ({api_key, api_root, secret}) => { @@ -47,7 +47,7 @@ const createToken = async ({api_key, api_root, secret}) => { method: 'auth.gettoken', api_key: api_key, format: 'json' - } + }; let api_sig = createApiSig(data, secret); let response = await fetch(`${api_root}${createQueryString(data, api_sig)}`); response = await response.json(); @@ -59,7 +59,7 @@ const authenticate = async (config) => { config.token = await createToken(config); setOptions('last-fm', config); open(`https://www.last.fm/api/auth/?api_key=${config.api_key}&token=${config.token}`); - return config + return config; } const getAndSetSessionKey = async (config) => { @@ -69,7 +69,7 @@ const getAndSetSessionKey = async (config) => { format: 'json', method: 'auth.getsession', token: config.token, - } + }; api_sig = createApiSig(data, config.secret); res = await fetch(`${config.api_root}${createQueryString(data, api_sig)}`); res = await res.json(); @@ -77,7 +77,7 @@ const getAndSetSessionKey = async (config) => { await authenticate(config); config.session_key = res?.session?.key; setOptions('last-fm', config); - return config + return config; } @@ -94,10 +94,9 @@ const addScrobble = async (songInfo, config) => { method: 'track.scrobble', timestamp: ~~((Date.now() - songInfo.elapsedSeconds)/1000), duration: songInfo.songDuration, - } - data.api_sig = createApiSig(data, config.secret) + }; + data.api_sig = createApiSig(data, config.secret); axios.post('https://ws.audioscrobbler.com/2.0/', createFormData(data)) - .then(res => res.data.scrobbles) .catch(res => { if (res.response.data.error == 9){ // session key is invalid @@ -108,6 +107,32 @@ const addScrobble = async (songInfo, config) => { }); } +const setNowPlaying = async (songInfo, config) => { + // this adds one scrobbled song + if (!config.session_key) + await getAndSetSessionKey(config); + data = { + track: songInfo.title, + artist: songInfo.artist, + api_key: config.api_key, + sk: config.session_key, + format: 'json', + method: 'track.updateNowPlaying', + duration: songInfo.songDuration, + }; + data.api_sig = createApiSig(data, config.secret); + axios.post('https://ws.audioscrobbler.com/2.0/', createFormData(data)) + .catch(res => { + if (res.response.data.error == 9){ + // session key is invalid + config.session_key = undefined; + setOptions('last-fm', config); + authenticate(config); + } + }); +} + + // this will store the timeout that will trigger addScrobble let scrobbleTimer = undefined; @@ -119,9 +144,10 @@ const lastfm = async (win, config) => { config = await getAndSetSessionKey(config); } - registerCallback((songInfo)=> { + registerCallback( songInfo => { clearTimeout(scrobbleTimer); if (!songInfo.isPaused) { + setNowPlaying(songInfo, config); let scrobbleTime = Math.min(Math.ceil(songInfo.songDuration/2), 4*60); if (scrobbleTime > songInfo.elapsedSeconds) { // scrobble still needs to happen @@ -129,7 +155,7 @@ const lastfm = async (win, config) => { scrobbleTimer = setTimeout(addScrobble, timeToWait, songInfo, config); } } - }) + }); } module.exports = lastfm; \ No newline at end of file From 2bb67db888ea1da9f8f05a73dddb7617d85e894b Mon Sep 17 00:00:00 2001 From: Sem Visscher Date: Sun, 21 Mar 2021 18:47:16 +0100 Subject: [PATCH 3/8] Fixed backwards compatibility --- plugins/last-fm/back.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/plugins/last-fm/back.js b/plugins/last-fm/back.js index 49eaaa5121..ece10a5a33 100644 --- a/plugins/last-fm/back.js +++ b/plugins/last-fm/back.js @@ -5,6 +5,13 @@ const axios = require('axios'); const { setOptions } = require('../../config/plugins'); const getSongInfo = require('../../providers/song-info'); +const defaultSettings = { + enabled: true, + api_root: "http://ws.audioscrobbler.com/2.0/", + api_key: "04d76faaac8726e60988e14c105d421a", // api key registered by @semvis123 + secret: "a5d2a36fdf64819290f6982481eaffa2", +} + const createFormData = (params) => { // creates the body for in the post request let formData = new URLSearchParams(); @@ -138,7 +145,13 @@ let scrobbleTimer = undefined; const lastfm = async (win, config) => { const registerCallback = getSongInfo(win); - + + if (!config.api_root){ + // settings are not present, creating them with the default values + config = defaultSettings; + setOptions('last-fm', config); + } + if (!config.session_key) { // not authenticated config = await getAndSetSessionKey(config); From 1355b692b927deec4e1fdc641f7bad645ab01667 Mon Sep 17 00:00:00 2001 From: Sem Visscher Date: Sun, 21 Mar 2021 22:28:05 +0100 Subject: [PATCH 4/8] Remove the - Topic for more matches --- plugins/last-fm/back.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/last-fm/back.js b/plugins/last-fm/back.js index ece10a5a33..9eea51eca8 100644 --- a/plugins/last-fm/back.js +++ b/plugins/last-fm/back.js @@ -94,7 +94,7 @@ const addScrobble = async (songInfo, config) => { await getAndSetSessionKey(config); data = { track: songInfo.title, - artist: songInfo.artist, + artist: songInfo.artist?.replace(' - Topic', ''), api_key: config.api_key, sk: config.session_key, format: 'json', @@ -120,7 +120,7 @@ const setNowPlaying = async (songInfo, config) => { await getAndSetSessionKey(config); data = { track: songInfo.title, - artist: songInfo.artist, + artist: songInfo.artist?.replace(' - Topic', ''), api_key: config.api_key, sk: config.session_key, format: 'json', From bffbcb229d54f64bf84f77f373bb7bc1f8447426 Mon Sep 17 00:00:00 2001 From: Sem Visscher Date: Thu, 25 Mar 2021 20:15:43 +0100 Subject: [PATCH 5/8] Made the suffixes configurable and added VEVO suffix to remove --- config/defaults.js | 1 + plugins/last-fm/back.js | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/config/defaults.js b/config/defaults.js index b81d1d2e65..2f345ab463 100644 --- a/config/defaults.js +++ b/config/defaults.js @@ -40,6 +40,7 @@ const defaultConfig = { api_root: "http://ws.audioscrobbler.com/2.0/", api_key: "04d76faaac8726e60988e14c105d421a", // api key registered by @semvis123 secret: "a5d2a36fdf64819290f6982481eaffa2", + suffixesToRemove: [' - Topic', 'VEVO'] // removes suffixes of the artist name, for better recognition } }, }; diff --git a/plugins/last-fm/back.js b/plugins/last-fm/back.js index 9eea51eca8..c1c6a09dd7 100644 --- a/plugins/last-fm/back.js +++ b/plugins/last-fm/back.js @@ -10,6 +10,20 @@ const defaultSettings = { api_root: "http://ws.audioscrobbler.com/2.0/", api_key: "04d76faaac8726e60988e14c105d421a", // api key registered by @semvis123 secret: "a5d2a36fdf64819290f6982481eaffa2", + suffixesToRemove: [' - Topic', 'VEVO'] +} + +const cleanupArtistName = (config, artist) => { + let { suffixesToRemove } = config; + if (suffixesToRemove === undefined){ + suffixesToRemove = defaultSettings.suffixesToRemove; + config.suffixesToRemove = suffixesToRemove; + setOptions('last-fm', config); + } + for (suffix of suffixesToRemove){ + artist = artist.replace(suffix,''); + } + return artist } const createFormData = (params) => { @@ -94,7 +108,7 @@ const addScrobble = async (songInfo, config) => { await getAndSetSessionKey(config); data = { track: songInfo.title, - artist: songInfo.artist?.replace(' - Topic', ''), + artist: songInfo.artist, api_key: config.api_key, sk: config.session_key, format: 'json', @@ -120,7 +134,7 @@ const setNowPlaying = async (songInfo, config) => { await getAndSetSessionKey(config); data = { track: songInfo.title, - artist: songInfo.artist?.replace(' - Topic', ''), + artist: songInfo.artist, api_key: config.api_key, sk: config.session_key, format: 'json', @@ -159,6 +173,7 @@ const lastfm = async (win, config) => { registerCallback( songInfo => { clearTimeout(scrobbleTimer); + songInfo.artist = cleanupArtistName(config, songInfo.artist); if (!songInfo.isPaused) { setNowPlaying(songInfo, config); let scrobbleTime = Math.min(Math.ceil(songInfo.songDuration/2), 4*60); From f253a69656aeafa88bcca224d5fcbdc78cace647 Mon Sep 17 00:00:00 2001 From: Sem Visscher Date: Thu, 25 Mar 2021 21:11:55 +0100 Subject: [PATCH 6/8] added comment for the added function --- plugins/last-fm/back.js | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/last-fm/back.js b/plugins/last-fm/back.js index c1c6a09dd7..0d32387f46 100644 --- a/plugins/last-fm/back.js +++ b/plugins/last-fm/back.js @@ -14,6 +14,7 @@ const defaultSettings = { } const cleanupArtistName = (config, artist) => { + // removes the suffixes of the artist name for more recognition by last.fm let { suffixesToRemove } = config; if (suffixesToRemove === undefined){ suffixesToRemove = defaultSettings.suffixesToRemove; From bd82bd224968238f46b4aca6a22da0f569fe7d92 Mon Sep 17 00:00:00 2001 From: Sem Visscher Date: Thu, 25 Mar 2021 21:27:33 +0100 Subject: [PATCH 7/8] added extra comments and corrected existing ones --- plugins/last-fm/back.js | 44 +++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/plugins/last-fm/back.js b/plugins/last-fm/back.js index 0d32387f46..4e23846953 100644 --- a/plugins/last-fm/back.js +++ b/plugins/last-fm/back.js @@ -16,18 +16,18 @@ const defaultSettings = { const cleanupArtistName = (config, artist) => { // removes the suffixes of the artist name for more recognition by last.fm let { suffixesToRemove } = config; - if (suffixesToRemove === undefined){ + if (suffixesToRemove === undefined) { suffixesToRemove = defaultSettings.suffixesToRemove; config.suffixesToRemove = suffixesToRemove; setOptions('last-fm', config); } - for (suffix of suffixesToRemove){ - artist = artist.replace(suffix,''); + for (suffix of suffixesToRemove) { + artist = artist.replace(suffix, ''); } return artist } -const createFormData = (params) => { +const createFormData = params => { // creates the body for in the post request let formData = new URLSearchParams(); for (key in params) { @@ -48,7 +48,7 @@ const createQueryString = (params, api_sig) => { const createApiSig = (params, secret) => { // this function creates the api signature, see: https://www.last.fm/api/authspec let keys = []; - for (key in params){ + for (key in params) { keys.push(key); } keys.sort(); @@ -63,8 +63,8 @@ const createApiSig = (params, secret) => { return sig; } -const createToken = async ({api_key, api_root, secret}) => { - // creates an auth token +const createToken = async ({ api_key, api_root, secret }) => { + // creates and stores the auth token data = { method: 'auth.gettoken', api_key: api_key, @@ -76,16 +76,16 @@ const createToken = async ({api_key, api_root, secret}) => { return response?.token; } -const authenticate = async (config) => { - // asks user for authentication +const authenticate = async config => { + // asks the user for authentication config.token = await createToken(config); setOptions('last-fm', config); open(`https://www.last.fm/api/auth/?api_key=${config.api_key}&token=${config.token}`); return config; } -const getAndSetSessionKey = async (config) => { - // get and set the session key +const getAndSetSessionKey = async config => { + // get and store the session key data = { api_key: config.api_key, format: 'json', @@ -104,7 +104,7 @@ const getAndSetSessionKey = async (config) => { const addScrobble = async (songInfo, config) => { - // this adds one scrobbled song + // this adds one scrobbled song to last.fm if (!config.session_key) await getAndSetSessionKey(config); data = { @@ -114,14 +114,14 @@ const addScrobble = async (songInfo, config) => { sk: config.session_key, format: 'json', method: 'track.scrobble', - timestamp: ~~((Date.now() - songInfo.elapsedSeconds)/1000), + timestamp: ~~((Date.now() - songInfo.elapsedSeconds) / 1000), duration: songInfo.songDuration, }; data.api_sig = createApiSig(data, config.secret); axios.post('https://ws.audioscrobbler.com/2.0/', createFormData(data)) .catch(res => { - if (res.response.data.error == 9){ - // session key is invalid + if (res.response.data.error == 9) { + // session key is invalid, so remove it from the config and reauthenticate config.session_key = undefined; setOptions('last-fm', config); authenticate(config); @@ -130,7 +130,7 @@ const addScrobble = async (songInfo, config) => { } const setNowPlaying = async (songInfo, config) => { - // this adds one scrobbled song + // this sets the now playing status in last.fm if (!config.session_key) await getAndSetSessionKey(config); data = { @@ -145,8 +145,8 @@ const setNowPlaying = async (songInfo, config) => { data.api_sig = createApiSig(data, config.secret); axios.post('https://ws.audioscrobbler.com/2.0/', createFormData(data)) .catch(res => { - if (res.response.data.error == 9){ - // session key is invalid + if (res.response.data.error == 9) { + // session key is invalid, so remove it from the config and reauthenticate config.session_key = undefined; setOptions('last-fm', config); authenticate(config); @@ -161,7 +161,7 @@ let scrobbleTimer = undefined; const lastfm = async (win, config) => { const registerCallback = getSongInfo(win); - if (!config.api_root){ + if (!config.api_root) { // settings are not present, creating them with the default values config = defaultSettings; setOptions('last-fm', config); @@ -173,14 +173,16 @@ const lastfm = async (win, config) => { } registerCallback( songInfo => { + // set remove the old scrobble timer clearTimeout(scrobbleTimer); + // make the artist name a bit cleaner songInfo.artist = cleanupArtistName(config, songInfo.artist); if (!songInfo.isPaused) { setNowPlaying(songInfo, config); - let scrobbleTime = Math.min(Math.ceil(songInfo.songDuration/2), 4*60); + let scrobbleTime = Math.min(Math.ceil(songInfo.songDuration / 2), 4 * 60); if (scrobbleTime > songInfo.elapsedSeconds) { // scrobble still needs to happen - timeToWait = (scrobbleTime-songInfo.elapsedSeconds)*1000; + timeToWait = (scrobbleTime - songInfo.elapsedSeconds) * 1000; scrobbleTimer = setTimeout(addScrobble, timeToWait, songInfo, config); } } From 0c6630d2d922bcfe0b1e3bd6d77e930d7990f38d Mon Sep 17 00:00:00 2001 From: semvis123 Date: Sat, 3 Apr 2021 17:12:28 +0200 Subject: [PATCH 8/8] additional cleanup/refactoring --- package.json | 1 - plugins/last-fm/back.js | 97 +++++++++++++++++------------------------ yarn.lock | 49 ++++++++++++++++++++- 3 files changed, 87 insertions(+), 60 deletions(-) diff --git a/package.json b/package.json index 79a9cbf7f2..aa3f1b957c 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,6 @@ "@ffmpeg/core": "^0.8.5", "@ffmpeg/ffmpeg": "^0.9.7", "YoutubeNonStop": "git://github.com/lawfx/YoutubeNonStop.git#v0.8.1", - "axios": "^0.21.1", "async-mutex": "^0.3.1", "browser-id3-writer": "^4.4.0", "discord-rpc": "^3.2.0", diff --git a/plugins/last-fm/back.js b/plugins/last-fm/back.js index 4e23846953..a34a0a19f1 100644 --- a/plugins/last-fm/back.js +++ b/plugins/last-fm/back.js @@ -1,35 +1,24 @@ const fetch = require('node-fetch'); const md5 = require('md5'); const open = require("open"); -const axios = require('axios'); const { setOptions } = require('../../config/plugins'); const getSongInfo = require('../../providers/song-info'); - -const defaultSettings = { - enabled: true, - api_root: "http://ws.audioscrobbler.com/2.0/", - api_key: "04d76faaac8726e60988e14c105d421a", // api key registered by @semvis123 - secret: "a5d2a36fdf64819290f6982481eaffa2", - suffixesToRemove: [' - Topic', 'VEVO'] -} +const defaultConfig = require('../../config/defaults'); const cleanupArtistName = (config, artist) => { // removes the suffixes of the artist name for more recognition by last.fm - let { suffixesToRemove } = config; - if (suffixesToRemove === undefined) { - suffixesToRemove = defaultSettings.suffixesToRemove; - config.suffixesToRemove = suffixesToRemove; - setOptions('last-fm', config); - } + const { suffixesToRemove } = config; + if (suffixesToRemove === undefined) return artist; + for (suffix of suffixesToRemove) { artist = artist.replace(suffix, ''); } - return artist + return artist; } const createFormData = params => { // creates the body for in the post request - let formData = new URLSearchParams(); + const formData = new URLSearchParams(); for (key in params) { formData.append(key, params[key]); } @@ -40,14 +29,14 @@ const createQueryString = (params, api_sig) => { const queryData = []; params.api_sig = api_sig; for (key in params) { - queryData.push(`${key}=${params[key]}`); + queryData.push(`${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`); } return '?'+queryData.join('&'); } const createApiSig = (params, secret) => { // this function creates the api signature, see: https://www.last.fm/api/authspec - let keys = []; + const keys = []; for (key in params) { keys.push(key); } @@ -65,12 +54,12 @@ const createApiSig = (params, secret) => { const createToken = async ({ api_key, api_root, secret }) => { // creates and stores the auth token - data = { + const data = { method: 'auth.gettoken', api_key: api_key, format: 'json' }; - let api_sig = createApiSig(data, secret); + const api_sig = createApiSig(data, secret); let response = await fetch(`${api_root}${createQueryString(data, api_sig)}`); response = await response.json(); return response?.token; @@ -86,14 +75,14 @@ const authenticate = async config => { const getAndSetSessionKey = async config => { // get and store the session key - data = { + const data = { api_key: config.api_key, format: 'json', method: 'auth.getsession', token: config.token, }; - api_sig = createApiSig(data, config.secret); - res = await fetch(`${config.api_root}${createQueryString(data, api_sig)}`); + const api_sig = createApiSig(data, config.secret); + let res = await fetch(`${config.api_root}${createQueryString(data, api_sig)}`); res = await res.json(); if (res.error) await authenticate(config); @@ -102,23 +91,23 @@ const getAndSetSessionKey = async config => { return config; } - -const addScrobble = async (songInfo, config) => { - // this adds one scrobbled song to last.fm +const postSongDataToAPI = async (songInfo, config, data) => { + // this sends a post request to the api, and adds the common data if (!config.session_key) await getAndSetSessionKey(config); - data = { + + const postData = { track: songInfo.title, + duration: songInfo.songDuration, artist: songInfo.artist, api_key: config.api_key, sk: config.session_key, format: 'json', - method: 'track.scrobble', - timestamp: ~~((Date.now() - songInfo.elapsedSeconds) / 1000), - duration: songInfo.songDuration, + ...data, }; - data.api_sig = createApiSig(data, config.secret); - axios.post('https://ws.audioscrobbler.com/2.0/', createFormData(data)) + + postData.api_sig = createApiSig(postData, config.secret); + fetch('https://ws.audioscrobbler.com/2.0/', {method: 'POST', body: createFormData(postData)}) .catch(res => { if (res.response.data.error == 9) { // session key is invalid, so remove it from the config and reauthenticate @@ -129,29 +118,21 @@ const addScrobble = async (songInfo, config) => { }); } -const setNowPlaying = async (songInfo, config) => { +const addScrobble = (songInfo, config) => { + // this adds one scrobbled song to last.fm + const data = { + method: 'track.scrobble', + timestamp: ~~((Date.now() - songInfo.elapsedSeconds) / 1000), + }; + postSongDataToAPI(songInfo, config, data); +} + +const setNowPlaying = (songInfo, config) => { // this sets the now playing status in last.fm - if (!config.session_key) - await getAndSetSessionKey(config); - data = { - track: songInfo.title, - artist: songInfo.artist, - api_key: config.api_key, - sk: config.session_key, - format: 'json', + const data = { method: 'track.updateNowPlaying', - duration: songInfo.songDuration, }; - data.api_sig = createApiSig(data, config.secret); - axios.post('https://ws.audioscrobbler.com/2.0/', createFormData(data)) - .catch(res => { - if (res.response.data.error == 9) { - // session key is invalid, so remove it from the config and reauthenticate - config.session_key = undefined; - setOptions('last-fm', config); - authenticate(config); - } - }); + postSongDataToAPI(songInfo, config, data); } @@ -161,9 +142,10 @@ let scrobbleTimer = undefined; const lastfm = async (win, config) => { const registerCallback = getSongInfo(win); - if (!config.api_root) { + if (!config.api_root || !config.suffixesToRemove) { // settings are not present, creating them with the default values - config = defaultSettings; + config = defaultConfig.plugins['last-fm']; + config.enabled = true; setOptions('last-fm', config); } @@ -179,10 +161,11 @@ const lastfm = async (win, config) => { songInfo.artist = cleanupArtistName(config, songInfo.artist); if (!songInfo.isPaused) { setNowPlaying(songInfo, config); - let scrobbleTime = Math.min(Math.ceil(songInfo.songDuration / 2), 4 * 60); + // scrobble when the song is half way through, or has passed the 4 minute mark + const scrobbleTime = Math.min(Math.ceil(songInfo.songDuration / 2), 4 * 60); if (scrobbleTime > songInfo.elapsedSeconds) { // scrobble still needs to happen - timeToWait = (scrobbleTime - songInfo.elapsedSeconds) * 1000; + const timeToWait = (scrobbleTime - songInfo.elapsedSeconds) * 1000; scrobbleTimer = setTimeout(addScrobble, timeToWait, songInfo, config); } } diff --git a/yarn.lock b/yarn.lock index f1a14d402a..f428bd55d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1708,6 +1708,13 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== +axios@^0.21.1: + version "0.21.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" + integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== + dependencies: + follow-redirects "^1.10.0" + babel-eslint@^10.1.0: version "10.1.0" resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232" @@ -2223,6 +2230,11 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +charenc@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= + chownr@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" @@ -2587,6 +2599,11 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +crypt@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= + crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -2756,6 +2773,11 @@ defer-to-connect@^2.0.0: resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.0.tgz#83d6b199db041593ac84d781b5222308ccf4c2c1" integrity sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg== +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== + define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -3961,6 +3983,11 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469" integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA== +follow-redirects@^1.10.0: + version "1.13.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267" + integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA== + for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -4686,7 +4713,7 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= -is-buffer@^1.1.5: +is-buffer@^1.1.5, is-buffer@~1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== @@ -4747,7 +4774,7 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2: is-data-descriptor "^1.0.0" kind-of "^6.0.2" -is-docker@^2.0.0: +is-docker@^2.0.0, is-docker@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156" integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw== @@ -6023,6 +6050,15 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" +md5@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" + integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== + dependencies: + charenc "0.0.2" + crypt "0.0.2" + is-buffer "~1.1.6" + memory-fs@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.2.0.tgz#f2bb25368bc121e391c2520de92969caee0a0290" @@ -6519,6 +6555,15 @@ open@^7.3.0: is-docker "^2.0.0" is-wsl "^2.1.1" +open@^8.0.3: + version "8.0.5" + resolved "https://registry.yarnpkg.com/open/-/open-8.0.5.tgz#92ee3faafef4ddbe78006f7881572f3e81430b8f" + integrity sha512-hkPXCz7gijWp2GoWqsQ4O/5p7F6d5pIQ/+9NyeWG1nABJ4zvLi9kJRv1a44kVf5p13wK0WMoiRA+Xey68yOytA== + dependencies: + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" + optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"