feat(server): analyzeLink endpoint; test(server): analyzeLink unit tests; chore(server): linting

This commit is contained in:
Roberto Tonino 2021-06-01 22:35:49 +02:00
parent ffedd67a11
commit cb77745776
12 changed files with 283 additions and 67 deletions

View File

@ -27,7 +27,7 @@ export const listener = {
send(key: string, data?: any) { send(key: string, data?: any) {
if (data) console.log(key, data) if (data) console.log(key, data)
else console.log(key) else console.log(key)
if (["downloadInfo", "downloadWarn"].includes(key)) return if (['downloadInfo', 'downloadWarn'].includes(key)) return
wss.clients.forEach(client => { wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) { if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({ key, data })) client.send(JSON.stringify({ key, data }))
@ -37,7 +37,7 @@ export const listener = {
} }
export function getSettings(): any { export function getSettings(): any {
return {settings, defaultSettings, spotifySettings: plugins.spotify.getCredentials()} return { settings, defaultSettings, spotifySettings: plugins.spotify.getCredentials() }
} }
export function saveSettings(newSettings: any, newSpotifySettings: any) { export function saveSettings(newSettings: any, newSpotifySettings: any) {
@ -56,33 +56,33 @@ export async function addToQueue(dz: any, url: string[], bitrate: number) {
if (!dz.logged_in) throw new NotLoggedIn() if (!dz.logged_in) throw new NotLoggedIn()
let downloadObjs: any[] = [] let downloadObjs: any[] = []
let link: string = "" let link: string = ''
const requestUUID = uuidv4() const requestUUID = uuidv4()
if (url.length > 1){ if (url.length > 1) {
listener.send("startGeneratingItems", {uuid: requestUUID, total: url.length}) listener.send('startGeneratingItems', { uuid: requestUUID, total: url.length })
} }
for (let i = 0; i < url.length; i++){ for (let i = 0; i < url.length; i++) {
link = url[i] link = url[i]
console.log(`Adding ${link} to queue`) console.log(`Adding ${link} to queue`)
let downloadObj = await deemix.generateDownloadObject(dz, link, bitrate, plugins, listener) const downloadObj = await deemix.generateDownloadObject(dz, link, bitrate, plugins, listener)
if (Array.isArray(downloadObj)){ if (Array.isArray(downloadObj)) {
downloadObjs = downloadObjs.concat(downloadObj) downloadObjs = downloadObjs.concat(downloadObj)
} else { } else {
downloadObjs.push(downloadObj) downloadObjs.push(downloadObj)
} }
} }
if (url.length > 1){ if (url.length > 1) {
listener.send("finishGeneratingItems", {uuid: requestUUID, total: downloadObjs.length}) listener.send('finishGeneratingItems', { uuid: requestUUID, total: downloadObjs.length })
} }
const slimmedObjects: any[] = [] const slimmedObjects: any[] = []
downloadObjs.forEach((downloadObj: any, pos: number) => { downloadObjs.forEach((downloadObj: any, pos: number) => {
// Check if element is already in queue // Check if element is already in queue
if (Object.keys(queue).includes(downloadObj.uuid)){ if (Object.keys(queue).includes(downloadObj.uuid)) {
listener.send('alreadyInQueue', downloadObj.getEssentialDict()) listener.send('alreadyInQueue', downloadObj.getEssentialDict())
delete downloadObjs[pos] delete downloadObjs[pos]
return return
@ -133,7 +133,10 @@ export async function startQueue(dz: any): Promise<any> {
case 'Convertable': case 'Convertable':
downloadObject = new Convertable(currentItem) downloadObject = new Convertable(currentItem)
downloadObject = await plugins[downloadObject.plugin].convert(dz, downloadObject, settings, listener) downloadObject = await plugins[downloadObject.plugin].convert(dz, downloadObject, settings, listener)
fs.writeFileSync(configFolder + `queue${sep}${downloadObject.uuid}.json`, JSON.stringify({...downloadObject.toDict(), status: 'inQueue'})) fs.writeFileSync(
configFolder + `queue${sep}${downloadObject.uuid}.json`,
JSON.stringify({ ...downloadObject.toDict(), status: 'inQueue' })
)
break break
} }
currentJob = new Downloader(dz, downloadObject, settings, listener) currentJob = new Downloader(dz, downloadObject, settings, listener)

View File

@ -0,0 +1,30 @@
import { appSendGet } from '../../../../tests/utils'
describe('analyzeLink requests', () => {
it('should respond 200 to calls with supported term', async () => {
const res = await appSendGet('/api/analyzeLink/?term=https://www.deezer.com/en/album/100896762')
expect(res.status).toBe(200)
})
it('should respond with an error to calls with not supported term', async () => {
const res = await appSendGet('/api/analyzeLink/?term=https://www.deezer.com/en/artist/15166511')
expect(res.status).toBe(400)
expect(res.body.errorMessage).toBe('Not supported')
})
it('should respond album analyzed data', async () => {
const res = await appSendGet('/api/analyzeLink/?term=https://www.deezer.com/en/album/100896762')
expect(res.body.type).toBe('album')
expect(res.body.artist.name).toBe('Lil Nas X')
})
it('should respond track analyzed data', async () => {
const res = await appSendGet('/api/analyzeLink/?term=https://www.deezer.com/en/track/1283264142')
expect(res.body.type).toBe('track')
expect(res.body.artist.name).toBe('Lil Nas X')
})
})

View File

@ -0,0 +1,47 @@
import type { RequestHandler } from 'express'
// @ts-expect-error
import deemix from 'deemix'
// @ts-expect-error
import { Deezer } from 'deezer-js'
import type { ApiHandler, GetTrackResponse, GetAlbumResponse } from '../../../types'
import { sessionDZ } from '../../../main'
export interface AnalyzeQuery {
term?: string
}
type ResBody = GetAlbumResponse | GetTrackResponse
const path: ApiHandler['path'] = '/analyzeLink'
const handler: RequestHandler<ResBody, {}, {}, AnalyzeQuery> = async (req, res) => {
try {
if (!req.query || !req.query.term) {
return res.status(400).send({ errorMessage: 'No term specified', errorCode: 'AL01' })
}
const { term: linkToAnalyze } = req.query
const [, linkType, linkId] = await deemix.parseLink(linkToAnalyze)
const isTrackOrAlbum = ['track', 'album'].includes(linkType)
if (isTrackOrAlbum) {
if (!sessionDZ[req.session.id]) sessionDZ[req.session.id] = new Deezer()
const dz = sessionDZ[req.session.id]
const apiMethod = linkType === 'track' ? 'get_track' : 'get_album'
const resBody: ResBody = await dz.api[apiMethod](linkId)
return res.status(200).send(resBody)
}
return res.status(400).send({ errorMessage: 'Not supported', errorCode: 'AL02' })
} catch (error) {
return res
.status(500)
.send({ errorMessage: 'The server had a problem. Please try again', errorObject: error, errorCode: 'AL03' })
}
}
const apiHandler: ApiHandler = { path, handler }
export default apiHandler

View File

@ -7,7 +7,7 @@ const path: ApiHandler['path'] = '/getQueue'
// let homeCache: any // let homeCache: any
const handler: ApiHandler['handler'] = (_, res) => { const handler: ApiHandler['handler'] = (_, res) => {
const result:any = { const result: any = {
queue, queue,
order: queueOrder order: queueOrder
} }

View File

@ -20,42 +20,42 @@ const handler: ApiHandler['handler'] = async (req, res) => {
} }
case 'spotifyplaylist': case 'spotifyplaylist':
case 'spotify_playlist': { case 'spotify_playlist': {
if (!plugins.spotify.enabled){ if (!plugins.spotify.enabled) {
res.send({ res.send({
collaborative: false, collaborative: false,
description: "", description: '',
external_urls: {spotify: null}, external_urls: { spotify: null },
followers: {total: 0, href: null}, followers: { total: 0, href: null },
id: null, id: null,
images: [], images: [],
name: "Something went wrong", name: 'Something went wrong',
owner: { owner: {
display_name: "Error", display_name: 'Error',
id: null id: null
}, },
public: true, public: true,
tracks : [], tracks: [],
type: 'playlist', type: 'playlist',
uri: null uri: null
}) })
break break
} }
let sp = plugins.spotify.sp const sp = plugins.spotify.sp
let playlist = await sp.getPlaylist(list_id) let playlist = await sp.getPlaylist(list_id)
playlist = playlist.body playlist = playlist.body
let tracklist = playlist.tracks.items let tracklist = playlist.tracks.items
while (playlist.tracks.next) { while (playlist.tracks.next) {
let regExec = /offset=(\d+)&limit=(\d+)/g.exec(playlist.tracks.next) const regExec = /offset=(\d+)&limit=(\d+)/g.exec(playlist.tracks.next)
let offset = regExec![1] const offset = regExec![1]
let limit = regExec![2] const limit = regExec![2]
let playlistTracks = await sp.getPlaylistTracks(list_id, { offset, limit }) const playlistTracks = await sp.getPlaylistTracks(list_id, { offset, limit })
playlist.tracks = playlistTracks.body playlist.tracks = playlistTracks.body
tracklist = tracklist.concat(playlist.tracks.items) tracklist = tracklist.concat(playlist.tracks.items)
} }
tracklist.forEach((item:any, i:number) => { tracklist.forEach((item: any, i: number) => {
tracklist[i] = item.track tracklist[i] = item.track
tracklist[i].selected = false tracklist[i].selected = false
}); })
playlist.tracks = tracklist playlist.tracks = tracklist
res.send(playlist) res.send(playlist)
break break

View File

@ -6,25 +6,25 @@ const path: ApiHandler['path'] = '/getUserSpotifyPlaylists'
const handler: ApiHandler['handler'] = async (req, res) => { const handler: ApiHandler['handler'] = async (req, res) => {
let data let data
if (plugins.spotify.enabled){ if (plugins.spotify.enabled) {
let sp = plugins.spotify.sp const sp = plugins.spotify.sp
const username = req.query.spotifyUser const username = req.query.spotifyUser
data = [] data = []
let playlists = await sp.getUserPlaylists(username) let playlists = await sp.getUserPlaylists(username)
let playlistList = playlists.body.items let playlistList = playlists.body.items
while (playlists.next) { while (playlists.next) {
let regExec = /offset=(\d+)&limit=(\d+)/g.exec(playlists.next) const regExec = /offset=(\d+)&limit=(\d+)/g.exec(playlists.next)
let offset = regExec![1] const offset = regExec![1]
let limit = regExec![2] const limit = regExec![2]
let newPlaylists = await sp.getUserPlaylists(username, { offset, limit }) const newPlaylists = await sp.getUserPlaylists(username, { offset, limit })
playlists = newPlaylists.body playlists = newPlaylists.body
playlistList = playlistList.concat(playlists.items) playlistList = playlistList.concat(playlists.items)
} }
playlistList.forEach((playlist: any) => { playlistList.forEach((playlist: any) => {
data.push(plugins.spotify._convertPlaylistStructure(playlist)) data.push(plugins.spotify._convertPlaylistStructure(playlist))
}) })
} else { } else {
data = { error: 'spotifyNotEnabled'} data = { error: 'spotifyNotEnabled' }
} }
res.send(data) res.send(data)
} }

View File

@ -1,3 +1,4 @@
import analyzeLink from './analyzeLink'
import getHome from './getHome' import getHome from './getHome'
import getCharts from './getCharts' import getCharts from './getCharts'
import mainSearch from './mainSearch' import mainSearch from './mainSearch'
@ -16,6 +17,7 @@ import getQueue from './getQueue'
export default [ export default [
albumSearch, albumSearch,
analyzeLink,
getHome, getHome,
getCharts, getCharts,
getChartTracks, getChartTracks,

View File

@ -8,12 +8,12 @@ import logout from './logout'
import saveSettings from './saveSettings' import saveSettings from './saveSettings'
export default [ export default [
loginArl, loginArl,
addToQueue, addToQueue,
loginWithCredentials, loginWithCredentials,
cancelAllDownloads, cancelAllDownloads,
removeFinishedDownloads, removeFinishedDownloads,
removeFromQueue, removeFromQueue,
logout, logout,
saveSettings saveSettings
] ]

View File

@ -7,7 +7,7 @@ const handler: ApiHandler['handler'] = async (req, res) => {
const { email, password } = req.body const { email, password } = req.body
let accessToken = req.body.accessToken let accessToken = req.body.accessToken
if (!accessToken){ if (!accessToken) {
accessToken = await getAccessToken(email, password) accessToken = await getAccessToken(email, password)
} }
let arl let arl

View File

@ -4,13 +4,13 @@ import { cancelDownload } from '../../../main'
const path = '/removeFromQueue' const path = '/removeFromQueue'
const handler: ApiHandler['handler'] = async (req, res) => { const handler: ApiHandler['handler'] = async (req, res) => {
const {uuid} = req.query const { uuid } = req.query
if (uuid){ if (uuid) {
cancelDownload(uuid) cancelDownload(uuid)
res.send({ result: true }) res.send({ result: true })
}else{ } else {
res.send({ result: false }) res.send({ result: false })
} }
} }
const apiHandler = { path, handler } const apiHandler = { path, handler }

View File

@ -1,6 +1,5 @@
import { ApiHandler } from '../../../types' import { ApiHandler, Settings, SpotifySettings } from '../../../types'
import { saveSettings, listener } from '../../../main' import { saveSettings, listener } from '../../../main'
import { Settings, SpotifySettings } from '../../../types'
const path = '/saveSettings' const path = '/saveSettings'
@ -10,8 +9,8 @@ export interface SaveSettingsData {
} }
const handler: ApiHandler['handler'] = async (req, res) => { const handler: ApiHandler['handler'] = async (req, res) => {
const { settings, spotifySettings }: SaveSettingsData = req.query const { settings, spotifySettings }: SaveSettingsData = req.query
saveSettings(settings, spotifySettings) saveSettings(settings, spotifySettings)
listener.send('updateSettings', { settings, spotifySettings }) listener.send('updateSettings', { settings, spotifySettings })
res.send({ result: true }) res.send({ result: true })
} }

View File

@ -96,3 +96,138 @@ export interface Settings {
// TODO // TODO
export interface SpotifySettings {} export interface SpotifySettings {}
interface BaseDeezerObject {
id: number
type: string
}
interface NamedDeezerObject extends BaseDeezerObject {
name: string
}
interface PicturedDeezerObject extends BaseDeezerObject {
picture: string
picture_small: string
picture_medium: string
picture_big: string
picture_xl: string
}
interface CoveredDeezerObject extends BaseDeezerObject {
cover: string
cover_small: string
cover_medium: string
cover_big: string
cover_xl: string
}
interface DeezerWrapper<Type> {
data: Type[]
}
export interface DeezerContributor extends NamedDeezerObject, PicturedDeezerObject {
link: string
share: string
radio: boolean
tracklist: string
role: string
}
export interface DeezerTrackArtist extends NamedDeezerObject, PicturedDeezerObject {
link: string
share: string
radio: boolean
tracklist: string
}
export interface DeezerAlbumArtist extends NamedDeezerObject, PicturedDeezerObject {
tracklist: string
}
export interface DeezerAlbum extends BaseDeezerObject, CoveredDeezerObject {
title: string
link: string
md5_image: string
release_date: string
tracklist: string
}
export interface DeezerGenre extends NamedDeezerObject {
picture: string
}
type DeezerGenres = DeezerWrapper<DeezerGenre>
export interface GetAlbumTrackArtist extends NamedDeezerObject {
tracklist: string
}
export interface DeezerTrack extends BaseDeezerObject {
readable: boolean
title: string
title_short: string
title_version: string
link: string
duration: number
rank: number
explicit_lyrics: boolean
explicit_content_lyrics: number
explicit_content_cover: number
preview: string
md5_image: string
artist: GetAlbumTrackArtist
}
type DeezerTracks = DeezerWrapper<DeezerTrack>
export interface GetTrackResponse extends BaseDeezerObject {
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: number
explicit_content_cover: number
preview: string
bpm: number
gain: number
available_countries: string[]
contributors: DeezerContributor[]
md5_image: string
artist: DeezerTrackArtist
album: DeezerAlbum
}
export interface GetAlbumResponse extends BaseDeezerObject, CoveredDeezerObject {
title: string
upc: string
link: string
share: string
md5_image: string
genre_id: number
genres: DeezerGenres
label: string
nb_tracks: number
duration: number
fans: number
rating: number
release_date: string
record_type: string
available: boolean
tracklist: string
explicit_lyrics: boolean
explicit_content_lyrics: number
explicit_content_cover: number
contributors: DeezerContributor[]
artist: DeezerAlbumArtist
tracks: DeezerTracks
}