Download quality fallback, shuffle, repeat, lastfm
This commit is contained in:
@ -61,6 +61,7 @@ class DeezerAPI {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return data.data;
|
||||
}
|
||||
|
||||
|
@ -13,8 +13,8 @@ class Track {
|
||||
this.artistString = this.artists.map((a) => a.name).join(', ');
|
||||
|
||||
this.album = new Album(json);
|
||||
this.trackNumber = json.TRACK_NUMBER;
|
||||
this.diskNumber = json.DISK_NUMBER;
|
||||
this.trackNumber = parseInt((json.TRACK_NUMBER || 0).toString(), 10);
|
||||
this.diskNumber = parseInt((json.DISK_NUMBER || 0).toString(), 10);
|
||||
this.explicit = json['EXPLICIT_LYRICS'] == 1 ? true:false;
|
||||
this.lyricsId = json.LYRICS_ID;
|
||||
|
||||
@ -63,7 +63,7 @@ class Album {
|
||||
//Type
|
||||
this.type = 'album';
|
||||
if (json.TYPE && json.TYPE.toString() == "0") this.type = 'single';
|
||||
if (!json.ARTISTS_ALBUMS_IS_OFFICIAL) this.type = 'featured';
|
||||
if (json.ROLE_ID == 5) this.type = 'featured';
|
||||
|
||||
//Helpers
|
||||
this.artistString = this.artists.map((a) => a.name).join(', ');
|
||||
|
@ -54,7 +54,7 @@ class Downloads {
|
||||
|
||||
generateTrackPath(track, quality) {
|
||||
//Generate filename
|
||||
let fn = this.settings.downloadFilename + (quality == 9 ? '.flac' : '.mp3');
|
||||
let fn = this.settings.downloadFilename;
|
||||
|
||||
//Disable feats for single artist
|
||||
let feats = '';
|
||||
@ -108,12 +108,15 @@ class Downloads {
|
||||
//Save to DB
|
||||
if (this.download) {
|
||||
await new Promise((res, rej) => {
|
||||
// this.db.update({_id: this.download.id}, {state: 3}, (e) => {
|
||||
// res();
|
||||
// });
|
||||
this.db.remove({_id: this.download.id}, (e) => {
|
||||
this.db.update({_id: this.download.id}, {
|
||||
state: this.download.state,
|
||||
fallback: this.download.fallback,
|
||||
}, (e) => {
|
||||
res();
|
||||
});
|
||||
// this.db.remove({_id: this.download.id}, (e) => {
|
||||
// res();
|
||||
// });
|
||||
});
|
||||
}
|
||||
|
||||
@ -144,7 +147,7 @@ class Downloads {
|
||||
if (!docs) return;
|
||||
|
||||
for (let d of docs) {
|
||||
if (d.state < 3) this.downloads.push(Download.fromDB(d, () => {this._downloadDone();}));
|
||||
if (d.state < 3 && d.state >= 0) this.downloads.push(Download.fromDB(d, () => {this._downloadDone();}));
|
||||
//TODO: Ignore for now completed
|
||||
}
|
||||
res();
|
||||
@ -196,7 +199,9 @@ class Download {
|
||||
//1 - downloading
|
||||
//2 - post-processing
|
||||
//3 - done
|
||||
//-1 - download error
|
||||
this.state = 0;
|
||||
this.fallback = false;
|
||||
|
||||
this._request;
|
||||
//Post Processing Promise
|
||||
@ -213,7 +218,8 @@ class Download {
|
||||
path: this.path,
|
||||
quality: this.quality,
|
||||
track: this.track,
|
||||
state: this.state
|
||||
state: this.state,
|
||||
fallback: this.fallback
|
||||
}
|
||||
|
||||
}
|
||||
@ -221,6 +227,7 @@ class Download {
|
||||
//Create download from DB document
|
||||
static fromDB(doc, onDone) {
|
||||
let d = new Download(doc.track, doc.path, doc.quality, onDone);
|
||||
d.fallback = doc.fallback ? true : false; //Null check
|
||||
d.state = doc.state;
|
||||
return d;
|
||||
}
|
||||
@ -240,8 +247,24 @@ class Download {
|
||||
|
||||
//Get download info
|
||||
if (!this.url) this.url = Track.getUrl(this.track.streamUrl, this.quality);
|
||||
|
||||
this._request = https.get(this.url, {headers: {'Range': `bytes=${start}-`}}, (r) => {
|
||||
|
||||
//Error
|
||||
if (r.statusCode >= 400) {
|
||||
//Fallback on error
|
||||
if (this.quality > 1) {
|
||||
if (this.quality == 3) this.quality = 1;
|
||||
if (this.quality == 9) this.quality = 3;
|
||||
this.url = null;
|
||||
this.fallback = true;
|
||||
return this.start();
|
||||
};
|
||||
//Error
|
||||
this.state = -1;
|
||||
console.log(`Undownloadable track ID: ${this.track.id}`);
|
||||
return this.onDone();
|
||||
}
|
||||
|
||||
//On download done
|
||||
r.on('end', () => {
|
||||
if (this.downloaded != this.size) return;
|
||||
@ -288,6 +311,7 @@ class Download {
|
||||
} catch (e) {};
|
||||
|
||||
//Decrypt
|
||||
this.path += (this.quality == 9) ? '.flac' : '.mp3';
|
||||
decryptor.decryptFile(decryptor.getKey(this.track.id), tmp, `${tmp}.DEC`);
|
||||
fs.promises.copyFile(`${tmp}.DEC`, this.path);
|
||||
//Delete encrypted
|
||||
|
@ -3,6 +3,7 @@ const path = require('path');
|
||||
const https = require('https');
|
||||
const fs = require('fs');
|
||||
const axios = require('axios').default;
|
||||
const LastfmAPI = require('lastfmapi');
|
||||
const {DeezerAPI, DeezerDecryptionStream} = require('./deezer');
|
||||
const {Settings} = require('./settings');
|
||||
const {Track, Album, Artist, Playlist, DeezerProfile, SearchResults, DeezerLibrary, DeezerPage, Lyrics} = require('./definitions');
|
||||
@ -21,6 +22,12 @@ app.use(express.static(path.join(__dirname, '../client', 'dist')));
|
||||
//Server
|
||||
const server = require('http').createServer(app);
|
||||
const io = require('socket.io').listen(server);
|
||||
//LastFM
|
||||
//plz don't steal creds, it's just lastfm
|
||||
let lastfm = new LastfmAPI({
|
||||
api_key: 'b6ab5ae967bcd8b10b23f68f42493829',
|
||||
secret: '861b0dff9a8a574bec747f9dab8b82bf'
|
||||
});
|
||||
|
||||
//Get playback info
|
||||
app.get('/playback', async (req, res) => {
|
||||
@ -425,20 +432,58 @@ app.delete('/downloads/:index', async (req, res) => {
|
||||
res.status(200).end();
|
||||
});
|
||||
|
||||
//Log listen to deezer
|
||||
app.put('/log/:id', async (req, res) => {
|
||||
await deezer.callApi('log.listen', {
|
||||
params: {
|
||||
timestamp: Math.floor(new Date() / 1000),
|
||||
ts_listen: Math.floor(new Date() / 1000),
|
||||
type: 1,
|
||||
stat: {seek: 0, pause: 0, sync: 0},
|
||||
media: {id: req.params.id.toString(), type: 'song', format: 'MP3_128'}
|
||||
}
|
||||
});
|
||||
//Log listen to deezer & lastfm
|
||||
app.post('/log', async (req, res) => {
|
||||
//LastFM
|
||||
if (settings.lastFM)
|
||||
lastfm.track.scrobble({
|
||||
artist: req.body.artists[0].name,
|
||||
track: req.body.title,
|
||||
timestamp: Math.floor((new Date()).getTime() / 1000)
|
||||
});
|
||||
|
||||
//Deezer
|
||||
if (settings.logListen)
|
||||
await deezer.callApi('log.listen', {
|
||||
params: {
|
||||
timestamp: Math.floor(new Date() / 1000),
|
||||
ts_listen: Math.floor(new Date() / 1000),
|
||||
type: 1,
|
||||
stat: {seek: 0, pause: 0, sync: 0},
|
||||
media: {id: req.body.id, type: 'song', format: 'MP3_128'}
|
||||
}
|
||||
});
|
||||
res.status(200).end();
|
||||
});
|
||||
|
||||
//Last.FM authorization callback
|
||||
app.get('/lastfm', async (req, res) => {
|
||||
//Got token
|
||||
if (req.query.token) {
|
||||
let token = req.query.token;
|
||||
await new Promise((res, rej) => {
|
||||
lastfm.authenticate(token, (err, sess) => {
|
||||
if (err) res();
|
||||
//Save to settings
|
||||
settings.lastFM = {
|
||||
name: sess.username,
|
||||
key: sess.key
|
||||
};
|
||||
settings.save();
|
||||
res();
|
||||
});
|
||||
});
|
||||
authorizeLastFM();
|
||||
//Redirect to homepage
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
//Get auth url
|
||||
res.json({
|
||||
url: lastfm.getAuthenticationUrl({cb: `http://${req.socket.remoteAddress}:${settings.port}/lastfm`})
|
||||
}).end();
|
||||
});
|
||||
|
||||
//Redirect to index on unknown path
|
||||
app.all('*', (req, res) => {
|
||||
res.redirect('/');
|
||||
@ -490,6 +535,12 @@ async function qualityFallback(info, quality = 3) {
|
||||
}
|
||||
}
|
||||
|
||||
//Autorize lastfm with saved credentials
|
||||
function authorizeLastFM() {
|
||||
if (!settings.lastFM) return;
|
||||
lastfm.setSessionCredentials(settings.lastFM.name, settings.lastFM.key);
|
||||
}
|
||||
|
||||
//ecb = Error callback
|
||||
async function createServer(electron = false, ecb) {
|
||||
//Prepare globals
|
||||
@ -526,6 +577,9 @@ async function createServer(electron = false, ecb) {
|
||||
});
|
||||
}, 350);
|
||||
|
||||
//LastFM
|
||||
authorizeLastFM();
|
||||
|
||||
//Start server
|
||||
server.on('error', (e) => {
|
||||
ecb(e);
|
||||
|
@ -24,6 +24,7 @@ class Settings {
|
||||
this.downloadFilename = '%0trackNumber%. %artists% - %title%';
|
||||
|
||||
this.logListen = false;
|
||||
this.lastFM = null;
|
||||
}
|
||||
|
||||
//Based on electorn app.getPath
|
||||
@ -80,7 +81,8 @@ class Settings {
|
||||
Object.assign(this, JSON.parse(data));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Error loading settings: ${e}. Using defaults.`)
|
||||
console.error(`Error loading settings: ${e}. Using defaults.`);
|
||||
this.save();
|
||||
}
|
||||
this.electron = e;
|
||||
|
||||
|
Reference in New Issue
Block a user