diff --git a/package.json b/package.json index 0383a33..39e7b85 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,12 @@ "name": "deemix-web-frontend", "version": "1.0.0", "description": "a dumb frontend for just getting some got damned songs", - "main": "src/index.js", + "main": "dist/index.js", + "type": "module", "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": { "type": "git", @@ -30,5 +33,11 @@ "optionalDependencies": { "bufferutil": "^4.0.5", "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" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed34bd5..9c8c189 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,6 +1,9 @@ lockfileVersion: 5.3 specifiers: + '@types/express': ^4.17.13 + '@types/express-ws': ^3.0.1 + '@types/ws': ^8.2.0 bufferutil: ^4.0.5 deemix: git+https://git.freezer.life/RemixDev/deemix-js deezer-js: ^1.2.4 @@ -9,6 +12,7 @@ specifiers: express-ws: ^5.0.2 timeago.js: ^4.0.2 toml: ^3.0.0 + typescript: ^4.4.4 utf-8-validate: ^5.0.7 winston: ^3.3.3 ws: ^8.2.3 @@ -28,6 +32,12 @@ optionalDependencies: bufferutil: 4.0.5 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: /@dabh/diagnostics/2.0.2: @@ -50,6 +60,13 @@ packages: defer-to-connect: 2.0.1 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: resolution: {integrity: sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==} dependencies: @@ -59,6 +76,37 @@ packages: '@types/responselike': 1.0.0 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: resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==} dev: false @@ -69,9 +117,20 @@ packages: '@types/node': 16.11.4 dev: false + /@types/mime/1.3.2: + resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} + dev: true + /@types/node/16.11.4: 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: resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} @@ -79,6 +138,19 @@ packages: '@types/node': 16.11.4 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: resolution: {integrity: sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==} engines: {node: '>= 0.6'} @@ -993,6 +1065,12 @@ packages: mime-types: 2.1.33 dev: false + /typescript/4.4.4: + resolution: {integrity: sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + /universalify/0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} diff --git a/src/index.js b/src/index.ts similarity index 81% rename from src/index.js rename to src/index.ts index 96d17fb..b3e359c 100644 --- a/src/index.js +++ b/src/index.ts @@ -1,13 +1,16 @@ -const express = require('express'); -const deezer = require('deezer-js'); -const deemix = require('deemix'); -const path = require('path'); -const { promisify } = require('util'); -const fs = require('fs'); -const { exec } = require('child_process'); -const timeago = require('timeago.js'); -const toml = require('toml'); -const winston = require('winston'); +import express from 'express'; +import deezer from 'deezer-js'; +import deemix from 'deemix'; +import path from 'path'; +import { promisify } from 'util'; +import fs from 'fs'; +import { exec } from 'child_process'; +import timeago from 'timeago.js'; +import toml from 'toml'; +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 }) => { 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!'); 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'); -let searchcache = {}; -let albumcache = {}; -let trackcache = {}; +let searchcache: Record = {}; +let albumcache: Record = {}; +let trackcache: Record = {}; const port = config.server.port || 4500; const deleteTimer = config.timer.deleteTimer || 1000 * 60 * 25; -require('dotenv').config(); +dotenv.config(); -const app = new express(); -const expressWs = require('express-ws')(app); +const app = express(); +expressws(app); const deezerInstance = new deezer.Deezer(); let deemixDownloader; -let deemixSettings = deemix.settings.DEFAULTS +let deemixSettings = deemix.settings.DEFAULTS; deemixSettings.downloadLocation = path.join(process.cwd(), 'data/'); deemixSettings.overwriteFile = deemix.settings.OverwriteOption.ONLY_TAGS; @@ -84,10 +87,15 @@ deemixSettings.localArtworkFormat = deemixSettings.localArtworkFormat || deemixS deemixSettings.jpegImageQuality = deemixSettings.jpegImageQuality || deemixSettings.jpegImageQuality; deemixSettings.removeDuplicateArtists = config.deemix.removeDuplicateArtists !== undefined ? config.deemix.removeDuplicateArtists : deemixSettings.removeDuplicateArtists; +interface QueuedFile { + file: string, + date: number +} + const toDeleteLocation = './data/toDelete.json'; 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() { try { @@ -97,8 +105,8 @@ function updateQueueFile() { } } -function deleteQueuedFile(file) { - toDelete = toDelete.filter(c => c.file !== file); +function deleteQueuedFile(file: string) { + toDelete = toDelete.filter((c: QueuedFile) => c.file !== file); updateQueueFile(); fs.unlink(file, (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)}`); toDelete.push({ @@ -158,18 +166,21 @@ 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; + 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 { s = searchcache[req.query.search] || (await deezerInstance.api.search_album(req.query.search, { limit: config.limits.searchLimit || 15, - })); + })).data; } catch(err) { - logger.error(err.toString()); - res.sendStatus(500); + logger.error((err as Error).toString()); + return res.sendStatus(500); } if (!searchcache[req.query.search]) searchcache[req.query.search] = s; - let format = s.data.map(s => { + let format = s.map(s => { return { id: s.id, title: s.title, @@ -185,15 +196,19 @@ app.get('/api/search', async (req, res) => { }); app.get('/api/album', async (req, res) => { - if (!req.query.id) return req.sendStatus(400); - let album; + if (!req.query.id) return res.sendStatus(400); + 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 { album = albumcache[req.query.id] || (await deezerInstance.api.get_album(req.query.id)); if (!albumcache[req.query.id]) albumcache[req.query.id] = album; } catch (err) { - logger.error(err.toString()); - return req.status(404).send('Album not found!'); + logger.error((err as Error).toString()); + return res.status(404).send('Album not found!'); } + res.send({ id: album.id, title: album.title, @@ -210,11 +225,17 @@ app.get('/api/album', async (req, res) => { }); }); -async function deemixDownloadWrapper(dlObj, ws, coverArt, metadata) { - let trackpaths = []; +interface Metadata { + 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 = { - send(key, data) { + send(key: any, data: any) { if (data.downloaded) { trackpaths.push(data.downloadPath); queueDeletion(data.downloadPath); @@ -231,7 +252,7 @@ async function deemixDownloadWrapper(dlObj, ws, coverArt, metadata) { try { await deemixDownloader.start(); } catch(err) { - logger.warn(err.toString()); + logger.warn((err as Error).toString()); logger.warn('(this may be deemix just being whiny)'); } @@ -249,7 +270,7 @@ async function deemixDownloadWrapper(dlObj, ws, coverArt, metadata) { try { await promisify(exec)(`${config.server.zipBinaryLocation} ${config.server.zipArguments} "data/${folderName}.zip" "data/${folderName}"`); } catch(err) { - logger.error(err.toString()); + logger.error((err as Error).toString()); 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!'); 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)); if (!albumcache[req.query.id]) albumcache[req.query.id] = album; } catch(err) { - logger.error(err.toString()); + logger.error((err as Error).toString()); return ws.close(1012, 'Album not found'); } @@ -289,13 +310,13 @@ app.ws('/api/album', async (ws, req) => { 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!'); const dlObj = await deemix.generateDownloadObject(deezerInstance, 'https://www.deezer.com/track/' + req.query.id, format); let isDone = false; - ws.on('close', (code) => { + ws.on('close', (code: number) => { if (isDone) return; dlObj.isCanceled = true; 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)); if (!trackcache[req.query.id]) trackcache[req.query.id] = track; } catch(err) { - logger.error(err.toString()); + logger.error((err as Error).toString()); return ws.close(1012, 'Track not found'); } @@ -317,7 +338,7 @@ app.ws('/api/track', async (ws, req) => { 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'); app.listen(port, () => { logger.info(`hosting on http://localhost:${port} and wss://localhost:${port}`); diff --git a/src/types/deemix.d.ts b/src/types/deemix.d.ts new file mode 100644 index 0000000..7c061bd --- /dev/null +++ b/src/types/deemix.d.ts @@ -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 + + module downloader { + class Downloader { + dz: Deezer; + + constructor(dz: Deezer, downloadObject: IDownloadObject, settings: DeemixSettings, listener: Listener): void + + start(): Promise + } + } + + 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 + } +} \ No newline at end of file diff --git a/src/types/deezer.d.ts b/src/types/deezer.d.ts new file mode 100644 index 0000000..53a86b6 --- /dev/null +++ b/src/types/deezer.d.ts @@ -0,0 +1,217 @@ +type DeezerResponse = { + 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, + album: Pick, +} + +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, + tracks: Pick[]>, "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; + + get_album(album_id: string | number): Promise; + get_album_by_UPC(upc: string): Promise; + get_album_comments(album_id: string | number, options?: SearchOptions): Promise; + get_album_fans(album_id: string | number, options?: SearchOptions): Promise; + get_album_tracks(album_id: string | number, options?: SearchOptions): Promise>; + + get_artist(artist_id: string | number): Promise; + get_artist_top(artist_id: string | number, options?: SearchOptions): Promise; + get_artist_albums(artist_id: string | number, options?: SearchOptions): Promise>; + get_artist_comments(artist_id: string | number, options?: SearchOptions): Promise; + get_artist_fans(artist_id: string | number, options?: SearchOptions): Promise; + get_artist_related(artist_id: string | number, options?: SearchOptions): Promise; + get_artist_radio(artist_id: string | number, options?: SearchOptions): Promise; + get_artist_playlists(artist_id: string | number, options?: SearchOptions): Promise; + + get_chart(genre_id?: string | number, options?: SearchOptions): Promise; + get_chart_tracks(genre_id?: string | number, options?: SearchOptions): Promise; + get_chart_albums(genre_id?: string | number, options?: SearchOptions): Promise; + get_chart_artists(genre_id?: string | number, options?: SearchOptions): Promise; + get_chart_playlists(genre_id?: string | number, options?: SearchOptions): Promise; + get_chart_podcasts(genre_id?: string | number, options?: SearchOptions): Promise; + + get_comment(comment_id: string | number): Promise; + + get_editorials(options?: SearchOptions): Promise; + get_editorial(genre_id?: number): Promise; + + // for now who cares + + search(query: string, options?: QueryOptions): Promise>; + search_album(query: string, options?: QueryOptions): Promise>; + search_artist(query: string, options?: QueryOptions): Promise>; + search_playlist(query: string, options?: QueryOptions): Promise>; + search_radio(query: string, options?: QueryOptions): Promise>; + search_track(query: string, options?: QueryOptions): Promise>; + + get_track(song_id: string | number): Promise; + get_track_by_ISRC(isrc: string): Promise; + + get_user(user_id: string | number): Promise; + } + + 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; + login_via_arl(arl: string): Promise; + _post_login(user_data: any): void; + change_account(child_n: number): any[2]; + get_track_url(track_token: string, format: TrackFormats): Promise; + get_tracks_url(track_tokens: string[] | string, format: TrackFormats): Promise; + } +} \ No newline at end of file diff --git a/src/types/index.d.ts b/src/types/index.d.ts new file mode 100644 index 0000000..6de9de7 --- /dev/null +++ b/src/types/index.d.ts @@ -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 + } + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..4615452 --- /dev/null +++ b/tsconfig.json @@ -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" + } +} \ No newline at end of file