mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-09 01:31:46 +00:00
172 lines
4.8 KiB
JavaScript
172 lines
4.8 KiB
JavaScript
const { shell } = require('electron');
|
|
const fetch = require('node-fetch');
|
|
const md5 = require('md5');
|
|
|
|
const { setOptions } = require('../../config/plugins');
|
|
const registerCallback = require('../../providers/song-info');
|
|
const defaultConfig = require('../../config/defaults');
|
|
|
|
const createFormData = (parameters) => {
|
|
// Creates the body for in the post request
|
|
const formData = new URLSearchParams();
|
|
for (const key in parameters) {
|
|
formData.append(key, parameters[key]);
|
|
}
|
|
|
|
return formData;
|
|
};
|
|
|
|
const createQueryString = (parameters, api_sig) => {
|
|
// Creates a querystring
|
|
const queryData = [];
|
|
parameters.api_sig = api_sig;
|
|
for (const key in parameters) {
|
|
queryData.push(`${encodeURIComponent(key)}=${encodeURIComponent(parameters[key])}`);
|
|
}
|
|
|
|
return '?' + queryData.join('&');
|
|
};
|
|
|
|
const createApiSig = (parameters, secret) => {
|
|
// This function creates the api signature, see: https://www.last.fm/api/authspec
|
|
const keys = [];
|
|
for (const key in parameters) {
|
|
keys.push(key);
|
|
}
|
|
|
|
keys.sort();
|
|
let sig = '';
|
|
for (const key of keys) {
|
|
if (String(key) === 'format') {
|
|
continue;
|
|
}
|
|
|
|
sig += `${key}${parameters[key]}`;
|
|
}
|
|
|
|
sig += secret;
|
|
sig = md5(sig);
|
|
return sig;
|
|
};
|
|
|
|
const createToken = async ({ api_key, api_root, secret }) => {
|
|
// Creates and stores the auth token
|
|
const data = {
|
|
method: 'auth.gettoken',
|
|
api_key,
|
|
format: 'json',
|
|
};
|
|
const 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 the user for authentication
|
|
config.token = await createToken(config);
|
|
setOptions('last-fm', config);
|
|
shell.openExternal(`https://www.last.fm/api/auth/?api_key=${config.api_key}&token=${config.token}`);
|
|
return config;
|
|
};
|
|
|
|
const getAndSetSessionKey = async (config) => {
|
|
// Get and store the session key
|
|
const data = {
|
|
api_key: config.api_key,
|
|
format: 'json',
|
|
method: 'auth.getsession',
|
|
token: config.token,
|
|
};
|
|
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);
|
|
}
|
|
|
|
config.session_key = res?.session?.key;
|
|
setOptions('last-fm', config);
|
|
return config;
|
|
};
|
|
|
|
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);
|
|
}
|
|
|
|
const postData = {
|
|
track: songInfo.title,
|
|
duration: songInfo.songDuration,
|
|
artist: songInfo.artist,
|
|
...(songInfo.album ? { album: songInfo.album } : undefined), // Will be undefined if current song is a video
|
|
api_key: config.api_key,
|
|
sk: config.session_key,
|
|
format: 'json',
|
|
...data,
|
|
};
|
|
|
|
postData.api_sig = createApiSig(postData, config.secret);
|
|
fetch('https://ws.audioscrobbler.com/2.0/', { method: 'POST', body: createFormData(postData) })
|
|
.catch((error) => {
|
|
if (error.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);
|
|
}
|
|
});
|
|
};
|
|
|
|
const addScrobble = (songInfo, config) => {
|
|
// This adds one scrobbled song to last.fm
|
|
const data = {
|
|
method: 'track.scrobble',
|
|
timestamp: Math.trunc((Date.now() - songInfo.elapsedSeconds) / 1000),
|
|
};
|
|
postSongDataToAPI(songInfo, config, data);
|
|
};
|
|
|
|
const setNowPlaying = (songInfo, config) => {
|
|
// This sets the now playing status in last.fm
|
|
const data = {
|
|
method: 'track.updateNowPlaying',
|
|
};
|
|
postSongDataToAPI(songInfo, config, data);
|
|
};
|
|
|
|
// This will store the timeout that will trigger addScrobble
|
|
let scrobbleTimer;
|
|
|
|
const lastfm = async (_win, config) => {
|
|
if (!config.api_root) {
|
|
// Settings are not present, creating them with the default values
|
|
config = defaultConfig.plugins['last-fm'];
|
|
config.enabled = true;
|
|
setOptions('last-fm', config);
|
|
}
|
|
|
|
if (!config.session_key) {
|
|
// Not authenticated
|
|
config = await getAndSetSessionKey(config);
|
|
}
|
|
|
|
registerCallback((songInfo) => {
|
|
// Set remove the old scrobble timer
|
|
clearTimeout(scrobbleTimer);
|
|
if (!songInfo.isPaused) {
|
|
setNowPlaying(songInfo, config);
|
|
// 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
|
|
const timeToWait = (scrobbleTime - songInfo.elapsedSeconds) * 1000;
|
|
scrobbleTimer = setTimeout(addScrobble, timeToWait, songInfo, config);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
module.exports = lastfm;
|