mirror of
https://github.com/YuzuZensai/play-dl-test.git
synced 2026-01-06 04:32:40 +00:00
Add support for fetching information from Deezer
This commit is contained in:
500
play-dl/Deezer/classes.ts
Normal file
500
play-dl/Deezer/classes.ts
Normal file
@@ -0,0 +1,500 @@
|
||||
import { request } from '../Request';
|
||||
|
||||
interface DeezerImage {
|
||||
xl: string;
|
||||
big: string;
|
||||
medium: string;
|
||||
small: string;
|
||||
}
|
||||
|
||||
interface DeezerGenre {
|
||||
name: string;
|
||||
picture: DeezerImage;
|
||||
}
|
||||
|
||||
interface DeezerUser {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for Deezer Tracks
|
||||
*/
|
||||
export class DeezerTrack {
|
||||
id: number;
|
||||
title: string;
|
||||
shortTitle: string;
|
||||
url: string;
|
||||
durationInSec: number;
|
||||
rank: number;
|
||||
explicit: boolean;
|
||||
previewURL: string;
|
||||
artist: DeezerArtist;
|
||||
album: DeezerTrackAlbum;
|
||||
type: 'track' | 'playlist' | 'album';
|
||||
|
||||
/**
|
||||
* true for tracks in search results and false if the track was fetched directly.
|
||||
*/
|
||||
partial: boolean;
|
||||
|
||||
trackPosition?: number;
|
||||
diskNumber?: number;
|
||||
releaseDate?: Date;
|
||||
bpm?: number;
|
||||
gain?: number;
|
||||
contributors?: DeezerArtist[];
|
||||
|
||||
constructor(data: any, partial: boolean) {
|
||||
this.id = data.id;
|
||||
this.title = data.title;
|
||||
this.shortTitle = data.title_short;
|
||||
this.url = data.link;
|
||||
this.durationInSec = data.duration;
|
||||
this.rank = data.rank;
|
||||
this.explicit = data.explicit_lyrics;
|
||||
this.previewURL = data.preview;
|
||||
this.artist = new DeezerArtist(data.artist);
|
||||
this.album = new DeezerTrackAlbum(data.album);
|
||||
this.type = 'track';
|
||||
|
||||
this.partial = partial;
|
||||
|
||||
if (!partial) {
|
||||
this.trackPosition = data.track_position;
|
||||
this.diskNumber = data.disk_number;
|
||||
this.releaseDate = new Date(data.release_date);
|
||||
this.bpm = data.bpm;
|
||||
this.gain = data.gain;
|
||||
this.contributors = [];
|
||||
|
||||
data.contributors.forEach((contributor: any) => {
|
||||
this.contributors?.push(new DeezerArtist(contributor));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the missing data for a partial {@link DeezerTrack}.
|
||||
*/
|
||||
async fetch(): Promise<DeezerTrack> {
|
||||
if (!this.partial) return this;
|
||||
|
||||
const response = await request(`https://api.deezer.com/track/${this.id}/`).catch((err: Error) => err);
|
||||
|
||||
if (response instanceof Error) throw response;
|
||||
const jsonData = JSON.parse(response);
|
||||
|
||||
this.partial = false;
|
||||
|
||||
this.trackPosition = jsonData.track_position;
|
||||
this.diskNumber = jsonData.disk_number;
|
||||
this.releaseDate = new Date(jsonData.release_date);
|
||||
this.bpm = jsonData.bpm;
|
||||
this.gain = jsonData.gain;
|
||||
this.contributors = [];
|
||||
|
||||
jsonData.contributors.forEach((contributor: any) => {
|
||||
this.contributors?.push(new DeezerArtist(contributor));
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
title: this.title,
|
||||
shortTitle: this.shortTitle,
|
||||
url: this.url,
|
||||
durationInSec: this.durationInSec,
|
||||
rank: this.rank,
|
||||
explicit: this.explicit,
|
||||
previewURL: this.previewURL,
|
||||
artist: this.artist,
|
||||
album: this.album,
|
||||
type: this.type,
|
||||
trackPosition: this.trackPosition,
|
||||
diskNumber: this.diskNumber,
|
||||
releaseDate: this.releaseDate,
|
||||
bpm: this.bpm,
|
||||
gain: this.gain,
|
||||
contributors: this.contributors
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Class for Deezer Albums
|
||||
*/
|
||||
export class DeezerAlbum {
|
||||
id: number;
|
||||
title: string;
|
||||
url: string;
|
||||
recordType: string;
|
||||
explicit: boolean;
|
||||
artist: DeezerArtist;
|
||||
cover: DeezerImage;
|
||||
type: 'track' | 'playlist' | 'album';
|
||||
tracksCount: number;
|
||||
|
||||
/**
|
||||
* true for albums in search results and false if the album was fetched directly.
|
||||
*/
|
||||
partial: boolean;
|
||||
|
||||
upc?: string;
|
||||
durationInSec?: number;
|
||||
numberOfFans?: number;
|
||||
releaseDate?: Date;
|
||||
available?: boolean;
|
||||
genres?: DeezerGenre[];
|
||||
contributors?: DeezerArtist[];
|
||||
|
||||
tracks: DeezerTrack[];
|
||||
|
||||
constructor(data: any, partial: boolean) {
|
||||
this.id = data.id;
|
||||
this.title = data.title;
|
||||
this.url = data.link;
|
||||
this.recordType = data.record_type;
|
||||
this.explicit = data.explicit_lyrics;
|
||||
this.artist = new DeezerArtist(data.artist);
|
||||
this.type = 'album';
|
||||
this.tracksCount = data.nb_tracks;
|
||||
this.contributors = [];
|
||||
this.genres = [];
|
||||
this.tracks = [];
|
||||
this.cover = {
|
||||
xl: data.cover_xl,
|
||||
big: data.cover_big,
|
||||
medium: data.cover_medium,
|
||||
small: data.cover_small
|
||||
};
|
||||
|
||||
this.partial = partial;
|
||||
|
||||
if (!partial) {
|
||||
this.upc = data.upc;
|
||||
this.durationInSec = data.duration;
|
||||
this.numberOfFans = data.fans;
|
||||
this.releaseDate = new Date(data.release_date);
|
||||
this.available = data.available;
|
||||
|
||||
data.contributors.forEach((contributor: any) => {
|
||||
this.contributors?.push(new DeezerArtist(contributor));
|
||||
});
|
||||
|
||||
data.genres.data.forEach((genre: any) => {
|
||||
this.genres?.push({
|
||||
name: genre.name,
|
||||
picture: {
|
||||
xl: `${genre.picture}?size=xl`,
|
||||
big: `${genre.picture}?size=big`,
|
||||
medium: `${genre.picture}?size=medium`,
|
||||
small: `${genre.picture}?size=small`
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const trackAlbum: any = {
|
||||
id: this.id,
|
||||
title: this.title,
|
||||
cover_xl: this.cover.xl,
|
||||
cover_big: this.cover.big,
|
||||
cover_medium: this.cover.medium,
|
||||
cover_small: this.cover.small,
|
||||
release_date: data.release_date
|
||||
};
|
||||
data.tracks.data.forEach((track: any) => {
|
||||
track.album = trackAlbum;
|
||||
this.tracks.push(new DeezerTrack(track, true));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the missing data for a partial {@link DeezerAlbum}.
|
||||
*/
|
||||
async fetch(): Promise<DeezerAlbum> {
|
||||
if (!this.partial) return this;
|
||||
|
||||
const response = await request(`https://api.deezer.com/album/${this.id}/`).catch((err: Error) => err);
|
||||
|
||||
if (response instanceof Error) throw response;
|
||||
const jsonData = JSON.parse(response);
|
||||
|
||||
this.partial = false;
|
||||
|
||||
this.upc = jsonData.upc;
|
||||
this.durationInSec = jsonData.duration;
|
||||
this.numberOfFans = jsonData.fans;
|
||||
this.releaseDate = new Date(jsonData.release_date);
|
||||
this.available = jsonData.available;
|
||||
this.contributors = [];
|
||||
this.genres = [];
|
||||
this.tracks = [];
|
||||
|
||||
jsonData.contributors.forEach((contributor: any) => {
|
||||
this.contributors?.push(new DeezerArtist(contributor));
|
||||
});
|
||||
|
||||
jsonData.genres.data.forEach((genre: any) => {
|
||||
this.genres?.push({
|
||||
name: genre.name,
|
||||
picture: {
|
||||
xl: `${genre.picture}?size=xl`,
|
||||
big: `${genre.picture}?size=big`,
|
||||
medium: `${genre.picture}?size=medium`,
|
||||
small: `${genre.picture}?size=small`
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const trackAlbum: any = {
|
||||
id: this.id,
|
||||
title: this.title,
|
||||
cover_xl: this.cover.xl,
|
||||
cover_big: this.cover.big,
|
||||
cover_medium: this.cover.medium,
|
||||
cover_small: this.cover.small,
|
||||
release_date: jsonData.release_date
|
||||
};
|
||||
jsonData.tracks.data.forEach((track: any) => {
|
||||
track.album = trackAlbum;
|
||||
this.tracks.push(new DeezerTrack(track, true));
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
title: this.title,
|
||||
url: this.url,
|
||||
recordType: this.recordType,
|
||||
explicit: this.explicit,
|
||||
artist: this.artist,
|
||||
cover: this.cover,
|
||||
type: this.type,
|
||||
upc: this.upc,
|
||||
tracksCount: this.tracksCount,
|
||||
durationInSec: this.durationInSec,
|
||||
numberOfFans: this.numberOfFans,
|
||||
releaseDate: this.releaseDate,
|
||||
available: this.available,
|
||||
genres: this.genres,
|
||||
contributors: this.contributors,
|
||||
tracks: this.tracks.map((track) => track.toJSON())
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Class for Deezer Albums
|
||||
*/
|
||||
export class DeezerPlaylist {
|
||||
id: number;
|
||||
title: string;
|
||||
public: boolean;
|
||||
url: string;
|
||||
picture: DeezerImage;
|
||||
creationDate: Date;
|
||||
type: 'track' | 'playlist' | 'album';
|
||||
creator: DeezerUser;
|
||||
tracksCount: number;
|
||||
|
||||
partial: boolean;
|
||||
|
||||
description?: string;
|
||||
durationInSec?: number;
|
||||
isLoved?: boolean;
|
||||
collaborative?: boolean;
|
||||
fans?: number;
|
||||
|
||||
tracks: DeezerTrack[];
|
||||
|
||||
constructor(data: any, partial: boolean) {
|
||||
this.id = data.id;
|
||||
this.title = data.title;
|
||||
this.public = data.public;
|
||||
this.url = data.link;
|
||||
this.creationDate = new Date(data.creation_date);
|
||||
this.type = 'playlist';
|
||||
this.tracksCount = data.nb_tracks;
|
||||
this.tracks = [];
|
||||
|
||||
this.picture = {
|
||||
xl: data.picture_xl,
|
||||
big: data.picture_big,
|
||||
medium: data.picture_medium,
|
||||
small: data.picture_small
|
||||
};
|
||||
|
||||
if (data.user) {
|
||||
this.creator = {
|
||||
id: data.user.id,
|
||||
name: data.user.name
|
||||
};
|
||||
} else {
|
||||
this.creator = {
|
||||
id: data.creator.id,
|
||||
name: data.creator.name
|
||||
};
|
||||
}
|
||||
|
||||
this.partial = partial;
|
||||
|
||||
if (!partial) {
|
||||
this.description = data.description;
|
||||
this.durationInSec = data.duration;
|
||||
this.isLoved = data.is_loved_track;
|
||||
this.collaborative = data.collaborative;
|
||||
this.fans = data.fans;
|
||||
|
||||
if (this.public) {
|
||||
this.tracks = data.tracks.data.map((track: any) => {
|
||||
return new DeezerTrack(track, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the missing data for a partial {@link DeezerPlaylist} as well as fetching all tracks.
|
||||
* @returns The Deezer playlist object this method was called on.
|
||||
*/
|
||||
async fetch(): Promise<DeezerPlaylist> {
|
||||
if (!this.partial && (this.tracks.length === this.tracksCount || !this.public)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (this.partial) {
|
||||
const response = await request(`https://api.deezer.com/playlist/${this.id}/`).catch((err: Error) => err);
|
||||
|
||||
if (response instanceof Error) throw response;
|
||||
const jsonData = JSON.parse(response);
|
||||
|
||||
this.partial = false;
|
||||
|
||||
this.description = jsonData.description;
|
||||
this.durationInSec = jsonData.duration;
|
||||
this.isLoved = jsonData.is_loved_track;
|
||||
this.collaborative = jsonData.collaborative;
|
||||
this.fans = jsonData.fans;
|
||||
|
||||
if (this.public) {
|
||||
this.tracks = jsonData.tracks.data.map((track: any) => {
|
||||
return new DeezerTrack(track, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const currentTracksCount = this.tracks.length;
|
||||
if (this.public && currentTracksCount !== this.tracksCount) {
|
||||
let missing = this.tracksCount - currentTracksCount;
|
||||
|
||||
if (missing > 1000) missing = 1000;
|
||||
|
||||
const promises: Promise<DeezerTrack[]>[] = [];
|
||||
for (let i = 1; i <= Math.ceil(missing / 100); i++) {
|
||||
promises.push(
|
||||
new Promise(async (resolve, reject) => {
|
||||
const response = await request(
|
||||
`https://api.deezer.com/playlist/${this.id}/tracks?limit=100&index=${i * 100}`
|
||||
).catch((err) => reject(err));
|
||||
|
||||
if (typeof response !== 'string') return;
|
||||
const jsonData = JSON.parse(response);
|
||||
const tracks = jsonData.data.map((track: any) => {
|
||||
return new DeezerTrack(track, true);
|
||||
});
|
||||
|
||||
resolve(tracks);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const results = await Promise.allSettled(promises);
|
||||
const newTracks: DeezerTrack[] = [];
|
||||
|
||||
for (const result of results) {
|
||||
if (result.status === 'fulfilled') {
|
||||
newTracks.push(...result.value);
|
||||
} else {
|
||||
throw result.reason;
|
||||
}
|
||||
}
|
||||
|
||||
this.tracks.push(...newTracks);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
title: this.title,
|
||||
public: this.public,
|
||||
url: this.url,
|
||||
picture: this.picture,
|
||||
creationDate: this.creationDate,
|
||||
type: this.type,
|
||||
creator: this.creator,
|
||||
tracksCount: this.tracksCount,
|
||||
description: this.description,
|
||||
durationInSec: this.durationInSec,
|
||||
isLoved: this.isLoved,
|
||||
collaborative: this.collaborative,
|
||||
fans: this.fans,
|
||||
tracks: this.tracks.map((track) => track.toJSON())
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class DeezerTrackAlbum {
|
||||
id: number;
|
||||
title: string;
|
||||
url: string;
|
||||
cover: DeezerImage;
|
||||
releaseDate?: Date;
|
||||
|
||||
constructor(data: any) {
|
||||
this.id = data.id;
|
||||
this.title = data.title;
|
||||
this.url = `https://www.deezer.com/album/${data.id}/`;
|
||||
this.cover = {
|
||||
xl: data.cover_xl,
|
||||
big: data.cover_big,
|
||||
medium: data.cover_medium,
|
||||
small: data.cover_small
|
||||
};
|
||||
|
||||
if (data.release_date) this.releaseDate = new Date(data.release_date);
|
||||
}
|
||||
}
|
||||
|
||||
class DeezerArtist {
|
||||
id: number;
|
||||
name: string;
|
||||
url: string;
|
||||
picture?: DeezerImage;
|
||||
role?: string;
|
||||
|
||||
constructor(data: any) {
|
||||
this.id = data.id;
|
||||
this.name = data.name;
|
||||
|
||||
this.url = data.link ? data.link : `https://www.deezer.com/artist/${data.id}/`;
|
||||
|
||||
if (data.picture_xl)
|
||||
this.picture = {
|
||||
xl: data.picture_xl,
|
||||
big: data.picture_big,
|
||||
medium: data.picture_medium,
|
||||
small: data.picture_small
|
||||
};
|
||||
|
||||
if (data.role) this.role = data.role;
|
||||
}
|
||||
}
|
||||
212
play-dl/Deezer/index.ts
Normal file
212
play-dl/Deezer/index.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
import { URL } from 'url';
|
||||
import { request, request_resolve_redirect } from '../Request';
|
||||
import { DeezerAlbum, DeezerPlaylist, DeezerTrack } from './classes';
|
||||
|
||||
interface TypeData {
|
||||
type: 'track' | 'playlist' | 'album' | 'search' | 'share' | false;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
interface DeezerSearchOptions {
|
||||
type?: 'track' | 'playlist' | 'album';
|
||||
limit?: number;
|
||||
fuzzy?: boolean;
|
||||
}
|
||||
|
||||
function internalValidate(url: string): TypeData {
|
||||
let urlObj;
|
||||
try {
|
||||
// will throw a TypeError if the input is not a valid URL so we need to catch it
|
||||
urlObj = new URL(url);
|
||||
} catch {
|
||||
return { type: 'search' };
|
||||
}
|
||||
|
||||
if (urlObj.protocol !== 'https:' && urlObj.protocol !== 'http:') {
|
||||
return { type: 'search' };
|
||||
}
|
||||
|
||||
let pathname = urlObj.pathname;
|
||||
if (pathname.endsWith('/')) {
|
||||
pathname = pathname.slice(0, -1);
|
||||
}
|
||||
const path = pathname.split('/');
|
||||
switch (urlObj.hostname) {
|
||||
case 'deezer.com':
|
||||
case 'www.deezer.com': {
|
||||
if (path.length === 4) {
|
||||
const lang = path.splice(1, 1)[0];
|
||||
if (!lang.match(/^[a-z]{2}$/)) {
|
||||
return { type: false };
|
||||
}
|
||||
} else if (path.length !== 3) {
|
||||
return { type: false };
|
||||
}
|
||||
|
||||
if ((path[1] === 'track' || path[1] === 'album' || path[1] === 'playlist') && path[2].match(/^[0-9]+$/)) {
|
||||
return {
|
||||
type: path[1],
|
||||
id: path[2]
|
||||
};
|
||||
} else {
|
||||
return { type: false };
|
||||
}
|
||||
}
|
||||
case 'api.deezer.com': {
|
||||
if (
|
||||
path.length === 3 &&
|
||||
(path[1] === 'track' || path[1] === 'album' || path[1] === 'playlist') &&
|
||||
path[2].match(/^[0-9]+$/)
|
||||
) {
|
||||
return {
|
||||
type: path[1],
|
||||
id: path[2]
|
||||
};
|
||||
} else {
|
||||
return { type: false };
|
||||
}
|
||||
}
|
||||
case 'deezer.page.link': {
|
||||
if (path.length === 2 && path[1].match(/^[A-Za-z0-9]+$/)) {
|
||||
return { type: 'share' };
|
||||
} else {
|
||||
return { type: false };
|
||||
}
|
||||
}
|
||||
default:
|
||||
return { type: 'search' };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared type for Deezer tracks, playlists and albums
|
||||
*/
|
||||
export type Deezer = DeezerTrack | DeezerPlaylist | DeezerAlbum;
|
||||
|
||||
/**
|
||||
* Fetches the information for a track, playlist or album on Deezer
|
||||
* @param url The track, playlist or album URL
|
||||
* @returns A {@link DeezerTrack}, {@link DeezerPlaylist} or {@link DeezerAlbum}
|
||||
* object depending on the provided URL.
|
||||
*/
|
||||
export async function deezer(url: string): Promise<Deezer> {
|
||||
const typeData = internalValidate(url);
|
||||
|
||||
if (!typeData.type || typeData.type === 'search')
|
||||
throw new Error('This is not a Deezer track, playlist or album URL');
|
||||
|
||||
if (typeData.type === 'share') {
|
||||
const resolvedURL = await internalResolve(url);
|
||||
return await deezer(resolvedURL);
|
||||
}
|
||||
|
||||
const response = await request(`https://api.deezer.com/${typeData.type}/${typeData.id}`).catch((err: Error) => err);
|
||||
|
||||
if (response instanceof Error) throw response;
|
||||
|
||||
const jsonData = JSON.parse(response);
|
||||
|
||||
if (jsonData.error) {
|
||||
throw new Error(`Deezer API Error: ${jsonData.error.type}: ${jsonData.error.message}`);
|
||||
}
|
||||
|
||||
switch (typeData.type) {
|
||||
case 'track':
|
||||
return new DeezerTrack(jsonData, false);
|
||||
case 'playlist':
|
||||
return new DeezerPlaylist(jsonData, false);
|
||||
case 'album':
|
||||
return new DeezerAlbum(jsonData, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a Deezer URL
|
||||
* @param url The URL to validate
|
||||
* @returns The type of the URL either 'track', 'playlist', 'album', 'search', 'share' or false.
|
||||
* false means that the provided URL was a wrongly formatted or unsupported Deezer URL.
|
||||
*/
|
||||
export function dz_validate(url: string): 'track' | 'playlist' | 'album' | 'search' | 'share' | false {
|
||||
return internalValidate(url).type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches Deezer for tracks, playlists or albums
|
||||
* @param query The search query
|
||||
* @param options Extra options to configure the search:
|
||||
*
|
||||
* type?: The type to search for `'track'`, `'playlist'` or `'album'`. Defaults to `'track'`.
|
||||
*
|
||||
* limit?: The maximum number of results to return, maximum `100`, defaults to `10`.
|
||||
*
|
||||
* fuzzy?: Whether the search should be fuzzy or only return exact matches. Defaults to `true`.
|
||||
* @returns An array of tracks, playlists or albums
|
||||
*/
|
||||
export async function dz_search(query: string, options: DeezerSearchOptions): Promise<Deezer[]> {
|
||||
let query_ = query.trim();
|
||||
|
||||
const type = options.type ?? 'track';
|
||||
const limit = options.limit ?? 10;
|
||||
const fuzzy = options.fuzzy ?? true;
|
||||
|
||||
if (query_.length === 0) throw new Error('A query is required to search.');
|
||||
if (limit > 100) throw new Error('The maximum search limit for Deezer is 100');
|
||||
if (limit < 1) throw new Error('The minimum search limit for Deezer is 1');
|
||||
if (type !== 'track' && type !== 'album' && type != 'playlist')
|
||||
throw new Error(`"${type}" is not a valid Deezer search type`);
|
||||
|
||||
query_ = encodeURIComponent(query_);
|
||||
const response = await request(
|
||||
`https://api.deezer.com/search/${type}/?q=${query_}&limit=${limit}${fuzzy ? '' : 'strict=on'}`
|
||||
).catch((err: Error) => err);
|
||||
|
||||
if (response instanceof Error) throw response;
|
||||
|
||||
const jsonData = JSON.parse(response);
|
||||
|
||||
if (jsonData.error) {
|
||||
throw new Error(`Deezer API Error: ${jsonData.error.type}: ${jsonData.error.message}`);
|
||||
}
|
||||
|
||||
let results: Deezer[] = [];
|
||||
switch (type) {
|
||||
case 'track':
|
||||
results = jsonData.data.map((track: any) => new DeezerTrack(track, true));
|
||||
break;
|
||||
case 'playlist':
|
||||
results = jsonData.data.map((playlist: any) => new DeezerPlaylist(playlist, true));
|
||||
break;
|
||||
case 'album':
|
||||
results = jsonData.data.map((album: any) => new DeezerAlbum(album, true));
|
||||
break;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async function internalResolve(url: string): Promise<string> {
|
||||
const resolved = await request_resolve_redirect(url);
|
||||
const urlObj = new URL(resolved);
|
||||
urlObj.search = ''; // remove tracking parameters, not needed and also make that URL unnecessarily longer
|
||||
return urlObj.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a Deezer share link (deezer.page.link) to the equivalent Deezer link.
|
||||
*
|
||||
* The {@link deezer} function automatically does this if {@link dz_validate} returns 'share'.
|
||||
*
|
||||
* @param url The Deezer share link (deezer.page.link) to resolve
|
||||
* @returns The resolved URL.
|
||||
*/
|
||||
export async function dz_resolve_share_url(url: string): Promise<string> {
|
||||
const typeData = internalValidate(url);
|
||||
|
||||
if (typeData.type === 'share') {
|
||||
return await internalResolve(url);
|
||||
} else if (typeData.type === 'track' || typeData.type === 'playlist' || typeData.type === 'album') {
|
||||
return url;
|
||||
} else {
|
||||
throw new Error('This is not a valid Deezer URL');
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ export type ProxyOptions = ProxyOpts | string;
|
||||
|
||||
interface RequestOpts extends RequestOptions {
|
||||
body?: string;
|
||||
method?: 'GET' | 'POST';
|
||||
method?: 'GET' | 'POST' | 'HEAD';
|
||||
proxies?: ProxyOptions[];
|
||||
cookies?: boolean;
|
||||
}
|
||||
@@ -119,6 +119,23 @@ export function request(req_url: string, options: RequestOpts = { method: 'GET'
|
||||
});
|
||||
}
|
||||
|
||||
export function request_resolve_redirect(url: string): Promise<string> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let res = await https_getter(url, { method: 'HEAD' }).catch((err: Error) => err);
|
||||
if (res instanceof Error) {
|
||||
reject(res);
|
||||
return;
|
||||
}
|
||||
const statusCode = Number(res.statusCode);
|
||||
if (statusCode >= 300 && statusCode < 400) {
|
||||
const resolved = await request_resolve_redirect(res.headers.location as string);
|
||||
resolve(resolved);
|
||||
} else {
|
||||
resolve(url);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Chooses one random number between max and min number.
|
||||
* @param min Minimum number
|
||||
|
||||
@@ -10,6 +10,7 @@ export {
|
||||
} from './YouTube';
|
||||
export { spotify, sp_validate, refreshToken, is_expired, Spotify } from './Spotify';
|
||||
export { soundcloud, so_validate, SoundCloud, SoundCloudStream, getFreeClientID } from './SoundCloud';
|
||||
export { deezer, dz_validate, dz_search, dz_resolve_share_url, Deezer } from './Deezer';
|
||||
export { setToken } from './token';
|
||||
|
||||
enum AudioPlayerStatus {
|
||||
@@ -26,6 +27,7 @@ interface SearchOptions {
|
||||
youtube?: 'video' | 'playlist' | 'channel';
|
||||
spotify?: 'album' | 'playlist' | 'track';
|
||||
soundcloud?: 'tracks' | 'playlists' | 'albums';
|
||||
deezer?: 'track' | 'playlist' | 'album';
|
||||
};
|
||||
}
|
||||
|
||||
@@ -47,6 +49,7 @@ import { InfoData, stream as yt_stream, StreamOptions, stream_from_info as yt_st
|
||||
import { SoundCloudTrack } from './SoundCloud/classes';
|
||||
import { yt_search } from './YouTube/search';
|
||||
import { EventEmitter } from 'stream';
|
||||
import { Deezer, dz_search, dz_validate } from './Deezer';
|
||||
|
||||
/**
|
||||
* Main stream Command for streaming through various sources
|
||||
@@ -62,6 +65,11 @@ export async function stream(url: string, options: StreamOptions = {}): Promise<
|
||||
'Streaming from Spotify is not supported. Please use search() to find a similar track on YouTube or SoundCloud instead.'
|
||||
);
|
||||
}
|
||||
if (url.indexOf('deezer') !== -1) {
|
||||
throw new Error(
|
||||
'Streaming from Deezer is not supported. Please use search() to find a similar track on YouTube or SoundCloud instead.'
|
||||
);
|
||||
}
|
||||
if (url.indexOf('soundcloud') !== -1) return await so_stream(url, options.quality);
|
||||
else return await yt_stream(url, options);
|
||||
}
|
||||
@@ -70,17 +78,19 @@ export async function stream(url: string, options: StreamOptions = {}): Promise<
|
||||
* Main Search Command for searching through various sources
|
||||
* @param query string to search.
|
||||
* @param options contains limit and source to choose.
|
||||
* @returns Array of YouTube or Spotify or SoundCloud
|
||||
* @returns Array of YouTube or Spotify or SoundCloud or Deezer
|
||||
*/
|
||||
export async function search(
|
||||
query: string,
|
||||
options: SearchOptions = {}
|
||||
): Promise<YouTube[] | Spotify[] | SoundCloud[]> {
|
||||
): Promise<YouTube[] | Spotify[] | SoundCloud[] | Deezer[]> {
|
||||
if (!options.source) options.source = { youtube: 'video' };
|
||||
query = encodeURIComponent(query);
|
||||
if (options.source.youtube) return await yt_search(query, { limit: options.limit, type: options.source.youtube });
|
||||
else if (options.source.spotify) return await sp_search(query, options.source.spotify, options.limit);
|
||||
else if (options.source.soundcloud) return await so_search(query, options.source.soundcloud, options.limit);
|
||||
else if (options.source.deezer)
|
||||
return await dz_search(query, { limit: options.limit, type: options.source.deezer });
|
||||
else throw new Error('Not possible to reach Here LOL. Easter Egg of play-dl if someone get this.');
|
||||
}
|
||||
|
||||
@@ -106,7 +116,19 @@ export async function stream_from_info(
|
||||
export async function validate(
|
||||
url: string
|
||||
): Promise<
|
||||
'so_playlist' | 'so_track' | 'sp_track' | 'sp_album' | 'sp_playlist' | 'yt_video' | 'yt_playlist' | 'search' | false
|
||||
| 'so_playlist'
|
||||
| 'so_track'
|
||||
| 'sp_track'
|
||||
| 'sp_album'
|
||||
| 'sp_playlist'
|
||||
| 'dz_track'
|
||||
| 'dz_playlist'
|
||||
| 'dz_album'
|
||||
| 'dz_share'
|
||||
| 'yt_video'
|
||||
| 'yt_playlist'
|
||||
| 'search'
|
||||
| false
|
||||
> {
|
||||
let check;
|
||||
if (!url.startsWith('https')) return 'search';
|
||||
@@ -116,6 +138,9 @@ export async function validate(
|
||||
} else if (url.indexOf('soundcloud') !== -1) {
|
||||
check = await so_validate(url);
|
||||
return check !== false ? (('so_' + check) as 'so_playlist' | 'so_track') : false;
|
||||
} else if (url.indexOf('deezer') !== -1) {
|
||||
check = dz_validate(url);
|
||||
return check !== false ? (('dz_' + check) as 'dz_track' | 'dz_playlist' | 'dz_album' | 'dz_share') : false;
|
||||
} else {
|
||||
check = yt_validate(url);
|
||||
return check !== false ? (('yt_' + check) as 'yt_video' | 'yt_playlist') : false;
|
||||
|
||||
Reference in New Issue
Block a user