ts migration wee
This commit is contained in:
parent
e5b48778f1
commit
24c097e2b1
13
package.json
13
package.json
|
@ -2,9 +2,12 @@
|
||||||
"name": "deemix-web-frontend",
|
"name": "deemix-web-frontend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "a dumb frontend for just getting some got damned songs",
|
"description": "a dumb frontend for just getting some got damned songs",
|
||||||
"main": "src/index.js",
|
"main": "dist/index.js",
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"quickrun": "tsc && node dist/index.js",
|
||||||
|
"build": "tsc"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -30,5 +33,11 @@
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"bufferutil": "^4.0.5",
|
"bufferutil": "^4.0.5",
|
||||||
"utf-8-validate": "^5.0.7"
|
"utf-8-validate": "^5.0.7"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^4.4.4",
|
||||||
|
"@types/express": "^4.17.13",
|
||||||
|
"@types/express-ws": "^3.0.1",
|
||||||
|
"@types/ws": "^8.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
lockfileVersion: 5.3
|
lockfileVersion: 5.3
|
||||||
|
|
||||||
specifiers:
|
specifiers:
|
||||||
|
'@types/express': ^4.17.13
|
||||||
|
'@types/express-ws': ^3.0.1
|
||||||
|
'@types/ws': ^8.2.0
|
||||||
bufferutil: ^4.0.5
|
bufferutil: ^4.0.5
|
||||||
deemix: git+https://git.freezer.life/RemixDev/deemix-js
|
deemix: git+https://git.freezer.life/RemixDev/deemix-js
|
||||||
deezer-js: ^1.2.4
|
deezer-js: ^1.2.4
|
||||||
|
@ -9,6 +12,7 @@ specifiers:
|
||||||
express-ws: ^5.0.2
|
express-ws: ^5.0.2
|
||||||
timeago.js: ^4.0.2
|
timeago.js: ^4.0.2
|
||||||
toml: ^3.0.0
|
toml: ^3.0.0
|
||||||
|
typescript: ^4.4.4
|
||||||
utf-8-validate: ^5.0.7
|
utf-8-validate: ^5.0.7
|
||||||
winston: ^3.3.3
|
winston: ^3.3.3
|
||||||
ws: ^8.2.3
|
ws: ^8.2.3
|
||||||
|
@ -28,6 +32,12 @@ optionalDependencies:
|
||||||
bufferutil: 4.0.5
|
bufferutil: 4.0.5
|
||||||
utf-8-validate: 5.0.7
|
utf-8-validate: 5.0.7
|
||||||
|
|
||||||
|
devDependencies:
|
||||||
|
'@types/express': 4.17.13
|
||||||
|
'@types/express-ws': 3.0.1
|
||||||
|
'@types/ws': 8.2.0
|
||||||
|
typescript: 4.4.4
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
/@dabh/diagnostics/2.0.2:
|
/@dabh/diagnostics/2.0.2:
|
||||||
|
@ -50,6 +60,13 @@ packages:
|
||||||
defer-to-connect: 2.0.1
|
defer-to-connect: 2.0.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@types/body-parser/1.19.1:
|
||||||
|
resolution: {integrity: sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==}
|
||||||
|
dependencies:
|
||||||
|
'@types/connect': 3.4.35
|
||||||
|
'@types/node': 16.11.4
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/cacheable-request/6.0.2:
|
/@types/cacheable-request/6.0.2:
|
||||||
resolution: {integrity: sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==}
|
resolution: {integrity: sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -59,6 +76,37 @@ packages:
|
||||||
'@types/responselike': 1.0.0
|
'@types/responselike': 1.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@types/connect/3.4.35:
|
||||||
|
resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 16.11.4
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@types/express-serve-static-core/4.17.24:
|
||||||
|
resolution: {integrity: sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==}
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 16.11.4
|
||||||
|
'@types/qs': 6.9.7
|
||||||
|
'@types/range-parser': 1.2.4
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@types/express-ws/3.0.1:
|
||||||
|
resolution: {integrity: sha512-VguRXzcpPBF0IggIGpUoM65cZJDfMQxoc6dKoCz1yLzcwcXW7ft60yhq3ygKhyEhEIQFtLrWjyz4AJ1qjmzCFw==}
|
||||||
|
dependencies:
|
||||||
|
'@types/express': 4.17.13
|
||||||
|
'@types/express-serve-static-core': 4.17.24
|
||||||
|
'@types/ws': 8.2.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@types/express/4.17.13:
|
||||||
|
resolution: {integrity: sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==}
|
||||||
|
dependencies:
|
||||||
|
'@types/body-parser': 1.19.1
|
||||||
|
'@types/express-serve-static-core': 4.17.24
|
||||||
|
'@types/qs': 6.9.7
|
||||||
|
'@types/serve-static': 1.13.10
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/http-cache-semantics/4.0.1:
|
/@types/http-cache-semantics/4.0.1:
|
||||||
resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==}
|
resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -69,9 +117,20 @@ packages:
|
||||||
'@types/node': 16.11.4
|
'@types/node': 16.11.4
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@types/mime/1.3.2:
|
||||||
|
resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/node/16.11.4:
|
/@types/node/16.11.4:
|
||||||
resolution: {integrity: sha512-TMgXmy0v2xWyuCSCJM6NCna2snndD8yvQF67J29ipdzMcsPa9u+o0tjF5+EQNdhcuZplYuouYqpc4zcd5I6amQ==}
|
resolution: {integrity: sha512-TMgXmy0v2xWyuCSCJM6NCna2snndD8yvQF67J29ipdzMcsPa9u+o0tjF5+EQNdhcuZplYuouYqpc4zcd5I6amQ==}
|
||||||
dev: false
|
|
||||||
|
/@types/qs/6.9.7:
|
||||||
|
resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@types/range-parser/1.2.4:
|
||||||
|
resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/responselike/1.0.0:
|
/@types/responselike/1.0.0:
|
||||||
resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
|
resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
|
||||||
|
@ -79,6 +138,19 @@ packages:
|
||||||
'@types/node': 16.11.4
|
'@types/node': 16.11.4
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@types/serve-static/1.13.10:
|
||||||
|
resolution: {integrity: sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==}
|
||||||
|
dependencies:
|
||||||
|
'@types/mime': 1.3.2
|
||||||
|
'@types/node': 16.11.4
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@types/ws/8.2.0:
|
||||||
|
resolution: {integrity: sha512-cyeefcUCgJlEk+hk2h3N+MqKKsPViQgF5boi9TTHSK+PoR9KWBb/C5ccPcDyAqgsbAYHTwulch725DV84+pSpg==}
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 16.11.4
|
||||||
|
dev: true
|
||||||
|
|
||||||
/accepts/1.3.7:
|
/accepts/1.3.7:
|
||||||
resolution: {integrity: sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==}
|
resolution: {integrity: sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
@ -993,6 +1065,12 @@ packages:
|
||||||
mime-types: 2.1.33
|
mime-types: 2.1.33
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/typescript/4.4.4:
|
||||||
|
resolution: {integrity: sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==}
|
||||||
|
engines: {node: '>=4.2.0'}
|
||||||
|
hasBin: true
|
||||||
|
dev: true
|
||||||
|
|
||||||
/universalify/0.1.2:
|
/universalify/0.1.2:
|
||||||
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
|
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
|
||||||
engines: {node: '>= 4.0.0'}
|
engines: {node: '>= 4.0.0'}
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
const express = require('express');
|
import express from 'express';
|
||||||
const deezer = require('deezer-js');
|
import deezer from 'deezer-js';
|
||||||
const deemix = require('deemix');
|
import deemix from 'deemix';
|
||||||
const path = require('path');
|
import path from 'path';
|
||||||
const { promisify } = require('util');
|
import { promisify } from 'util';
|
||||||
const fs = require('fs');
|
import fs from 'fs';
|
||||||
const { exec } = require('child_process');
|
import { exec } from 'child_process';
|
||||||
const timeago = require('timeago.js');
|
import timeago from 'timeago.js';
|
||||||
const toml = require('toml');
|
import toml from 'toml';
|
||||||
const winston = require('winston');
|
import winston from 'winston';
|
||||||
|
import WebSocket from 'ws';
|
||||||
|
import * as dotenv from 'dotenv';
|
||||||
|
import expressws from 'express-ws';
|
||||||
|
|
||||||
const logFormatter = winston.format.printf(({ level, message, timestamp }) => {
|
const logFormatter = winston.format.printf(({ level, message, timestamp }) => {
|
||||||
return `${new Date(timestamp).toLocaleDateString('en-GB', {timeZone: 'UTC'})} ${new Date(timestamp).toLocaleTimeString('en-GB', {timeZone: 'UTC'})} ${level.replace('info', 'I').replace('warn', '!').replace('error', '!!')}: ${message}`;
|
return `${new Date(timestamp).toLocaleDateString('en-GB', {timeZone: 'UTC'})} ${new Date(timestamp).toLocaleTimeString('en-GB', {timeZone: 'UTC'})} ${level.replace('info', 'I').replace('warn', '!').replace('error', '!!')}: ${message}`;
|
||||||
|
@ -49,24 +52,24 @@ if (!fs.existsSync('./config.toml')) {
|
||||||
logger.warn('copying config.example.toml to config.toml as it was not found. the default config may not be preferable!');
|
logger.warn('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');
|
fs.copyFileSync('./config.example.toml', './config.toml');
|
||||||
}
|
}
|
||||||
const config = toml.parse(fs.readFileSync('./config.toml'));
|
const config = toml.parse(fs.readFileSync('./config.toml', {encoding: 'utf8'}));
|
||||||
logger.info('loaded config');
|
logger.info('loaded config');
|
||||||
|
|
||||||
let searchcache = {};
|
let searchcache: Record<string, [Album]> = {};
|
||||||
let albumcache = {};
|
let albumcache: Record<string, Album> = {};
|
||||||
let trackcache = {};
|
let trackcache: Record<string, Track> = {};
|
||||||
|
|
||||||
const port = config.server.port || 4500;
|
const port = config.server.port || 4500;
|
||||||
const deleteTimer = config.timer.deleteTimer || 1000 * 60 * 25;
|
const deleteTimer = config.timer.deleteTimer || 1000 * 60 * 25;
|
||||||
|
|
||||||
require('dotenv').config();
|
dotenv.config();
|
||||||
|
|
||||||
const app = new express();
|
const app = express();
|
||||||
const expressWs = require('express-ws')(app);
|
expressws(app);
|
||||||
const deezerInstance = new deezer.Deezer();
|
const deezerInstance = new deezer.Deezer();
|
||||||
let deemixDownloader;
|
let deemixDownloader;
|
||||||
|
|
||||||
let deemixSettings = deemix.settings.DEFAULTS
|
let deemixSettings = deemix.settings.DEFAULTS;
|
||||||
deemixSettings.downloadLocation = path.join(process.cwd(), 'data/');
|
deemixSettings.downloadLocation = path.join(process.cwd(), 'data/');
|
||||||
deemixSettings.overwriteFile = deemix.settings.OverwriteOption.ONLY_TAGS;
|
deemixSettings.overwriteFile = deemix.settings.OverwriteOption.ONLY_TAGS;
|
||||||
|
|
||||||
|
@ -84,10 +87,15 @@ deemixSettings.localArtworkFormat = deemixSettings.localArtworkFormat || deemixS
|
||||||
deemixSettings.jpegImageQuality = deemixSettings.jpegImageQuality || deemixSettings.jpegImageQuality;
|
deemixSettings.jpegImageQuality = deemixSettings.jpegImageQuality || deemixSettings.jpegImageQuality;
|
||||||
deemixSettings.removeDuplicateArtists = config.deemix.removeDuplicateArtists !== undefined ? config.deemix.removeDuplicateArtists : deemixSettings.removeDuplicateArtists;
|
deemixSettings.removeDuplicateArtists = config.deemix.removeDuplicateArtists !== undefined ? config.deemix.removeDuplicateArtists : deemixSettings.removeDuplicateArtists;
|
||||||
|
|
||||||
|
interface QueuedFile {
|
||||||
|
file: string,
|
||||||
|
date: number
|
||||||
|
}
|
||||||
|
|
||||||
const toDeleteLocation = './data/toDelete.json';
|
const toDeleteLocation = './data/toDelete.json';
|
||||||
|
|
||||||
if (!fs.existsSync(toDeleteLocation)) fs.writeFileSync(toDeleteLocation, '[]', {encoding: 'utf8'});
|
if (!fs.existsSync(toDeleteLocation)) fs.writeFileSync(toDeleteLocation, '[]', {encoding: 'utf8'});
|
||||||
let toDelete = JSON.parse(fs.readFileSync(toDeleteLocation, {encoding: 'utf8'}));
|
let toDelete: QueuedFile[] = JSON.parse(fs.readFileSync(toDeleteLocation, {encoding: 'utf8'}));
|
||||||
|
|
||||||
function updateQueueFile() {
|
function updateQueueFile() {
|
||||||
try {
|
try {
|
||||||
|
@ -97,8 +105,8 @@ function updateQueueFile() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteQueuedFile(file) {
|
function deleteQueuedFile(file: string) {
|
||||||
toDelete = toDelete.filter(c => c.file !== file);
|
toDelete = toDelete.filter((c: QueuedFile) => c.file !== file);
|
||||||
updateQueueFile();
|
updateQueueFile();
|
||||||
fs.unlink(file, (err) => {
|
fs.unlink(file, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -109,7 +117,7 @@ function deleteQueuedFile(file) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function queueDeletion(file) {
|
function queueDeletion(file: string) {
|
||||||
logger.info(`queued deletion of ${file} ${timeago.format(Date.now() + deleteTimer)}`);
|
logger.info(`queued deletion of ${file} ${timeago.format(Date.now() + deleteTimer)}`);
|
||||||
|
|
||||||
toDelete.push({
|
toDelete.push({
|
||||||
|
@ -158,18 +166,21 @@ app.use('/data', express.static('data', {extensions: ['flac', 'mp3']}));
|
||||||
|
|
||||||
app.get('/api/search', async (req, res) => {
|
app.get('/api/search', async (req, res) => {
|
||||||
if (!req.query.search) return res.sendStatus(400);
|
if (!req.query.search) return res.sendStatus(400);
|
||||||
let s;
|
if (Array.isArray(req.query.search)) req.query.search = req.query.search.join('');
|
||||||
|
req.query.search = req.query.search as string;
|
||||||
|
|
||||||
|
let s: [Album];
|
||||||
try {
|
try {
|
||||||
s = searchcache[req.query.search] || (await deezerInstance.api.search_album(req.query.search, {
|
s = searchcache[req.query.search] || (await deezerInstance.api.search_album(req.query.search, {
|
||||||
limit: config.limits.searchLimit || 15,
|
limit: config.limits.searchLimit || 15,
|
||||||
}));
|
})).data;
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
logger.error(err.toString());
|
logger.error((err as Error).toString());
|
||||||
res.sendStatus(500);
|
return res.sendStatus(500);
|
||||||
}
|
}
|
||||||
if (!searchcache[req.query.search]) searchcache[req.query.search] = s;
|
if (!searchcache[req.query.search]) searchcache[req.query.search] = s;
|
||||||
|
|
||||||
let format = s.data.map(s => {
|
let format = s.map(s => {
|
||||||
return {
|
return {
|
||||||
id: s.id,
|
id: s.id,
|
||||||
title: s.title,
|
title: s.title,
|
||||||
|
@ -185,15 +196,19 @@ app.get('/api/search', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/album', async (req, res) => {
|
app.get('/api/album', async (req, res) => {
|
||||||
if (!req.query.id) return req.sendStatus(400);
|
if (!req.query.id) return res.sendStatus(400);
|
||||||
let album;
|
if (Array.isArray(req.query.id)) req.query.id = req.query.id.join('');
|
||||||
|
req.query.id = req.query.id as string;
|
||||||
|
|
||||||
|
let album: Album;
|
||||||
try {
|
try {
|
||||||
album = albumcache[req.query.id] || (await deezerInstance.api.get_album(req.query.id));
|
album = albumcache[req.query.id] || (await deezerInstance.api.get_album(req.query.id));
|
||||||
if (!albumcache[req.query.id]) albumcache[req.query.id] = album;
|
if (!albumcache[req.query.id]) albumcache[req.query.id] = album;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err.toString());
|
logger.error((err as Error).toString());
|
||||||
return req.status(404).send('Album not found!');
|
return res.status(404).send('Album not found!');
|
||||||
}
|
}
|
||||||
|
|
||||||
res.send({
|
res.send({
|
||||||
id: album.id,
|
id: album.id,
|
||||||
title: album.title,
|
title: album.title,
|
||||||
|
@ -210,11 +225,17 @@ app.get('/api/album', async (req, res) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
async function deemixDownloadWrapper(dlObj, ws, coverArt, metadata) {
|
interface Metadata {
|
||||||
let trackpaths = [];
|
id: number,
|
||||||
|
title: string,
|
||||||
|
artist: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deemixDownloadWrapper(dlObj: deemix.types.downloadObjects.IDownloadObject, ws: WebSocket, coverArt: string, metadata: Metadata) {
|
||||||
|
let trackpaths: string[] = [];
|
||||||
|
|
||||||
const listener = {
|
const listener = {
|
||||||
send(key, data) {
|
send(key: any, data: any) {
|
||||||
if (data.downloaded) {
|
if (data.downloaded) {
|
||||||
trackpaths.push(data.downloadPath);
|
trackpaths.push(data.downloadPath);
|
||||||
queueDeletion(data.downloadPath);
|
queueDeletion(data.downloadPath);
|
||||||
|
@ -231,7 +252,7 @@ async function deemixDownloadWrapper(dlObj, ws, coverArt, metadata) {
|
||||||
try {
|
try {
|
||||||
await deemixDownloader.start();
|
await deemixDownloader.start();
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
logger.warn(err.toString());
|
logger.warn((err as Error).toString());
|
||||||
logger.warn('(this may be deemix just being whiny)');
|
logger.warn('(this may be deemix just being whiny)');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,7 +270,7 @@ async function deemixDownloadWrapper(dlObj, ws, coverArt, metadata) {
|
||||||
try {
|
try {
|
||||||
await promisify(exec)(`${config.server.zipBinaryLocation} ${config.server.zipArguments} "data/${folderName}.zip" "data/${folderName}"`);
|
await promisify(exec)(`${config.server.zipBinaryLocation} ${config.server.zipArguments} "data/${folderName}.zip" "data/${folderName}"`);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
logger.error(err.toString());
|
logger.error((err as Error).toString());
|
||||||
return ws.close(1011, 'Zipping album failed');
|
return ws.close(1011, 'Zipping album failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,7 +282,7 @@ async function deemixDownloadWrapper(dlObj, ws, coverArt, metadata) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app.ws('/api/album', async (ws, req) => {
|
app.ws('/api/album', async (ws, req: any) => {
|
||||||
if (!req.query.id) return ws.close(1008, 'Supply a track ID in the query!');
|
if (!req.query.id) return ws.close(1008, 'Supply a track ID in the query!');
|
||||||
|
|
||||||
const dlObj = await deemix.generateDownloadObject(deezerInstance, 'https://www.deezer.com/album/' + req.query.id, format);
|
const dlObj = await deemix.generateDownloadObject(deezerInstance, 'https://www.deezer.com/album/' + req.query.id, format);
|
||||||
|
@ -278,7 +299,7 @@ app.ws('/api/album', async (ws, req) => {
|
||||||
album = albumcache[req.query.id] || (await deezerInstance.api.get_album(req.query.id));
|
album = albumcache[req.query.id] || (await deezerInstance.api.get_album(req.query.id));
|
||||||
if (!albumcache[req.query.id]) albumcache[req.query.id] = album;
|
if (!albumcache[req.query.id]) albumcache[req.query.id] = album;
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
logger.error(err.toString());
|
logger.error((err as Error).toString());
|
||||||
return ws.close(1012, 'Album not found');
|
return ws.close(1012, 'Album not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,13 +310,13 @@ app.ws('/api/album', async (ws, req) => {
|
||||||
ws.close(1000);
|
ws.close(1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.ws('/api/track', async (ws, req) => {
|
app.ws('/api/track', async (ws, req: any) => {
|
||||||
if (!req.query.id) return ws.close(1008, 'Supply a track ID in the query!');
|
if (!req.query.id) return ws.close(1008, 'Supply a track ID in the query!');
|
||||||
|
|
||||||
const dlObj = await deemix.generateDownloadObject(deezerInstance, 'https://www.deezer.com/track/' + req.query.id, format);
|
const dlObj = await deemix.generateDownloadObject(deezerInstance, 'https://www.deezer.com/track/' + req.query.id, format);
|
||||||
let isDone = false;
|
let isDone = false;
|
||||||
|
|
||||||
ws.on('close', (code) => {
|
ws.on('close', (code: number) => {
|
||||||
if (isDone) return;
|
if (isDone) return;
|
||||||
dlObj.isCanceled = true;
|
dlObj.isCanceled = true;
|
||||||
logger.debug(`client left unexpectedly with code ${code}; cancelling download`);
|
logger.debug(`client left unexpectedly with code ${code}; cancelling download`);
|
||||||
|
@ -306,7 +327,7 @@ app.ws('/api/track', async (ws, req) => {
|
||||||
track = trackcache[req.query.id] || (await deezerInstance.api.get_track(req.query.id));
|
track = trackcache[req.query.id] || (await deezerInstance.api.get_track(req.query.id));
|
||||||
if (!trackcache[req.query.id]) trackcache[req.query.id] = track;
|
if (!trackcache[req.query.id]) trackcache[req.query.id] = track;
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
logger.error(err.toString());
|
logger.error((err as Error).toString());
|
||||||
return ws.close(1012, 'Track not found');
|
return ws.close(1012, 'Track not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,7 +338,7 @@ app.ws('/api/track', async (ws, req) => {
|
||||||
ws.close(1000);
|
ws.close(1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
deezerInstance.login_via_arl(process.env.DEEZER_ARL).then(() => {
|
deezerInstance.login_via_arl(process.env.DEEZER_ARL || '').then(() => {
|
||||||
logger.info('logged into deezer');
|
logger.info('logged into deezer');
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
logger.info(`hosting on http://localhost:${port} and wss://localhost:${port}`);
|
logger.info(`hosting on http://localhost:${port} and wss://localhost:${port}`);
|
|
@ -0,0 +1,129 @@
|
||||||
|
interface Listener {
|
||||||
|
send(key: any, data: any): void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TagOptions {
|
||||||
|
title: boolean,
|
||||||
|
artist: boolean,
|
||||||
|
album: boolean,
|
||||||
|
cover: boolean,
|
||||||
|
trackNumber: boolean,
|
||||||
|
trackTotal: boolean,
|
||||||
|
discNumber: boolean,
|
||||||
|
discTotal: boolean,
|
||||||
|
albumArtist: boolean,
|
||||||
|
genre: boolean,
|
||||||
|
year: boolean,
|
||||||
|
date: boolean,
|
||||||
|
explicit: boolean,
|
||||||
|
isrc: boolean,
|
||||||
|
length: boolean,
|
||||||
|
barcode: boolean,
|
||||||
|
bpm: boolean,
|
||||||
|
replayGain: boolean,
|
||||||
|
label: boolean,
|
||||||
|
lyrics: boolean,
|
||||||
|
syncedLyrics: boolean,
|
||||||
|
copyright: boolean,
|
||||||
|
composer: boolean,
|
||||||
|
involvedPeople: boolean,
|
||||||
|
source: boolean,
|
||||||
|
rating: boolean,
|
||||||
|
savePlaylistAsCompilation: boolean,
|
||||||
|
useNullSeparator: boolean,
|
||||||
|
saveID3v1: boolean,
|
||||||
|
multiArtistSeparator: string,
|
||||||
|
singleAlbumArtist: boolean,
|
||||||
|
coverDescriptionUTF8: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeemixSettings {
|
||||||
|
downloadLocation: string,
|
||||||
|
tracknameTemplate: string,
|
||||||
|
albumTracknameTemplate: string,
|
||||||
|
playlistTracknameTemplate: string,
|
||||||
|
createPlaylistFolder: boolean,
|
||||||
|
playlistNameTemplate: string,
|
||||||
|
createArtistFolder: boolean,
|
||||||
|
artistNameTemplate: string,
|
||||||
|
createAlbumFolder: boolean,
|
||||||
|
albumNameTemplate: string,
|
||||||
|
createCDFolder: boolean,
|
||||||
|
createStructurePlaylist: boolean,
|
||||||
|
createSingleFolder: boolean,
|
||||||
|
padTracks: boolean,
|
||||||
|
paddingSize: string,
|
||||||
|
illegalCharacterReplacer: string,
|
||||||
|
queueConcurrency: number,
|
||||||
|
maxBitrate: keyof TrackFormats,
|
||||||
|
fallbackBitrate: boolean,
|
||||||
|
fallbackSearch: boolean,
|
||||||
|
logErrors: boolean,
|
||||||
|
logSearched: boolean,
|
||||||
|
overwriteFile: OverwriteOption,
|
||||||
|
createM3U8File: boolean,
|
||||||
|
playlistFilenameTemplate: string,
|
||||||
|
syncedLyrics: boolean,
|
||||||
|
embeddedArtworkSize: number,
|
||||||
|
embeddedArtworkPNG: boolean,
|
||||||
|
localArtworkSize: number,
|
||||||
|
localArtworkFormat: string,
|
||||||
|
saveArtwork: boolean,
|
||||||
|
coverImageTemplate: string,
|
||||||
|
saveArtworkArtist: boolean,
|
||||||
|
artistImageTemplate: string,
|
||||||
|
jpegImageQuality: number,
|
||||||
|
dateFormat: string,
|
||||||
|
albumVariousArtists: boolean,
|
||||||
|
removeAlbumVersion: boolean,
|
||||||
|
removeDuplicateArtists: boolean,
|
||||||
|
featuredToTitle: FeaturesOption,
|
||||||
|
titleCasing: string,
|
||||||
|
artistCasing: string,
|
||||||
|
executeCommand: string,
|
||||||
|
tags: TagOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'deemix' {
|
||||||
|
function generateDownloadObject(dz: Deezer, link: string, bitrate: TrackFormats, plugins?: any, listener?: Listener): Promise<IDownloadObject>
|
||||||
|
|
||||||
|
module downloader {
|
||||||
|
class Downloader {
|
||||||
|
dz: Deezer;
|
||||||
|
|
||||||
|
constructor(dz: Deezer, downloadObject: IDownloadObject, settings: DeemixSettings, listener: Listener): void
|
||||||
|
|
||||||
|
start(): Promise<void>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module types {
|
||||||
|
module downloadObjects {
|
||||||
|
class IDownloadObject {
|
||||||
|
isCanceled: boolean;
|
||||||
|
|
||||||
|
constructor(obj: any): void;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module settings {
|
||||||
|
enum OverwriteOption {
|
||||||
|
OVERWRITE = 'y', // Yes, overwrite the file
|
||||||
|
DONT_OVERWRITE = 'n', // No, don't overwrite the file
|
||||||
|
DONT_CHECK_EXT = 'e', // No, and don't check for extensions
|
||||||
|
KEEP_BOTH = 'b', // No, and keep both files
|
||||||
|
ONLY_TAGS = 't', // Overwrite only the tags
|
||||||
|
}
|
||||||
|
|
||||||
|
// What should I do with featured artists?
|
||||||
|
enum FeaturesOption {
|
||||||
|
NO_CHANGE = "0", // Do nothing
|
||||||
|
REMOVE_TITLE = "1", // Remove from track title
|
||||||
|
REMOVE_TITLE_ALBUM = "3", // Remove from track title and album title
|
||||||
|
MOVE_TITLE = "2" // Move to track title
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULTS: DeemixSettings
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,217 @@
|
||||||
|
type DeezerResponse<Data> = {
|
||||||
|
data: Data,
|
||||||
|
total: number,
|
||||||
|
next: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ExplicitContent {
|
||||||
|
NOT_EXPLICIT,
|
||||||
|
EXPLICIT,
|
||||||
|
UNKNOWN,
|
||||||
|
EDITED,
|
||||||
|
PARTIALLY_EXPLICIT,
|
||||||
|
PARTIALLY_UNKNOWN,
|
||||||
|
NO_ADVICE_AVAILABLE,
|
||||||
|
PARTIALLY_NO_ADVICE_AVAILABLE,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SearchOrder {
|
||||||
|
RANKING = "RANKING",
|
||||||
|
TRACK_ASC = "TRACK_ASC",
|
||||||
|
TRACK_DESC = "TRACK_DESC",
|
||||||
|
ARTIST_ASC = "ARTIST_ASC",
|
||||||
|
ARTIST_DESC = "ARTIST_DESC",
|
||||||
|
ALBUM_ASC = "ALBUM_ASC",
|
||||||
|
ALBUM_DESC = "ALBUM_DESC",
|
||||||
|
RATING_ASC = "RATING_ASC",
|
||||||
|
RATING_DESC = "RATING_DESC",
|
||||||
|
DURATION_ASC = "DURATION_ASC",
|
||||||
|
DURATION_DESC = "DURATION_DESC"
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Track {
|
||||||
|
id: number,
|
||||||
|
readable: boolean,
|
||||||
|
title: string,
|
||||||
|
title_short: string,
|
||||||
|
title_version: string,
|
||||||
|
isrc: string,
|
||||||
|
link: string,
|
||||||
|
share: string,
|
||||||
|
duration: number,
|
||||||
|
track_position: number,
|
||||||
|
disk_number: number,
|
||||||
|
rank: number,
|
||||||
|
release_date: string,
|
||||||
|
explicit_lyrics: boolean,
|
||||||
|
explicit_content_lyrics: ExplicitContent,
|
||||||
|
explicit_content_cover: ExplicitContent,
|
||||||
|
preview: string,
|
||||||
|
bpm: number,
|
||||||
|
gain: number,
|
||||||
|
available_countries: string[],
|
||||||
|
contributors: Contributor[],
|
||||||
|
md5_image: string,
|
||||||
|
artist: Pick<Artist, "id" | "name" | "link" | "share" | "picture" | "picture_small" | "picture_medium" | "picture_big" | "picture_xl" | "radio" | "tracklist">,
|
||||||
|
album: Pick<Album, "id" | "title" | "link" | "cover" | "cover_small" | "cover_medium" | "cover_big" | "cover_xl" | "md5_image" | "release_date" | "tracklist">,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Artist {
|
||||||
|
id: number,
|
||||||
|
name: string,
|
||||||
|
link: string,
|
||||||
|
share: string,
|
||||||
|
picture: string,
|
||||||
|
picture_small: string,
|
||||||
|
picture_medium: string,
|
||||||
|
picture_big: string,
|
||||||
|
picture_xl: string,
|
||||||
|
nb_album: number,
|
||||||
|
nb_fan: number,
|
||||||
|
radio: boolean,
|
||||||
|
tracklist: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Contributor {
|
||||||
|
id: number,
|
||||||
|
name: string,
|
||||||
|
link: string,
|
||||||
|
share: string,
|
||||||
|
picture: string,
|
||||||
|
picture_small: string,
|
||||||
|
picture_medium: string,
|
||||||
|
picture_big: string,
|
||||||
|
picture_xl: string,
|
||||||
|
radio: boolean,
|
||||||
|
tracklist: string,
|
||||||
|
role: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Album {
|
||||||
|
id: number,
|
||||||
|
title: string,
|
||||||
|
upc: string,
|
||||||
|
link: string,
|
||||||
|
share: string,
|
||||||
|
cover: string,
|
||||||
|
cover_small: string,
|
||||||
|
cover_medium: string,
|
||||||
|
cover_big: string,
|
||||||
|
cover_xl: string,
|
||||||
|
md5_image: string,
|
||||||
|
genre_id: number, // use an enum?
|
||||||
|
genres: any,
|
||||||
|
label: string,
|
||||||
|
nb_tracks: number,
|
||||||
|
duration: number,
|
||||||
|
fans: number,
|
||||||
|
release_date: string,
|
||||||
|
record_type: string,
|
||||||
|
available: boolean,
|
||||||
|
tracklist: string,
|
||||||
|
explicit_lyrics: boolean,
|
||||||
|
explicit_content_lyrics: ExplicitContent,
|
||||||
|
explicit_content_cover: ExplicitContent,
|
||||||
|
contributors: Contributor[],
|
||||||
|
artist: Pick<Artist, "id" | "name" | "picture" | "picture_small" | "picture_medium" | "picture_big" | "picture_xl", "tracklist">,
|
||||||
|
tracks: Pick<DeezerResponse<Pick<Track, "id" | "readable" | "title" | "title_short" | "title_version" | "link" | "duration" | "rank" | "explicit_lyrics" | "explicit_content_lyrics" | "explicit_content_cover" | "preview" | "md5_image" | "artist">[]>, "data">,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SearchOptions {
|
||||||
|
index?: number,
|
||||||
|
limit?: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QueryOptions extends SearchOptions {
|
||||||
|
strict?: boolean,
|
||||||
|
order?: SearchOrder,
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'deezer-js' {
|
||||||
|
enum TrackFormats {
|
||||||
|
FLAC = 9,
|
||||||
|
MP3_320 = 3,
|
||||||
|
MP3_128 = 1,
|
||||||
|
MP4_RA3 = 15,
|
||||||
|
MP4_RA2 = 14,
|
||||||
|
MP4_RA1 = 13,
|
||||||
|
DEFAULT = 8,
|
||||||
|
LOCAL = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
class API {
|
||||||
|
http_headers: any;
|
||||||
|
cookie_jar: CookieJar;
|
||||||
|
access_token: string | null;
|
||||||
|
|
||||||
|
constructor(cookie_jar: CookieJar, headers: any): void;
|
||||||
|
|
||||||
|
api_call(method: string, args?: any): Promise<any>;
|
||||||
|
|
||||||
|
get_album(album_id: string | number): Promise<Album>;
|
||||||
|
get_album_by_UPC(upc: string): Promise<Album>;
|
||||||
|
get_album_comments(album_id: string | number, options?: SearchOptions): Promise<any>;
|
||||||
|
get_album_fans(album_id: string | number, options?: SearchOptions): Promise<any>;
|
||||||
|
get_album_tracks(album_id: string | number, options?: SearchOptions): Promise<DeezerResponse<[Track]>>;
|
||||||
|
|
||||||
|
get_artist(artist_id: string | number): Promise<Artist>;
|
||||||
|
get_artist_top(artist_id: string | number, options?: SearchOptions): Promise<any>;
|
||||||
|
get_artist_albums(artist_id: string | number, options?: SearchOptions): Promise<DeezerResponse<[Album]>>;
|
||||||
|
get_artist_comments(artist_id: string | number, options?: SearchOptions): Promise<any>;
|
||||||
|
get_artist_fans(artist_id: string | number, options?: SearchOptions): Promise<any>;
|
||||||
|
get_artist_related(artist_id: string | number, options?: SearchOptions): Promise<any>;
|
||||||
|
get_artist_radio(artist_id: string | number, options?: SearchOptions): Promise<any>;
|
||||||
|
get_artist_playlists(artist_id: string | number, options?: SearchOptions): Promise<any>;
|
||||||
|
|
||||||
|
get_chart(genre_id?: string | number, options?: SearchOptions): Promise<any>;
|
||||||
|
get_chart_tracks(genre_id?: string | number, options?: SearchOptions): Promise<any>;
|
||||||
|
get_chart_albums(genre_id?: string | number, options?: SearchOptions): Promise<any>;
|
||||||
|
get_chart_artists(genre_id?: string | number, options?: SearchOptions): Promise<any>;
|
||||||
|
get_chart_playlists(genre_id?: string | number, options?: SearchOptions): Promise<any>;
|
||||||
|
get_chart_podcasts(genre_id?: string | number, options?: SearchOptions): Promise<any>;
|
||||||
|
|
||||||
|
get_comment(comment_id: string | number): Promise<Comment>;
|
||||||
|
|
||||||
|
get_editorials(options?: SearchOptions): Promise<any>;
|
||||||
|
get_editorial(genre_id?: number): Promise<any>;
|
||||||
|
|
||||||
|
// for now who cares
|
||||||
|
|
||||||
|
search(query: string, options?: QueryOptions): Promise<DeezerResponse<[Track | Album | Artist | Playlist | Radio | User]>>;
|
||||||
|
search_album(query: string, options?: QueryOptions): Promise<DeezerResponse<[Album]>>;
|
||||||
|
search_artist(query: string, options?: QueryOptions): Promise<DeezerResponse<[Artist]>>;
|
||||||
|
search_playlist(query: string, options?: QueryOptions): Promise<DeezerResponse<[Playlist]>>;
|
||||||
|
search_radio(query: string, options?: QueryOptions): Promise<DeezerResponse<[Radio]>>;
|
||||||
|
search_track(query: string, options?: QueryOptions): Promise<DeezerResponse<[Track]>>;
|
||||||
|
|
||||||
|
get_track(song_id: string | number): Promise<Track>;
|
||||||
|
get_track_by_ISRC(isrc: string): Promise<Track>;
|
||||||
|
|
||||||
|
get_user(user_id: string | number): Promise<User>;
|
||||||
|
}
|
||||||
|
|
||||||
|
class GW {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Deezer {
|
||||||
|
http_headers: any;
|
||||||
|
cookie_jar: CookieJar;
|
||||||
|
logged_in: boolean;
|
||||||
|
current_user: any;
|
||||||
|
childs: any[];
|
||||||
|
selected_account: number;
|
||||||
|
|
||||||
|
api: API;
|
||||||
|
gw: GW;
|
||||||
|
|
||||||
|
constructor(): void;
|
||||||
|
|
||||||
|
login(email: string, password: string, re_captcha_token: string, child: number): Promise<boolean>;
|
||||||
|
login_via_arl(arl: string): Promise<boolean>;
|
||||||
|
_post_login(user_data: any): void;
|
||||||
|
change_account(child_n: number): any[2];
|
||||||
|
get_track_url(track_token: string, format: TrackFormats): Promise<Track>;
|
||||||
|
get_tracks_url(track_tokens: string[] | string, format: TrackFormats): Promise<Track[]>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
import * as WS from 'ws';
|
||||||
|
|
||||||
|
/* eslint-disable no-unused-vars */
|
||||||
|
export {};
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace Express {
|
||||||
|
// Inject additional properties on express.Request
|
||||||
|
interface Application {
|
||||||
|
ws(route: string, callback: (ws: WS.WebSocket, req: Express.Request) => void): Express
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "esnext",
|
||||||
|
"target": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"outDir": "dist"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue