//Datatypes, constructor parameters = gw_light API call. class Track { constructor(json) { this.id = json.SNG_ID.toString(); this.title = `${json.SNG_TITLE}${json.VERSION ? ` ${json.VERSION}` : ''}`; //Duration as ms for easier use in frontend this.duration = parseInt(json.DURATION.toString(), 10) * 1000; this.albumArt = new DeezerImage(json.ALB_PICTURE); this.artists = (json.ARTISTS ? json.ARTISTS : [json]).map((a) => new Artist(a)); //Helper this.artistString = this.artists.map((a) => a.name).join(', '); this.album = new Album(json); this.trackNumber = parseInt((json.TRACK_NUMBER || 1).toString(), 10); this.diskNumber = parseInt((json.DISK_NUMBER || 1).toString(), 10); this.explicit = json['EXPLICIT_LYRICS'] == 1 ? true:false; this.lyricsId = json.LYRICS_ID; this.library = null; //Generate URL Part //0 - 32 = MD5 ORIGIN //33 - = 1/0 if md5origin ends with .mp3 //34 - 35 = MediaVersion //Rest = Track ID let md5 = json.MD5_ORIGIN.replace('.mp3', ''); let md5mp3bit = json.MD5_ORIGIN.includes('.mp3') ? '1' : '0'; let mv = json.MEDIA_VERSION.toString().padStart(2, '0'); this.streamUrl = `${md5}${md5mp3bit}${mv}${this.id}`; } //Get Deezer CDN url by streamUrl static getUrlInfo(info) { let md5origin = info.substring(0, 32); if (info.charAt(32) == '1') md5origin += '.mp3'; let mediaVersion = parseInt(info.substring(33, 34)).toString(); let trackId = info.substring(35); return new QualityInfo(md5origin, mediaVersion, trackId); } } class Album { constructor(json, tracksJson = {data: []}, library = false) { this.id = json.ALB_ID.toString(); this.title = json.ALB_TITLE; this.art = new DeezerImage(json.ALB_PICTURE); this.fans = json.NB_FAN; this.tracks = tracksJson.data.map((t) => new Track(t)); this.artists = (json.ARTISTS ? json.ARTISTS : [json]).map((a) => new Artist(a)); this.releaseDate = json.DIGITAL_RELEASE_DATE; //Explicit this.explicit = false; if (json.EXPLICIT_LYRICS && json.EXPLICIT_LYRICS.toString() == "1") this.explicit = true; if (json.EXPLICIT_ALBUM_CONTENT) { if (json.EXPLICIT_ALBUM_CONTENT.EXPLICIT_LYRICS_STATUS == 4) this.explicit = true; if (json.EXPLICIT_ALBUM_CONTENT.EXPLICIT_LYRICS_STATUS == 1) this.explicit = true; } //Type this.type = 'album'; if (json.TYPE && json.TYPE.toString() == "0") this.type = 'single'; if (json.ROLE_ID == 5) this.type = 'featured'; //Helpers this.artistString = this.artists.map((a) => a.name).join(', '); this.library = library; } } class Artist { constructor(json, albumsJson = {data: []}, topJson = {data: []}, library = false) { this.id = json.ART_ID.toString(); this.name = json.ART_NAME; this.fans = json.NB_FAN; this.picture = new DeezerImage(json.ART_PICTURE, 'artist'); this.albumCount = albumsJson.total; this.albums = albumsJson.data.map((a) => new Album(a)); this.topTracks = topJson.data.map((t) => new Track(t)); this.radio = json.SMARTRADIO; this.library = library; } } class Playlist { constructor(json, tracksJson = {data: []}, library = false) { this.id = json.PLAYLIST_ID.toString(), this.title = json.TITLE, this.trackCount = json.NB_SONG ? json.NB_SONG : tracksJson.total; this.image = new DeezerImage(json.PLAYLIST_PICTURE, json.PICTURE_TYPE); this.fans = json.NB_FAN; this.duration = parseInt((json.DURATION ? json.DURATION : 0).toString(), 10) * 1000; this.description = json.DESCRIPTION; this.user = new User( json.PARENT_USER_ID, json.PARENT_USERNAME, new DeezerImage(json.PARENT_USER_PICTURE, 'user') ); this.tracks = tracksJson.data.map((t) => new Track(t)); this.library = library; switch (json.STATUS) { case 0: this.type = 'public'; break; case 1: this.type = 'private'; break; case 2: this.type = 'collaborative' break; } } //Extend tracks extend(tracksJson = {data: []}) { let tracks = tracksJson.data.map((t) => new Track(t)); this.tracks.push(...tracks); } } class User { constructor(id, name, picture) { this.id = id; this.name = name; this.picture = picture; } } class DeezerImage { constructor(hash, type='cover') { this.hash = hash; this.type = type; //Create full and thumb, to standardize size and because functions aren't preserved this.full = DeezerImage.url(this.hash, this.type, 1400); this.thumb = DeezerImage.url(this.hash, this.type, 256); } static url(hash, type, size = 256) { if (!hash) return `https://e-cdns-images.dzcdn.net/images/${type}/${size}x${size}-000000-80-0-0.jpg`; return `https://e-cdns-images.dzcdn.net/images/${type}/${hash}/${size}x${size}-000000-80-0-0.jpg`; } } class SearchResults { constructor(json) { this.albums = json.ALBUM.data.map((a) => new Album(a)); this.artists = json.ARTIST.data.map((a) => new Artist(a)); this.tracks = json.TRACK.data.map((t) => new Track(t)); this.playlists = json.PLAYLIST.data.map((p) => new Playlist(p)); this.top = json.TOP_RESULT; } } class DeezerProfile { constructor(json) { this.token = json.checkForm; this.id = json.USER.USER_ID; this.name = json.USER.BLOG_NAME; this.favoritesPlaylist = json.USER.LOVEDTRACKS_ID; this.picture = new DeezerImage(json.USER.USER_PICTURE, 'user'); } } class DeezerLibrary { //Pass 'TAB' from API to parse constructor(json, type='tracks') { switch (type) { case 'tracks': this.count = json.loved.total; this.data = json.loved.data.map((t) => new Track(t)); break; case 'albums': this.count = json.albums.total; this.data = json.albums.data.map((a) => new Album(a, {data: []}, true)); break; case 'artists': this.count = json.artists.total; this.data = json.artists.data.map((a) => new Artist(a, {data: []}, {data: []}, true)); break; case 'playlists': this.count = json.playlists.total; this.data = json.playlists.data.map((p) => new Playlist(p, {data: []}, true)); break; } } } class SmartTrackList { constructor(json) { this.title = json.TITLE; this.subtitle = json.SUBTITLE; this.description = json.DESCRIPTION; this.id = json.SMARTTRACKLIST_ID this.cover = new DeezerImage(json.COVER.MD5, json.COVER.TYPE); } } class DeezerPage { constructor(json) { this.title = json.title; this.sections = json.sections.map((s) => new ChannelSection(s)); } } class DeezerChannel { constructor(json, target) { this.title = json.title; this.image = new DeezerImage(json.pictures[0].md5, json.pictures[0].type); this.color = json.background_color; this.id = json.id; this.slug = json.slug; //Hopefully it's used for path this.target = target; } } class ChannelSection { constructor(json) { //Parse layout switch (json.layout) { case 'grid': this.layout = 'grid'; break; case 'horizontal-grid': this.layout = 'row'; break; default: this.layout = 'row'; break; } this.title = json.title; this.hasMore = json.hasMoreItems ? true : false; this.target = json.target; this.items = json.items.map((i) => new ChannelSectionItem(i)); } } class ChannelSectionItem { constructor(json) { this.id = json.id; this.title = json.title; this.type = json.type; this.subtitle = json.subtitle; //Parse data switch (this.type) { case 'flow': case 'smarttracklist': this.data = new SmartTrackList(json.data); break; case 'playlist': this.data = new Playlist(json.data); break; case 'artist': this.data = new Artist(json.data); break; case 'channel': this.data = new DeezerChannel(json.data, json.target); break; case 'album': this.data = new Album(json.data); break; } } } class Lyrics { constructor(json) { this.id = json.LYRICS_ID; this.writer = json.LYRICS_WRITERS; this.text = []; if (json.LYRICS_TEXT) { this.text = json.LYRICS_TEXT.replace(new RegExp('\\r', 'g'), '').split('\n'); } //Parse invidual lines this.lyrics = []; if (json.LYRICS_SYNC_JSON) { for (let l of json.LYRICS_SYNC_JSON) { let lyric = Lyric.parseJson(l); if (lyric) this.lyrics.push(lyric); } } } } class Lyric { //NOT for parsing from deezer constructor(offset, text, lrcTimestamp) { this.offset = parseInt(offset.toString(), 10); this.text = text; this.lrcTimestamp = lrcTimestamp; } //Can return null if invalid lyric static parseJson(json) { if (!json.milliseconds || !json.line || json.line == '') return; return new Lyric(json.milliseconds, json.line, json.lrc_timestamp); } } class QualityInfo { constructor(md5origin, mediaVersion, trackId, quality = 1, source='stream') { this.md5origin = md5origin; this.mediaVersion = mediaVersion; this.trackId = trackId; this.quality = quality; this.source = source; //For FLAC bitrate calculation this.size = 1; this.url = ''; } //Generate direct stream URL generateUrl() { let md5 = this.md5origin.replace('.mp3', ''); let md5mp3bit = this.md5origin.includes('.mp3') ? '1' : '0'; let mv = this.mediaVersion.toString().padStart(2, '0'); this.url = `/stream/${md5}${md5mp3bit}${mv}${this.trackId}?q=${this.quality}`; } } module.exports = {Track, Album, Artist, Playlist, User, SearchResults, DeezerImage, DeezerProfile, DeezerLibrary, DeezerPage, Lyrics};