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 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) => { // 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)) .catch(res => { if (res.response.data.error == 9){ // session key is invalid config.session_key = undefined; setOptions('last-fm', config); authenticate(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; 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); } 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); if (scrobbleTime > songInfo.elapsedSeconds) { // scrobble still needs to happen timeToWait = (scrobbleTime-songInfo.elapsedSeconds)*1000; scrobbleTimer = setTimeout(addScrobble, timeToWait, songInfo, config); } } }); } module.exports = lastfm;