mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-10 18:21:47 +00:00
Added working lastfm support
currently only played songs are added
This commit is contained in:
@ -35,6 +35,12 @@ const defaultConfig = {
|
|||||||
ffmpegArgs: [], // e.g. ["-b:a", "192k"] for an audio bitrate of 192kb/s
|
ffmpegArgs: [], // e.g. ["-b:a", "192k"] for an audio bitrate of 192kb/s
|
||||||
downloadFolder: undefined, // Custom download folder (absolute path)
|
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",
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -66,6 +66,7 @@
|
|||||||
"@ffmpeg/core": "^0.8.5",
|
"@ffmpeg/core": "^0.8.5",
|
||||||
"@ffmpeg/ffmpeg": "^0.9.7",
|
"@ffmpeg/ffmpeg": "^0.9.7",
|
||||||
"YoutubeNonStop": "git://github.com/lawfx/YoutubeNonStop.git#v0.8.1",
|
"YoutubeNonStop": "git://github.com/lawfx/YoutubeNonStop.git#v0.8.1",
|
||||||
|
"axios": "^0.21.1",
|
||||||
"browser-id3-writer": "^4.4.0",
|
"browser-id3-writer": "^4.4.0",
|
||||||
"discord-rpc": "^3.2.0",
|
"discord-rpc": "^3.2.0",
|
||||||
"downloads-folder": "^3.0.1",
|
"downloads-folder": "^3.0.1",
|
||||||
@ -76,7 +77,9 @@
|
|||||||
"electron-unhandled": "^3.0.2",
|
"electron-unhandled": "^3.0.2",
|
||||||
"electron-updater": "^4.3.6",
|
"electron-updater": "^4.3.6",
|
||||||
"filenamify": "^4.2.0",
|
"filenamify": "^4.2.0",
|
||||||
|
"md5": "^2.3.0",
|
||||||
"node-fetch": "^2.6.1",
|
"node-fetch": "^2.6.1",
|
||||||
|
"open": "^8.0.3",
|
||||||
"ytdl-core": "^4.4.5"
|
"ytdl-core": "^4.4.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -45,9 +45,6 @@ module.exports = (win) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Startup the rpc client
|
// Startup the rpc client
|
||||||
rpc.login({
|
rpc.login({ clientId }).catch(console.error);
|
||||||
clientId,
|
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
135
plugins/last-fm/back.js
Normal file
135
plugins/last-fm/back.js
Normal file
@ -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;
|
||||||
Reference in New Issue
Block a user