partial playlist implementation
This commit is contained in:
parent
11de5d8038
commit
da5f4a64ba
5 changed files with 174 additions and 25 deletions
|
@ -147,13 +147,13 @@ client.on("interaction", async (interaction) => {
|
||||||
await interaction.defer();
|
await interaction.defer();
|
||||||
|
|
||||||
const query = interaction.options.get("query")!.value! as string;
|
const query = interaction.options.get("query")!.value! as string;
|
||||||
const searchResult = (await player
|
const searchResult = await player
|
||||||
.search(query, {
|
.search(query, {
|
||||||
requestedBy: interaction.user,
|
requestedBy: interaction.user,
|
||||||
searchEngine: interaction.commandName === "soundcloud" ? QueryType.SOUNDCLOUD_SEARCH : QueryType.AUTO
|
searchEngine: interaction.commandName === "soundcloud" ? QueryType.SOUNDCLOUD_SEARCH : QueryType.AUTO
|
||||||
})
|
})
|
||||||
.catch(() => [])) as Track[];
|
.catch(() => {});
|
||||||
if (!searchResult.length) return void interaction.followUp({ content: "No results were found!" });
|
if (!searchResult || !searchResult.tracks.length) return void interaction.followUp({ content: "No results were found!" });
|
||||||
|
|
||||||
const queue = await player.createQueue(interaction.guild, {
|
const queue = await player.createQueue(interaction.guild, {
|
||||||
metadata: interaction.channel as TextChannel
|
metadata: interaction.channel as TextChannel
|
||||||
|
@ -167,7 +167,8 @@ client.on("interaction", async (interaction) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
await interaction.followUp({ content: "⏱ | Loading your track..." });
|
await interaction.followUp({ content: "⏱ | Loading your track..." });
|
||||||
await queue.play(searchResult[0]);
|
searchResult.playlist ? queue.addTracks(searchResult.tracks) : queue.addTrack(searchResult.tracks[0]);
|
||||||
|
if (!queue.playing) await queue.play();
|
||||||
} else if (interaction.commandName === "volume") {
|
} else if (interaction.commandName === "volume") {
|
||||||
await interaction.defer();
|
await interaction.defer();
|
||||||
const queue = player.getQueue(interaction.guildID);
|
const queue = player.getQueue(interaction.guildID);
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { Util } from "./utils/Util";
|
||||||
import Spotify from "spotify-url-info";
|
import Spotify from "spotify-url-info";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { Client as SoundCloud } from "soundcloud-scraper";
|
import { Client as SoundCloud } from "soundcloud-scraper";
|
||||||
|
import { Playlist } from "./Structures/Playlist";
|
||||||
|
|
||||||
const soundcloud = new SoundCloud();
|
const soundcloud = new SoundCloud();
|
||||||
|
|
||||||
|
@ -82,7 +83,7 @@ class DiscordPlayer extends EventEmitter<PlayerEvents> {
|
||||||
* @returns {Promise<Track[]>}
|
* @returns {Promise<Track[]>}
|
||||||
*/
|
*/
|
||||||
async search(query: string | Track, options: SearchOptions) {
|
async search(query: string | Track, options: SearchOptions) {
|
||||||
if (query instanceof Track) return [query];
|
if (query instanceof Track) return { playlist: false, tracks: [query] };
|
||||||
if (!options) throw new Error("DiscordPlayer#search needs search options!");
|
if (!options) throw new Error("DiscordPlayer#search needs search options!");
|
||||||
if (!("searchEngine" in options)) options.searchEngine = QueryType.AUTO;
|
if (!("searchEngine" in options)) options.searchEngine = QueryType.AUTO;
|
||||||
|
|
||||||
|
@ -92,9 +93,10 @@ class DiscordPlayer extends EventEmitter<PlayerEvents> {
|
||||||
case QueryType.YOUTUBE_SEARCH: {
|
case QueryType.YOUTUBE_SEARCH: {
|
||||||
const videos = await YouTube.search(query, {
|
const videos = await YouTube.search(query, {
|
||||||
type: "video"
|
type: "video"
|
||||||
});
|
}).catch(() => {});
|
||||||
|
if (!videos) return { playlist: false, tracks: [] };
|
||||||
|
|
||||||
return videos.map((m) => {
|
const tracks = videos.map((m) => {
|
||||||
(m as any).source = "youtube";
|
(m as any).source = "youtube";
|
||||||
return new Track(this, {
|
return new Track(this, {
|
||||||
title: m.title,
|
title: m.title,
|
||||||
|
@ -104,16 +106,17 @@ class DiscordPlayer extends EventEmitter<PlayerEvents> {
|
||||||
requestedBy: options.requestedBy,
|
requestedBy: options.requestedBy,
|
||||||
thumbnail: m.thumbnail?.displayThumbnailURL("maxresdefault"),
|
thumbnail: m.thumbnail?.displayThumbnailURL("maxresdefault"),
|
||||||
views: m.views,
|
views: m.views,
|
||||||
fromPlaylist: false,
|
|
||||||
duration: m.durationFormatted,
|
duration: m.durationFormatted,
|
||||||
raw: m
|
raw: m
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return { playlist: false, tracks };
|
||||||
}
|
}
|
||||||
case QueryType.SOUNDCLOUD_TRACK:
|
case QueryType.SOUNDCLOUD_TRACK:
|
||||||
case QueryType.SOUNDCLOUD_SEARCH: {
|
case QueryType.SOUNDCLOUD_SEARCH: {
|
||||||
const result: any[] = QueryResolver.resolve(query) === QueryType.SOUNDCLOUD_TRACK ? [{ url: query }] : await soundcloud.search(query, "track").catch(() => {});
|
const result: any[] = QueryResolver.resolve(query) === QueryType.SOUNDCLOUD_TRACK ? [{ url: query }] : await soundcloud.search(query, "track").catch(() => {});
|
||||||
if (!result || !result.length) return [];
|
if (!result || !result.length) return { playlist: false, tracks: [] };
|
||||||
const res: Track[] = [];
|
const res: Track[] = [];
|
||||||
|
|
||||||
for (const r of result) {
|
for (const r of result) {
|
||||||
|
@ -129,7 +132,6 @@ class DiscordPlayer extends EventEmitter<PlayerEvents> {
|
||||||
views: trackInfo.playCount,
|
views: trackInfo.playCount,
|
||||||
author: trackInfo.author.name,
|
author: trackInfo.author.name,
|
||||||
requestedBy: options.requestedBy,
|
requestedBy: options.requestedBy,
|
||||||
fromPlaylist: false,
|
|
||||||
source: "soundcloud",
|
source: "soundcloud",
|
||||||
engine: trackInfo
|
engine: trackInfo
|
||||||
});
|
});
|
||||||
|
@ -137,11 +139,11 @@ class DiscordPlayer extends EventEmitter<PlayerEvents> {
|
||||||
res.push(track);
|
res.push(track);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return { playlist: false, tracks: res };
|
||||||
}
|
}
|
||||||
case QueryType.SPOTIFY_SONG: {
|
case QueryType.SPOTIFY_SONG: {
|
||||||
const spotifyData = await Spotify.getData(query).catch(() => {});
|
const spotifyData = await Spotify.getData(query).catch(() => {});
|
||||||
if (!spotifyData) return [];
|
if (!spotifyData) return { playlist: false, tracks: [] };
|
||||||
const spotifyTrack = new Track(this, {
|
const spotifyTrack = new Track(this, {
|
||||||
title: spotifyData.name,
|
title: spotifyData.name,
|
||||||
description: spotifyData.description ?? "",
|
description: spotifyData.description ?? "",
|
||||||
|
@ -154,14 +156,77 @@ class DiscordPlayer extends EventEmitter<PlayerEvents> {
|
||||||
duration: Util.buildTimeCode(Util.parseMS(spotifyData.duration_ms)),
|
duration: Util.buildTimeCode(Util.parseMS(spotifyData.duration_ms)),
|
||||||
views: 0,
|
views: 0,
|
||||||
requestedBy: options.requestedBy,
|
requestedBy: options.requestedBy,
|
||||||
fromPlaylist: false,
|
|
||||||
source: "spotify"
|
source: "spotify"
|
||||||
});
|
});
|
||||||
|
|
||||||
return [spotifyTrack];
|
return { playlist: false, tracks: [spotifyTrack] };
|
||||||
|
}
|
||||||
|
case QueryType.SPOTIFY_PLAYLIST:
|
||||||
|
case QueryType.SPOTIFY_ALBUM: {
|
||||||
|
const spotifyPlaylist = await Spotify.getData(query).catch(() => {});
|
||||||
|
if (!spotifyPlaylist) return { playlist: false, tracks: [] };
|
||||||
|
|
||||||
|
const playlist = new Playlist(this, {
|
||||||
|
title: spotifyPlaylist.name ?? spotifyPlaylist.title,
|
||||||
|
description: spotifyPlaylist.description ?? "",
|
||||||
|
thumbnail: spotifyPlaylist.images[0]?.url ?? "https://www.scdn.co/i/_global/twitter_card-default.jpg",
|
||||||
|
type: spotifyPlaylist.type,
|
||||||
|
source: "spotify",
|
||||||
|
author:
|
||||||
|
spotifyPlaylist.type !== "playlist"
|
||||||
|
? {
|
||||||
|
name: spotifyPlaylist.artists[0]?.name ?? "Unknown Artist",
|
||||||
|
url: spotifyPlaylist.artists[0]?.external_urls?.spotify ?? null
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
name: spotifyPlaylist.owner?.display_name ?? spotifyPlaylist.owner?.id ?? "Unknown Artist",
|
||||||
|
url: spotifyPlaylist.owner?.external_urls?.spotify ?? null
|
||||||
|
},
|
||||||
|
tracks: [],
|
||||||
|
id: spotifyPlaylist.id,
|
||||||
|
url: spotifyPlaylist.external_urls?.spotify ?? query
|
||||||
|
});
|
||||||
|
|
||||||
|
if (spotifyPlaylist.type !== "playlist") {
|
||||||
|
playlist.tracks = spotifyPlaylist.tracks.items.map((m: any) => {
|
||||||
|
const data = new Track(this, {
|
||||||
|
title: m.name ?? "",
|
||||||
|
description: m.description ?? "",
|
||||||
|
author: m.artists[0]?.name ?? "Unknown Artist",
|
||||||
|
url: m.external_urls?.spotify ?? query,
|
||||||
|
thumbnail: spotifyPlaylist.images[0]?.url ?? "https://www.scdn.co/i/_global/twitter_card-default.jpg",
|
||||||
|
duration: Util.buildTimeCode(Util.parseMS(m.duration_ms)),
|
||||||
|
views: 0,
|
||||||
|
requestedBy: options.requestedBy,
|
||||||
|
playlist,
|
||||||
|
source: "spotify"
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}) as Track[];
|
||||||
|
} else {
|
||||||
|
playlist.tracks = spotifyPlaylist.tracks.items.map((m: any) => {
|
||||||
|
const data = new Track(this, {
|
||||||
|
title: m.track.name ?? "",
|
||||||
|
description: m.track.description ?? "",
|
||||||
|
author: m.track.artists[0]?.name ?? "Unknown Artist",
|
||||||
|
url: m.track.external_urls?.spotify ?? query,
|
||||||
|
thumbnail: m.track.album?.images[0]?.url ?? "https://www.scdn.co/i/_global/twitter_card-default.jpg",
|
||||||
|
duration: Util.buildTimeCode(Util.parseMS(m.track.duration_ms)),
|
||||||
|
views: 0,
|
||||||
|
requestedBy: options.requestedBy,
|
||||||
|
playlist,
|
||||||
|
source: "spotify"
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}) as Track[];
|
||||||
|
}
|
||||||
|
|
||||||
|
return { playlist: true, tracks: playlist.tracks };
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return [];
|
return { playlist: false, tracks: [] };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,56 @@
|
||||||
import { Player } from "../Player";
|
import { Player } from "../Player";
|
||||||
import { Track } from "./Track";
|
import { Track } from "./Track";
|
||||||
|
import { PlaylistInitData, PlaylistJSON, TrackJSON, TrackSource } from "../types/types";
|
||||||
|
|
||||||
class Playlist {
|
class Playlist {
|
||||||
public readonly player: Player;
|
public readonly player: Player;
|
||||||
public tracks: Track[];
|
public tracks: Track[];
|
||||||
|
public title: string;
|
||||||
|
public description: string;
|
||||||
|
public thumbnail: string;
|
||||||
|
public type: "album" | "playlist";
|
||||||
|
public source: TrackSource;
|
||||||
|
public author: {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
public id: string;
|
||||||
|
public url: string;
|
||||||
|
|
||||||
constructor(player: Player, tracks: Track[]) {
|
constructor(player: Player, data: PlaylistInitData) {
|
||||||
this.player = player;
|
this.player = player;
|
||||||
this.tracks = tracks ?? [];
|
this.tracks = data.tracks ?? [];
|
||||||
|
this.author = data.author;
|
||||||
|
this.description = data.description;
|
||||||
|
this.thumbnail = data.thumbnail;
|
||||||
|
this.type = data.type;
|
||||||
|
this.source = data.source;
|
||||||
|
this.id = data.id;
|
||||||
|
this.url = data.url;
|
||||||
|
this.title = data.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
*[Symbol.iterator]() {
|
*[Symbol.iterator]() {
|
||||||
yield* this.tracks;
|
yield* this.tracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toJSON(withTracks = true) {
|
||||||
|
const payload = {
|
||||||
|
id: this.id,
|
||||||
|
url: this.url,
|
||||||
|
title: this.title,
|
||||||
|
description: this.description,
|
||||||
|
thumbnail: this.thumbnail,
|
||||||
|
type: this.type,
|
||||||
|
source: this.source,
|
||||||
|
author: this.author,
|
||||||
|
tracks: [] as TrackJSON[]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (withTracks) payload.tracks = this.tracks.map((m) => m.toJSON());
|
||||||
|
|
||||||
|
return payload as PlaylistJSON;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Playlist };
|
export { Playlist };
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { User, Util } from "discord.js";
|
import { User, Util } from "discord.js";
|
||||||
import { Player } from "../Player";
|
import { Player } from "../Player";
|
||||||
import { RawTrackData } from "../types/types";
|
import { RawTrackData, TrackJSON } from "../types/types";
|
||||||
|
import { Playlist } from "./Playlist";
|
||||||
import { Queue } from "./Queue";
|
import { Queue } from "./Queue";
|
||||||
|
|
||||||
class Track {
|
class Track {
|
||||||
|
@ -13,7 +14,7 @@ class Track {
|
||||||
public duration!: string;
|
public duration!: string;
|
||||||
public views!: number;
|
public views!: number;
|
||||||
public requestedBy!: User;
|
public requestedBy!: User;
|
||||||
public fromPlaylist!: boolean;
|
public playlist?: Playlist;
|
||||||
public readonly raw!: RawTrackData;
|
public readonly raw!: RawTrackData;
|
||||||
public readonly _trackID = Date.now();
|
public readonly _trackID = Date.now();
|
||||||
|
|
||||||
|
@ -102,7 +103,7 @@ class Track {
|
||||||
this.duration = data.duration ?? "";
|
this.duration = data.duration ?? "";
|
||||||
this.views = data.views ?? 0;
|
this.views = data.views ?? 0;
|
||||||
this.requestedBy = data.requestedBy;
|
this.requestedBy = data.requestedBy;
|
||||||
this.fromPlaylist = Boolean(data.fromPlaylist);
|
this.playlist = data.playlist;
|
||||||
|
|
||||||
// raw
|
// raw
|
||||||
Object.defineProperty(this, "raw", { get: () => data.raw ?? data, enumerable: false });
|
Object.defineProperty(this, "raw", { get: () => data.raw ?? data, enumerable: false });
|
||||||
|
@ -164,9 +165,9 @@ class Track {
|
||||||
duration: this.duration,
|
duration: this.duration,
|
||||||
durationMS: this.durationMS,
|
durationMS: this.durationMS,
|
||||||
views: this.views,
|
views: this.views,
|
||||||
requested: this.requestedBy.id,
|
requestedBy: this.requestedBy.id,
|
||||||
fromPlaylist: this.fromPlaylist
|
playlist: this.playlist?.toJSON(false) ?? null
|
||||||
};
|
} as TrackJSON;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { User } from "discord.js";
|
import { Snowflake, User } from "discord.js";
|
||||||
import { downloadOptions } from "ytdl-core";
|
import { downloadOptions } from "ytdl-core";
|
||||||
import { Readable, Duplex } from "stream";
|
import { Readable, Duplex } from "stream";
|
||||||
import { Queue } from "../Structures/Queue";
|
import { Queue } from "../Structures/Queue";
|
||||||
import Track from "../Structures/Track";
|
import Track from "../Structures/Track";
|
||||||
|
import { Playlist } from "../Structures/Playlist";
|
||||||
|
|
||||||
export type FiltersName = keyof QueueFilters;
|
export type FiltersName = keyof QueueFilters;
|
||||||
|
|
||||||
|
@ -48,7 +49,7 @@ export interface RawTrackData {
|
||||||
duration: string;
|
duration: string;
|
||||||
views: number;
|
views: number;
|
||||||
requestedBy: User;
|
requestedBy: User;
|
||||||
fromPlaylist: boolean;
|
playlist?: Playlist;
|
||||||
source?: TrackSource;
|
source?: TrackSource;
|
||||||
engine?: any;
|
engine?: any;
|
||||||
live?: boolean;
|
live?: boolean;
|
||||||
|
@ -162,3 +163,46 @@ export enum QueueRepeatMode {
|
||||||
TRACK = 1,
|
TRACK = 1,
|
||||||
QUEUE = 2
|
QUEUE = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PlaylistInitData {
|
||||||
|
tracks: Track[];
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
thumbnail: string;
|
||||||
|
type: "album" | "playlist";
|
||||||
|
source: TrackSource;
|
||||||
|
author: {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
id: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TrackJSON {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
author: string;
|
||||||
|
url: string;
|
||||||
|
thumbnail: string;
|
||||||
|
duration: string;
|
||||||
|
durationMS: number;
|
||||||
|
views: number;
|
||||||
|
requestedBy: Snowflake;
|
||||||
|
playlist?: PlaylistJSON;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlaylistJSON {
|
||||||
|
id: string;
|
||||||
|
url: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
thumbnail: string;
|
||||||
|
type: "album" | "playlist";
|
||||||
|
source: TrackSource;
|
||||||
|
author: {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
tracks: TrackJSON[];
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue