mirror of
https://codeberg.org/lucida/lucida.git
synced 2026-11-09 13:07:25 -05:00
add playlists to qobuz and deezer
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
export const GW_LIGHT_URL = 'https://www.deezer.com/ajax/gw-light.php'
|
||||
export const CLIENT_ID = '447462'
|
||||
export const CLIENT_SECRET = 'a83bf7f38ad2f137e444727cfc3775cf'
|
||||
export const SIZES = [32, 64, 128, 256, 512, 1024]
|
||||
export const SIZES = [128, 256, 512, 1024]
|
||||
|
||||
export const BLOWFISH_SECRET = 'g4el58wc0zvf9na1'
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
GetByUrlResponse,
|
||||
GetStreamResponse,
|
||||
ItemType,
|
||||
PlaylistGetByUrlResponse,
|
||||
SearchResults,
|
||||
StreamerAccount,
|
||||
StreamerWithLogin,
|
||||
@@ -15,6 +16,7 @@ import { createHash } from 'crypto'
|
||||
import {
|
||||
DeezerAlbum,
|
||||
DeezerArtist,
|
||||
DeezerPlaylistMetadata,
|
||||
DeezerFormat,
|
||||
DeezerLoginResponse,
|
||||
DeezerMediaResponse,
|
||||
@@ -22,7 +24,8 @@ import {
|
||||
DeezerUserData,
|
||||
parseAlbum,
|
||||
parseArtist,
|
||||
parseTrack
|
||||
parseTrack,
|
||||
parsePlaylistMetadata
|
||||
} from './parse.js'
|
||||
import { Readable, Transform } from 'stream'
|
||||
import { Blowfish } from 'blowfish-cbc'
|
||||
@@ -43,9 +46,15 @@ interface APIMethod {
|
||||
DATA: DeezerAlbum
|
||||
SONGS: { data: DeezerTrack[] }
|
||||
}
|
||||
'playlist.getSongs': {
|
||||
data: DeezerTrack[]
|
||||
}
|
||||
'deezer.pageTrack': {
|
||||
DATA: DeezerTrack
|
||||
}
|
||||
'deezer.pagePlaylist': {
|
||||
DATA: DeezerPlaylistMetadata
|
||||
}
|
||||
'user.getArl': string
|
||||
'search.music': { data: (DeezerArtist | DeezerAlbum | DeezerTrack)[] }
|
||||
'song.getListData': { data: DeezerTrack[] }
|
||||
@@ -272,7 +281,7 @@ export default class Deezer implements StreamerWithLogin {
|
||||
async #getInfoFromUrl(url: URL): Promise<{ type: ItemType; id: number }> {
|
||||
if (url.hostname == 'deezer.page.link') url = await this.#unshortenUrl(url)
|
||||
|
||||
const match = url.pathname.match(/^\/(?:[a-z]{2}\/)?(track|album|artist)\/(\d+)\/?$/)
|
||||
const match = url.pathname.match(/^\/(?:[a-z]{2}\/)?(track|album|artist|playlist)\/(\d+)\/?$/)
|
||||
if (!match) throw new Error('URL not supported')
|
||||
|
||||
const [, type, id] = match
|
||||
@@ -317,6 +326,29 @@ export default class Deezer implements StreamerWithLogin {
|
||||
return data
|
||||
}
|
||||
|
||||
// Playlist
|
||||
|
||||
async #getPlaylist(id: number): Promise<PlaylistGetByUrlResponse> {
|
||||
const playlistMetadata = (
|
||||
await this.#apiCall('deezer.pagePlaylist', {
|
||||
playlist_id: id
|
||||
})
|
||||
).DATA
|
||||
const playlistTracks = (
|
||||
await this.#apiCall('playlist.getSongs', {
|
||||
playlist_id: id,
|
||||
nb: 9999,
|
||||
start: 0
|
||||
})
|
||||
).data
|
||||
|
||||
return {
|
||||
type: 'playlist',
|
||||
metadata: parsePlaylistMetadata(playlistMetadata),
|
||||
tracks: playlistTracks.map(parseTrack)
|
||||
}
|
||||
}
|
||||
|
||||
// Track
|
||||
|
||||
async #getTrackData(id: number): Promise<DeezerTrack> {
|
||||
@@ -501,6 +533,9 @@ export default class Deezer implements StreamerWithLogin {
|
||||
}
|
||||
}
|
||||
}
|
||||
case 'playlist': {
|
||||
return await this.#getPlaylist(id)
|
||||
}
|
||||
default:
|
||||
throw new Error('URL unrecognised')
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Album, Artist, CoverArtwork, Track } from '../../types.js'
|
||||
import { Album, Artist, CoverArtwork, Playlist, Track } from '../../types.js'
|
||||
import { SIZES } from './constants.js'
|
||||
|
||||
export enum DeezerFormat {
|
||||
@@ -185,3 +185,22 @@ export function parseArtwork(picture: string): CoverArtwork[] {
|
||||
width: size
|
||||
}))
|
||||
}
|
||||
|
||||
export interface DeezerPlaylistMetadata {
|
||||
PLAYLIST_ID: string
|
||||
TITLE: string
|
||||
DESCRIPTION: string
|
||||
PLAYLIST_PICTURE: string
|
||||
DURATION: number
|
||||
NB_SONG: number
|
||||
}
|
||||
|
||||
export function parsePlaylistMetadata(raw: DeezerPlaylistMetadata) {
|
||||
return <Playlist>{
|
||||
id: raw.PLAYLIST_ID,
|
||||
title: raw.TITLE,
|
||||
url: `https://www.deezer.com/playlist/${raw.PLAYLIST_ID}`,
|
||||
coverArtwork: parseArtwork(raw.PLAYLIST_PICTURE),
|
||||
trackCount: raw.NB_SONG
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,16 @@ import {
|
||||
StreamerAccount
|
||||
} from '../../types.js'
|
||||
import { DEFAULT_HEADERS } from './constants.js'
|
||||
import { parseAlbum, parseTrack, parseArtist, RawAlbum, RawArtist, RawTrack } from './parse.js'
|
||||
import {
|
||||
parseAlbum,
|
||||
parseTrack,
|
||||
parseArtist,
|
||||
RawAlbum,
|
||||
RawArtist,
|
||||
RawTrack,
|
||||
RawPlaylist,
|
||||
parsePlaylist
|
||||
} from './parse.js'
|
||||
import { Readable } from 'stream'
|
||||
|
||||
function headers(token?: string): HeadersInit {
|
||||
@@ -207,6 +216,7 @@ export default class Qobuz implements StreamerWithLogin {
|
||||
app_id: this.appId
|
||||
})
|
||||
return {
|
||||
type: 'album',
|
||||
metadata: { ...parseAlbum(albumResponse), trackCount: albumResponse.tracks?.items.length },
|
||||
tracks: albumResponse.tracks?.items.map(parseTrack)
|
||||
}
|
||||
@@ -224,7 +234,21 @@ export default class Qobuz implements StreamerWithLogin {
|
||||
)
|
||||
}
|
||||
|
||||
#getUrlParts(url: string): ['artist' | 'album' | 'track', string] {
|
||||
async #getPlaylist(id: string) {
|
||||
const init = <RawPlaylist>await this.#get('playlist/get', {
|
||||
playlist_id: id,
|
||||
extra: 'tracks,getSimilarPlaylists',
|
||||
offset: 0,
|
||||
limit: 1000,
|
||||
app_id: this.appId
|
||||
})
|
||||
|
||||
if (init.tracks_count > 1000)
|
||||
throw new Error('To be added, playlists with over a thousand tracks.')
|
||||
else return parsePlaylist(init)
|
||||
}
|
||||
|
||||
#getUrlParts(url: string): ['artist' | 'album' | 'track' | 'playlist', string] {
|
||||
const urlObj = new URL(url)
|
||||
if (urlObj.hostname == 'www.qobuz.com' || urlObj.hostname == 'qobuz.com') {
|
||||
const urlParts = url
|
||||
@@ -239,6 +263,8 @@ export default class Qobuz implements StreamerWithLogin {
|
||||
case 'album':
|
||||
case 'track':
|
||||
return [type, id]
|
||||
case 'playlists':
|
||||
return ['playlist', id]
|
||||
default:
|
||||
throw new Error('URL unrecognised')
|
||||
}
|
||||
@@ -248,7 +274,12 @@ export default class Qobuz implements StreamerWithLogin {
|
||||
?.slice(1, 3)
|
||||
if (!urlParts) throw new Error('URL not supported')
|
||||
urlParts[1] = urlParts[1].replace(/\?.*?$/, '')
|
||||
if (urlParts[0] != 'artist' && urlParts[0] != 'album' && urlParts[0] != 'track') {
|
||||
if (
|
||||
urlParts[0] != 'artist' &&
|
||||
urlParts[0] != 'album' &&
|
||||
urlParts[0] != 'track' &&
|
||||
urlParts[0] != 'playlist'
|
||||
) {
|
||||
throw new Error('URL unrecognised')
|
||||
}
|
||||
return [urlParts[0], urlParts[1]]
|
||||
@@ -282,6 +313,9 @@ export default class Qobuz implements StreamerWithLogin {
|
||||
metadata: await this.#getArtistMetadata(id)
|
||||
}
|
||||
}
|
||||
case 'playlist': {
|
||||
return await this.#getPlaylist(id)
|
||||
}
|
||||
default:
|
||||
throw new Error('URL unrecognised')
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Artist, Album, Track } from '../../types.js'
|
||||
import { Artist, Album, Track, PlaylistGetByUrlResponse } from '../../types.js'
|
||||
|
||||
export interface RawArtist {
|
||||
id: number
|
||||
@@ -154,3 +154,37 @@ function parsePerformers(performers: string, track: Track) {
|
||||
|
||||
return track
|
||||
}
|
||||
|
||||
export interface RawPlaylist {
|
||||
owner: {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
id: number
|
||||
name: string
|
||||
images: string[]
|
||||
is_collaborative: boolean
|
||||
description: string
|
||||
created_at: number
|
||||
duration: number
|
||||
tracks_count: number
|
||||
tracks: {
|
||||
offset?: number
|
||||
limit?: number
|
||||
total?: number
|
||||
items: RawTrack[]
|
||||
}
|
||||
}
|
||||
|
||||
export function parsePlaylist(raw: RawPlaylist) {
|
||||
return <PlaylistGetByUrlResponse>{
|
||||
type: 'playlist',
|
||||
metadata: {
|
||||
id: raw.id,
|
||||
title: raw.name,
|
||||
trackCount: raw?.tracks_count || raw?.tracks?.total,
|
||||
url: `https://open.qobuz.com/playlist/${raw.id}`
|
||||
},
|
||||
tracks: raw.tracks?.items.map(parseTrack)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user