2021-10-19 09:22:31 +02:00
|
|
|
const express = require('express');
|
|
|
|
const deezer = require('deezer-js');
|
|
|
|
const deemix = require('deemix');
|
|
|
|
const path = require('path');
|
2021-10-21 18:27:00 +02:00
|
|
|
const { promisify } = require('util');
|
2021-10-19 09:22:31 +02:00
|
|
|
const fs = require('fs');
|
2021-10-20 19:17:28 +02:00
|
|
|
const { exec } = require('child_process');
|
2021-10-20 20:08:41 +02:00
|
|
|
const timeago = require('timeago.js');
|
2021-10-21 18:27:00 +02:00
|
|
|
const toml = require('toml');
|
2021-10-19 09:22:31 +02:00
|
|
|
|
2021-10-21 18:27:00 +02:00
|
|
|
if (!fs.existsSync('./config.toml')) {
|
|
|
|
if (!fs.existsSync('./config.example.toml')) {
|
|
|
|
console.error('!! no config.toml OR config.example.toml found!!! what the hell are you up to!!!');
|
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
console.log('! copying config.example.toml to config.toml as it was not found. the default config may not be preferable!');
|
|
|
|
fs.copyFileSync('./config.example.toml', './config.toml');
|
|
|
|
}
|
|
|
|
const config = toml.parse(fs.readFileSync('./config.toml'));
|
|
|
|
console.log('loaded config');
|
|
|
|
|
|
|
|
const port = config.server.port || 4500;
|
|
|
|
const deleteTimer = config.timer.deleteTimer || 1000 * 60 * 25;
|
2021-10-19 09:22:31 +02:00
|
|
|
|
|
|
|
require('dotenv').config();
|
|
|
|
|
|
|
|
const app = new express();
|
|
|
|
const expressWs = require('express-ws')(app);
|
|
|
|
const deezerInstance = new deezer.Deezer();
|
|
|
|
let deemixDownloader;
|
|
|
|
|
|
|
|
let deemixSettings = deemix.settings.DEFAULTS
|
|
|
|
deemixSettings.downloadLocation = path.join(process.cwd(), 'data/');
|
2021-10-21 18:27:00 +02:00
|
|
|
deemixSettings.overwriteFile = deemix.settings.OverwriteOption.ONLY_TAGS;
|
|
|
|
|
|
|
|
deemixSettings.maxBitrate = String(deezer.TrackFormats[config.deemix.trackFormat]);
|
|
|
|
deemixSettings.tracknameTemplate = config.deemix.trackNameTemplate || '%artist% - %title%';
|
|
|
|
deemixSettings.albumTracknameTemplate = config.deemix.albumTrackNameTemplate || '%tracknumber%. %artist% - %title%';
|
|
|
|
deemixSettings.albumNameTemplate = config.deemix.albumNameTemplate || '%artist% - %album%'
|
|
|
|
deemixSettings.createM3U8File = config.deemix.createM3U8File !== undefined ? config.deemix.createM3U8File : false;
|
|
|
|
deemixSettings.embeddedArtworkPNG = config.deemix.embeddedArtworkPNG !== undefined ? config.deemix.embeddedArtworkPNG : true;
|
|
|
|
deemixSettings.embeddedArtworkSize = config.deemix.embeddedArtworkSize || 800;
|
|
|
|
deemixSettings.saveArtwork = config.deemix.saveArtwork !== undefined ? config.deemix.saveArtwork : true;
|
|
|
|
deemixSettings.localArtworkSize = deemixSettings.localArtworkSize || 1200;
|
|
|
|
deemixSettings.localArtworkFormat = deemixSettings.localArtworkFormat || 'jpg';
|
|
|
|
deemixSettings.jpegImageQuality = deemixSettings.jpegImageQuality || 80;
|
|
|
|
deemixSettings.removeDuplicateArtists = config.deemix.removeDuplicateArtists !== undefined ? config.deemix.removeDuplicateArtists : true;
|
2021-10-19 09:22:31 +02:00
|
|
|
|
2021-10-20 20:08:41 +02:00
|
|
|
const toDeleteLocation = './data/toDelete.json';
|
|
|
|
|
|
|
|
if (!fs.existsSync(toDeleteLocation)) fs.writeFileSync(toDeleteLocation, '[]', {encoding: 'utf8'});
|
|
|
|
let toDelete = JSON.parse(fs.readFileSync(toDeleteLocation, {encoding: 'utf8'}));
|
|
|
|
|
|
|
|
function updateQueueFile() {
|
|
|
|
fs.writeFileSync(toDeleteLocation, JSON.stringify(toDelete), {encoding: 'utf8'});
|
|
|
|
}
|
|
|
|
|
|
|
|
function queueDeletion(file) {
|
|
|
|
console.log(`queued deletion of ${file} ${timeago.format(Date.now() + deleteTimer)}`);
|
|
|
|
|
|
|
|
toDelete.push({
|
|
|
|
date: Date.now() + deleteTimer,
|
|
|
|
file
|
|
|
|
});
|
|
|
|
setTimeout(() => {
|
|
|
|
toDelete = toDelete.filter(c => c.file !== file);
|
|
|
|
updateQueueFile();
|
|
|
|
console.log(`deleting queued file ${file}`);
|
|
|
|
try {
|
|
|
|
fs.unlinkSync(file);
|
|
|
|
} catch(err) {
|
|
|
|
console.log(`failed to delete ${file}! is the file already gone?`);
|
|
|
|
}
|
|
|
|
}, deleteTimer);
|
|
|
|
updateQueueFile();
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log(`loaded ${toDelete.length} items in deletion queue`);
|
|
|
|
let updateQueue = false;
|
|
|
|
for (let del of toDelete) {
|
|
|
|
if (Date.now() - del.date >= 0) {
|
2021-10-21 16:49:57 +02:00
|
|
|
toDelete = toDelete.filter(c => c.file !== del.file);
|
2021-10-20 20:08:41 +02:00
|
|
|
console.log(`deleting ${del.file} - was meant to be deleted ${timeago.format(del.date)}`);
|
|
|
|
updateQueue = true;
|
|
|
|
try {
|
|
|
|
fs.unlinkSync(del.file);
|
|
|
|
} catch(err) {
|
|
|
|
console.log(`failed to delete ${del.file}! is the file already gone?`);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
console.log(`queueing deletion of ${del.file} ${timeago.format(del.date)}`);
|
|
|
|
setTimeout(() => {
|
|
|
|
toDelete = toDelete.filter(c => c.file !== del.file);
|
|
|
|
updateQueueFile();
|
|
|
|
try {
|
|
|
|
fs.unlinkSync(del.file);
|
|
|
|
} catch(err) {
|
|
|
|
console.log(`failed to delete ${del.file}! is the file already gone?`);
|
|
|
|
}
|
|
|
|
}, del.date - Date.now());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if (updateQueue) {
|
|
|
|
updateQueueFile();
|
|
|
|
console.log('updated deletion queue json');
|
|
|
|
}
|
|
|
|
|
2021-10-19 18:00:54 +02:00
|
|
|
app.use(express.static('public'));
|
2021-10-19 09:22:31 +02:00
|
|
|
app.use('/data', express.static('data', {extensions: ['flac', 'mp3']}));
|
|
|
|
app.get('/api/search', async (req, res) => {
|
|
|
|
if (!req.query.search) return res.sendStatus(400);
|
|
|
|
|
|
|
|
let s = await deezerInstance.api.search_album(req.query.search, {
|
2021-10-21 18:27:00 +02:00
|
|
|
limit: config.limits.searchLimit || 15,
|
2021-10-19 09:22:31 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
let format = s.data.map(s => {
|
|
|
|
return {
|
|
|
|
id: s.id,
|
|
|
|
title: s.title,
|
|
|
|
cover: s.md5_image,
|
|
|
|
artist: {
|
|
|
|
id: s.artist.id,
|
|
|
|
name: s.artist.name
|
|
|
|
},
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
res.send(format);
|
|
|
|
});
|
|
|
|
|
|
|
|
app.get('/api/album', async (req, res) => {
|
2021-10-19 18:00:54 +02:00
|
|
|
if (!req.query.id) return req.sendStatus(400);
|
2021-10-20 07:26:03 +02:00
|
|
|
let album;
|
|
|
|
try {
|
|
|
|
album = await deezerInstance.api.get_album(req.query.id);
|
|
|
|
} catch (err) {
|
|
|
|
return req.status(404).send('Album not found!');
|
|
|
|
}
|
2021-10-19 18:00:54 +02:00
|
|
|
res.send({
|
|
|
|
id: album.id,
|
|
|
|
title: album.title,
|
|
|
|
link: album.link,
|
|
|
|
tracks: album.tracks.data.map(t => {
|
|
|
|
return {
|
|
|
|
id: t.id,
|
|
|
|
title: t.title,
|
|
|
|
duration: t.duration,
|
|
|
|
link: t.link,
|
|
|
|
artist: t.artist.name,
|
|
|
|
};
|
|
|
|
})
|
|
|
|
});
|
2021-10-19 09:22:31 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
app.ws('/api/album', async (ws, req) => {
|
2021-10-20 07:26:03 +02:00
|
|
|
if (!req.query.id) return ws.close(1008, 'Supply a track ID in the query!');
|
2021-10-19 09:22:31 +02:00
|
|
|
|
2021-10-20 19:17:28 +02:00
|
|
|
let trackpaths = [];
|
|
|
|
|
2021-10-19 09:22:31 +02:00
|
|
|
const listener = {
|
|
|
|
send(key, data) {
|
|
|
|
if (data.downloaded) {
|
2021-10-20 19:17:28 +02:00
|
|
|
// ws.send(JSON.stringify({key: 'download', data: data.downloadPath.replace(process.cwd(), '')}));
|
|
|
|
trackpaths.push(data.downloadPath);
|
2021-10-20 20:08:41 +02:00
|
|
|
queueDeletion(data.downloadPath);
|
2021-10-19 09:22:31 +02:00
|
|
|
}
|
|
|
|
|
2021-10-20 16:41:10 +02:00
|
|
|
if (data.state !== 'tagging' && data.state !== 'getAlbumArt' && data.state !== 'getTags') ws.send(JSON.stringify({key, data}));
|
2021-10-19 23:18:50 +02:00
|
|
|
//console.log(`[${key}] ${inspect(data)}`);
|
2021-10-19 09:22:31 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-10-19 21:30:55 +02:00
|
|
|
let album;
|
|
|
|
try {
|
|
|
|
album = await deezerInstance.api.get_album(req.query.id);
|
|
|
|
} catch(err) {
|
2021-10-20 07:26:03 +02:00
|
|
|
return ws.close(1012, 'Album not found');
|
2021-10-19 21:30:55 +02:00
|
|
|
}
|
2021-10-19 09:22:31 +02:00
|
|
|
|
|
|
|
listener.send('coverArt', album.cover_medium);
|
2021-10-20 16:41:10 +02:00
|
|
|
listener.send('metadata', {id: album.id, title: album.title, artist: album.artist.name});
|
2021-10-19 09:22:31 +02:00
|
|
|
|
|
|
|
let dlObj = await deemix.generateDownloadObject(deezerInstance, 'https://www.deezer.com/album/' + req.query.id, deezer.TrackFormats.FLAC);
|
|
|
|
deemixDownloader = new deemix.downloader.Downloader(deezerInstance, dlObj, deemixSettings, listener);
|
|
|
|
|
|
|
|
await deemixDownloader.start();
|
|
|
|
|
2021-10-20 20:08:41 +02:00
|
|
|
if (trackpaths.length > 1) {
|
|
|
|
await ws.send(JSON.stringify({key: 'zipping'}));
|
2021-10-20 19:39:18 +02:00
|
|
|
|
2021-10-20 20:08:41 +02:00
|
|
|
const folderName = trackpaths[0].split('/').slice(-2)[0];
|
2021-10-20 19:17:28 +02:00
|
|
|
try {
|
2021-10-21 18:27:00 +02:00
|
|
|
await promisify(exec)(`${config.server.zipBinaryLocation} ${config.server.zipArguments} "data/${folderName}.zip" "data/${folderName}"`);
|
2021-10-20 19:17:28 +02:00
|
|
|
} catch(err) {
|
2021-10-20 20:08:41 +02:00
|
|
|
return ws.close(1011, 'Zipping album failed');
|
2021-10-20 19:17:28 +02:00
|
|
|
}
|
2021-10-21 18:27:00 +02:00
|
|
|
|
2021-10-20 20:08:41 +02:00
|
|
|
await ws.send(JSON.stringify({key: 'download', data: `data/${folderName}.zip`}));
|
2021-10-21 18:27:00 +02:00
|
|
|
|
2021-10-20 20:08:41 +02:00
|
|
|
queueDeletion('./data/' + folderName + '.zip');
|
|
|
|
} else {
|
|
|
|
await ws.send(JSON.stringify({key: 'download', data: trackpaths[0].replace(process.cwd(), '')}));
|
|
|
|
}
|
2021-10-20 19:17:28 +02:00
|
|
|
|
2021-10-20 07:26:03 +02:00
|
|
|
ws.close(1000);
|
2021-10-19 09:22:31 +02:00
|
|
|
});
|
|
|
|
|
2021-10-19 21:30:55 +02:00
|
|
|
app.ws('/api/track', async (ws, req) => {
|
2021-10-20 07:26:03 +02:00
|
|
|
if (!req.query.id) return ws.close(1008, 'Supply a track ID in the query!');
|
2021-10-19 21:30:55 +02:00
|
|
|
|
|
|
|
const listener = {
|
|
|
|
send(key, data) {
|
|
|
|
if (data.downloaded) {
|
|
|
|
ws.send(JSON.stringify({key: 'download', data: data.downloadPath.replace(process.cwd(), '')}));
|
2021-10-20 20:08:41 +02:00
|
|
|
queueDeletion(data.downloadPath);
|
2021-10-19 21:30:55 +02:00
|
|
|
}
|
|
|
|
|
2021-10-20 16:41:10 +02:00
|
|
|
if (data.state !== 'tagging' && data.state !== 'getAlbumArt' && data.state !== 'getTags') ws.send(JSON.stringify({key, data}));
|
|
|
|
//console.log(`[${key}] ${inspect(data)}`);
|
2021-10-19 21:30:55 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let track;
|
|
|
|
try {
|
|
|
|
track = await deezerInstance.api.get_track(req.query.id);
|
|
|
|
} catch(err) {
|
2021-10-20 07:26:03 +02:00
|
|
|
return ws.close(1012, 'Track not found');
|
2021-10-19 21:30:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
listener.send('coverArt', track.album.cover_medium);
|
2021-10-20 16:41:10 +02:00
|
|
|
listener.send('metadata', {id: track.id, title: track.title, artist: track.artist.name});
|
2021-10-19 21:30:55 +02:00
|
|
|
|
|
|
|
let dlObj = await deemix.generateDownloadObject(deezerInstance, 'https://www.deezer.com/track/' + req.query.id, deezer.TrackFormats.FLAC);
|
|
|
|
deemixDownloader = new deemix.downloader.Downloader(deezerInstance, dlObj, deemixSettings, listener);
|
|
|
|
|
|
|
|
await deemixDownloader.start();
|
|
|
|
|
2021-10-20 07:26:03 +02:00
|
|
|
ws.close(1000);
|
2021-10-19 21:30:55 +02:00
|
|
|
});
|
|
|
|
|
2021-10-19 09:22:31 +02:00
|
|
|
deezerInstance.login_via_arl(process.env.DEEZER_ARL).then(() => {
|
|
|
|
console.log('logged into deezer');
|
|
|
|
app.listen(port, () => {
|
|
|
|
console.log('hosting on ' + port);
|
|
|
|
});
|
|
|
|
});
|