feat(server): analyzeLink endpoint; test(server): analyzeLink unit tests; chore(server): linting
This commit is contained in:
parent
ffedd67a11
commit
cb77745776
@ -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)
|
||||||
|
30
server/src/routes/api/get/analyzeLink.spec.ts
Normal file
30
server/src/routes/api/get/analyzeLink.spec.ts
Normal 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')
|
||||||
|
})
|
||||||
|
})
|
@ -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
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
]
|
]
|
||||||
|
@ -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
|
||||||
|
@ -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 }
|
||||||
|
@ -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 })
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user