Files
Yumi/src/providers/DiscordMusicPlayer.ts
T

633 lines
20 KiB
TypeScript
Raw Normal View History

2022-06-29 15:27:42 +07:00
import {
VoiceChannel,
Snowflake,
StageChannel,
Guild,
PermissionsBitField,
2022-06-29 18:59:06 +07:00
BaseGuildVoiceChannel,
BaseGuildTextChannel
2022-06-29 15:27:42 +07:00
} from 'discord.js';
2022-06-06 14:59:53 +07:00
import {
AudioPlayer,
VoiceConnection,
createAudioPlayer,
joinVoiceChannel,
createAudioResource,
VoiceConnectionStatus,
AudioPlayerStatus,
NoSubscriberBehavior,
VoiceConnectionState,
AudioPlayerError,
DiscordGatewayAdapterCreator
} from '@discordjs/voice';
2022-07-01 22:04:37 +07:00
import playdl, { Spotify, SpotifyPlaylist, SpotifyTrack, YouTubeVideo } from 'play-dl';
2024-08-17 21:11:09 +07:00
import NekoMelody, { Player } from '../../NekoMelody/src/index';
import { YtDlpProvider } from '../../NekoMelody/src/providers';
2022-06-06 14:59:53 +07:00
import DiscordProvider from './Discord';
import Environment from './Environment';
2022-07-15 14:57:40 +07:00
import Logger from '../libs/Logger';
2024-08-17 21:11:09 +07:00
import EventEmitter from 'events';
import { AudioInformation } from '../../NekoMelody/src/providers/base';
2022-01-30 20:09:54 +07:00
2023-04-23 18:54:32 +07:00
const LOGGING_TAG = '[DiscordMusicPlayer]';
2022-07-01 22:04:37 +07:00
export type ValidTracks = YouTubeVideo | SpotifyTrack;
declare class YouTubeThumbnail {
url: string;
width: number;
height: number;
constructor(data: any);
toJSON(): {
url: string;
width: number;
height: number;
};
}
interface SpotifyThumbnail {
height: number;
width: number;
url: string;
}
2022-01-30 20:09:54 +07:00
2022-07-18 15:01:37 +07:00
interface TokenOptions {
spotify?: {
client_id: string;
client_secret: string;
refresh_token: string;
market: string;
};
soundcloud?: {
client_id: string;
};
youtube?: {
cookie: string;
};
useragent?: string[];
2022-01-30 21:35:19 +07:00
}
2022-07-01 22:04:37 +07:00
2022-07-18 15:01:37 +07:00
let tokenObject: TokenOptions = {};
if (Environment.get().YOUTUBE_COOKIE_BASE64) {
2023-04-23 18:54:32 +07:00
Logger.debug(LOGGING_TAG, 'Setting YouTube cookie');
2022-07-18 15:01:37 +07:00
tokenObject.youtube = {
cookie: Buffer.from(Environment.get().YOUTUBE_COOKIE_BASE64, 'base64').toString()
2023-03-05 14:46:25 +07:00
};
2022-07-18 15:01:37 +07:00
}
2023-03-05 14:46:25 +07:00
if (
Environment.get().SPOTIFY_CLIENT_ID &&
Environment.get().SPOTIFY_CLIENT_SECRET &&
Environment.get().SPOTIFY_REFRESH_TOKEN &&
Environment.get().SPOTIFY_CLIENT_MARKET
) {
2023-04-23 18:54:32 +07:00
Logger.debug(LOGGING_TAG, 'Setting Spotify token');
2022-07-18 15:01:37 +07:00
tokenObject.spotify = {
client_id: Environment.get().SPOTIFY_CLIENT_ID,
client_secret: Environment.get().SPOTIFY_CLIENT_SECRET,
refresh_token: Environment.get().SPOTIFY_REFRESH_TOKEN,
market: Environment.get().SPOTIFY_CLIENT_MARKET
2023-03-05 14:46:25 +07:00
};
2022-07-18 15:01:37 +07:00
}
playdl.setToken(tokenObject);
2022-07-01 22:04:37 +07:00
export class TrackUtils {
public static getTitle(track: ValidTracks) {
if (track instanceof YouTubeVideo) {
return track.title;
} else if (track instanceof SpotifyTrack) {
2023-03-05 14:46:25 +07:00
let artistNames = '';
for (let artist of track.artists) artistNames += artist.name + ' ';
2022-07-01 22:04:37 +07:00
2023-03-05 14:46:25 +07:00
return `${artistNames} - ${track.name}`;
2022-07-01 22:04:37 +07:00
} else {
throw new Error('Invalid track type');
}
}
public static async getThumbnails(track: ValidTracks) {
if (track instanceof YouTubeVideo) {
return track.thumbnails;
} else if (track instanceof SpotifyTrack) {
2023-03-05 14:46:25 +07:00
if (track.thumbnail) return [track.thumbnail];
2022-07-01 22:04:37 +07:00
else {
// Try to find the thumbnail again
const result = await DiscordMusicPlayer_Instance.searchSpotifyBySpotifyLink(track);
2023-03-05 14:46:25 +07:00
if (!result) return null;
if (result.thumbnail) return [result.thumbnail];
2022-07-01 22:04:37 +07:00
return null;
}
} else {
throw new Error('Invalid track type');
}
}
2023-03-05 14:46:25 +07:00
public static getHighestResolutionThumbnail(thumbnails: YouTubeThumbnail[] | SpotifyThumbnail[] | null) {
if (!thumbnails) return null;
2022-07-01 22:04:37 +07:00
return (thumbnails as any[]).reduce((prev: any, current: any) =>
prev.height * prev.width > current.height * current.width ? prev : current
);
}
}
2022-01-30 20:09:54 +07:00
export class Queue {
2022-06-06 14:59:53 +07:00
public track: ValidTracks[] = [];
2022-01-30 20:09:54 +07:00
}
export class YouTubeLink {
public videoId: string;
public list?: string;
constructor(videoId: string, list: string) {
this.videoId = videoId;
this.list = list;
}
}
2022-07-01 22:04:37 +07:00
export class SpotifyLink {
public id: string;
public type: 'track' | 'playlist' | 'album';
public url: string;
2023-03-05 14:46:25 +07:00
constructor(id: string, type: 'track' | 'playlist' | 'album', url: string) {
2022-07-01 22:04:37 +07:00
this.id = id;
this.type = type;
this.url = url;
}
}
2022-01-30 20:09:54 +07:00
export class PlayerPlayingEvent {
2022-01-30 21:35:56 +07:00
public instance: DiscordMusicPlayerInstance;
2022-01-30 20:09:54 +07:00
constructor(instance: DiscordMusicPlayerInstance) {
this.instance = instance;
}
}
2022-02-27 17:15:20 +07:00
export class PlayerErrorEvent {
public instance: DiscordMusicPlayerInstance;
public error: Error;
constructor(instance: DiscordMusicPlayerInstance, error: Error) {
this.instance = instance;
this.error = error;
}
}
export class VoiceDisconnectedEvent {
public instance: DiscordMusicPlayerInstance;
constructor(instance: DiscordMusicPlayerInstance) {
this.instance = instance;
}
}
2022-03-03 23:57:30 +07:00
export enum DiscordMusicPlayerLoopMode {
2022-06-06 14:59:53 +07:00
None = 'none',
Current = 'current'
2022-03-03 23:57:30 +07:00
}
2023-04-23 18:54:32 +07:00
2022-01-30 20:09:54 +07:00
export class DiscordMusicPlayerInstance {
public queue: Queue;
2024-08-17 21:11:09 +07:00
public discordPlayer: AudioPlayer;
public nekoPlayer: Player;
2022-06-29 18:59:06 +07:00
public textChannel?: BaseGuildTextChannel | BaseGuildVoiceChannel;
2022-06-06 14:59:53 +07:00
public voiceChannel: VoiceChannel | StageChannel;
2022-01-30 20:09:54 +07:00
public voiceConnection?: VoiceConnection;
2022-05-01 16:56:37 +07:00
public previousTrack?: ValidTracks;
2022-01-30 20:09:54 +07:00
2022-06-06 02:18:41 +07:00
public paused: boolean = false;
2022-03-03 23:57:30 +07:00
public loopMode: DiscordMusicPlayerLoopMode = DiscordMusicPlayerLoopMode.None;
2022-01-30 20:09:54 +07:00
public readonly events: EventEmitter;
2024-08-17 21:11:09 +07:00
private providers = [new YtDlpProvider()];
2022-06-06 14:59:53 +07:00
constructor({ voiceChannel }: { voiceChannel: VoiceChannel | StageChannel }) {
2022-01-30 20:09:54 +07:00
this.queue = new Queue();
2024-08-17 21:11:09 +07:00
this.discordPlayer = createAudioPlayer({
2022-01-30 20:09:54 +07:00
behaviors: {
noSubscriber: NoSubscriberBehavior.Pause,
maxMissedFrames: 1000
}
});
2024-08-17 21:11:09 +07:00
this.nekoPlayer = NekoMelody.createPlayer(this.providers);
2022-01-30 20:09:54 +07:00
this.voiceChannel = voiceChannel;
this.events = new EventEmitter();
2024-08-17 21:11:09 +07:00
// this.player.on(AudioPlayerStatus.Idle, async (oldStage, newStage) => {
// //The player stopped
// if (newStage.status === AudioPlayerStatus.Idle && oldStage.status !== AudioPlayerStatus.Idle) {
// // Loop mode is set to current song
// if (this.loopMode === DiscordMusicPlayerLoopMode.Current) {
// if (this.queue.track.length !== 0) {
// this.previousTrack = this.queue.track[0];
// this.playTrack(this.queue.track[0]);
// }
// return;
// }
2022-03-03 23:57:30 +07:00
2024-08-17 21:11:09 +07:00
// // There are more songs in the queue, remove finished song and play the next one
// if (this.queue.track.length !== 0) {
// let previousTrack = this.queue.track.shift();
// if (previousTrack) this.previousTrack = previousTrack;
2022-05-01 16:56:37 +07:00
2024-08-17 21:11:09 +07:00
// if (this.queue.track.length > 0) {
// this.playTrack(this.queue.track[0]);
// }
// }
// }
// });
2022-01-30 20:09:54 +07:00
2024-08-17 21:11:09 +07:00
// this.player.on(AudioPlayerStatus.Playing, (oldState: any, newState: any) => {
// this.events.emit('playing', new PlayerPlayingEvent(this));
// });
// this.player.on('error', (error: Error) => {
// this.events.emit('error', new PlayerErrorEvent(this, error));
// });
this.nekoPlayer.on('play', (information: AudioInformation) => {
if (!this.nekoPlayer.stream) throw new Error('No input stream');
const resource = createAudioResource(this.nekoPlayer.stream, {
//inlineVolume: true,
});
this.discordPlayer.play(resource);
this.nekoPlayer.startCurrentStream();
2022-01-30 20:09:54 +07:00
this.events.emit('playing', new PlayerPlayingEvent(this));
2022-02-27 17:15:20 +07:00
2024-08-17 21:11:09 +07:00
this.discordPlayer.on('stateChange', (oldState, newState) => {
console.log('State change', oldState.status, newState.status);
if (oldState.status === 'playing' && newState.status === 'idle') {
this.nekoPlayer.endCurrentStream();
}
});
2022-02-27 17:15:20 +07:00
});
2022-01-30 20:09:54 +07:00
}
2023-03-05 14:46:25 +07:00
public joinVoiceChannel(
voiceChannel: VoiceChannel | StageChannel,
textChannel?: BaseGuildTextChannel | BaseGuildVoiceChannel
) {
2022-01-30 20:09:54 +07:00
const permissions = voiceChannel.permissionsFor(DiscordProvider.client.user!);
2022-06-29 15:27:42 +07:00
if (!permissions || !voiceChannel.joinable || !permissions.has(PermissionsBitField.Flags.Connect))
2022-06-06 14:59:53 +07:00
throw new Error('No permissions');
2022-01-30 20:09:54 +07:00
2022-06-06 14:59:53 +07:00
if (textChannel) this.textChannel = textChannel;
2022-01-30 20:09:54 +07:00
this.voiceConnection = joinVoiceChannel({
channelId: this.voiceChannel.id,
guildId: this.voiceChannel.guild.id,
2022-06-29 15:27:42 +07:00
adapterCreator: this.voiceChannel.guild.voiceAdapterCreator as DiscordGatewayAdapterCreator
2022-01-30 20:09:54 +07:00
});
2024-08-17 21:11:09 +07:00
this.voiceConnection.subscribe(this.discordPlayer);
2022-06-06 14:59:53 +07:00
this.voiceConnection.on(
VoiceConnectionStatus.Ready,
2022-06-29 15:27:42 +07:00
async (oldState: VoiceConnectionState, newState: VoiceConnectionState) => {
let guild = DiscordProvider.client.guilds.cache.get(voiceChannel.guild.id);
if (guild) {
let currentVC = guild?.members.me?.voice.channel;
if (currentVC && currentVC.id !== this.voiceChannel.id) {
this.voiceChannel = currentVC;
}
2022-02-27 17:15:20 +07:00
}
2022-06-06 14:59:53 +07:00
}
);
this.voiceConnection.on(
VoiceConnectionStatus.Disconnected,
(oldState: VoiceConnectionState, newState: VoiceConnectionState) => {
2022-06-29 15:27:42 +07:00
setTimeout(async () => {
let guild = DiscordProvider.client.guilds.cache.get(voiceChannel.guildId);
2023-03-05 14:46:25 +07:00
if (guild) {
2022-06-29 15:27:42 +07:00
if (!guild?.members.me?.voice.channelId) {
this.events.emit('disconnect', new VoiceDisconnectedEvent(this));
}
2022-06-06 14:59:53 +07:00
}
2022-06-28 14:51:05 +07:00
}, 2000);
2022-06-06 14:59:53 +07:00
}
);
2022-01-30 20:09:54 +07:00
}
public async leaveVoiceChannel() {
2024-08-17 21:11:09 +07:00
if (this.discordPlayer) this.discordPlayer.pause();
2022-01-30 20:09:54 +07:00
2022-06-29 15:27:42 +07:00
const guild = DiscordProvider.client.guilds.cache.get(this.voiceChannel.guild.id);
if (!guild) return;
if (guild.members.me?.voice) this.voiceConnection?.disconnect();
2022-06-06 02:18:41 +07:00
}
2022-01-30 21:35:56 +07:00
2022-06-06 02:18:41 +07:00
public async pausePlayer() {
2024-08-17 21:11:09 +07:00
if (this.paused || !this.discordPlayer) return;
if (!this.discordPlayer.pause(true)) throw new Error('Unable to pause player');
2022-06-06 02:18:41 +07:00
this.paused = true;
}
public async resumePlayer() {
2024-08-17 21:11:09 +07:00
if (!this.paused || !this.discordPlayer) return;
if (!this.discordPlayer.unpause()) throw new Error('Unable to resume player');
2022-06-06 02:18:41 +07:00
this.paused = false;
2022-01-30 20:09:54 +07:00
}
2024-08-17 21:11:09 +07:00
public async addTrackToQueue(track: ValidTracks) {
return await this.nekoPlayer.enqueue(track.url);
2022-01-30 20:09:54 +07:00
}
public async skipTrack() {
2022-06-06 14:59:53 +07:00
if (!this.voiceConnection) throw new Error('No voice connection');
2022-01-30 20:09:54 +07:00
2024-08-17 21:11:09 +07:00
if (this.nekoPlayer.getQueue().length === 0) return;
await this.nekoPlayer.skip();
if (this.paused) this.paused = false;
2022-01-30 20:09:54 +07:00
}
2022-07-18 14:16:26 +07:00
public clearQueue() {
if (!this.voiceConnection) throw new Error('No voice connection');
2023-03-05 14:46:25 +07:00
if (!this.queue.track || this.queue.track.length === 0) return;
2022-07-18 14:16:26 +07:00
this.queue.track = [this.queue.track[0]];
}
2022-07-18 14:53:35 +07:00
public shuffleQueue() {
if (!this.voiceConnection) throw new Error('No voice connection');
const shuffleFixedFirst = (queue: ValidTracks[]) => {
2023-03-05 14:46:25 +07:00
if (queue.length <= 2) return queue;
2022-07-18 14:53:35 +07:00
const fixedFirst = queue.shift();
2023-03-05 14:46:25 +07:00
if (!fixedFirst) return queue;
2022-07-18 14:53:35 +07:00
queue.sort(() => Math.random() - 0.5);
queue.unshift(fixedFirst);
return queue;
2023-03-05 14:46:25 +07:00
};
2022-07-18 14:53:35 +07:00
2023-03-05 14:46:25 +07:00
if (!this.queue.track || this.queue.track.length === 0) return;
2022-07-18 14:53:35 +07:00
this.queue.track = shuffleFixedFirst(this.queue.track);
}
2022-03-03 23:57:30 +07:00
public setLoopMode(mode: DiscordMusicPlayerLoopMode) {
this.loopMode = mode;
}
public getLoopMode() {
return this.loopMode;
}
2022-05-01 16:56:37 +07:00
public getPreviousTrack(): ValidTracks | undefined {
return this.previousTrack;
}
2024-08-17 21:11:09 +07:00
public getQueue() {
return this.nekoPlayer.getQueue();
2022-07-01 22:04:37 +07:00
}
2022-07-18 14:16:26 +07:00
public isReady() {
if (!this.voiceConnection) return false;
return this.voiceConnection.state.status === VoiceConnectionStatus.Ready;
}
public isConnected() {
if (!this.voiceConnection) return false;
if (this.voiceConnection.state.status === VoiceConnectionStatus.Destroyed) return false;
if (this.voiceConnection.state.status === VoiceConnectionStatus.Disconnected) return false;
return true;
}
2023-03-05 14:46:25 +07:00
2022-06-06 02:18:41 +07:00
public isPaused() {
return this.paused;
}
2022-01-30 20:09:54 +07:00
public async destroy() {
await this.leaveVoiceChannel();
2022-01-30 21:35:56 +07:00
if (this.voiceConnection) {
2022-06-06 14:59:53 +07:00
if (this.voiceConnection.state.status !== 'destroyed') this.voiceConnection.destroy();
2022-01-30 20:09:54 +07:00
}
2022-01-30 21:35:56 +07:00
2024-08-17 21:11:09 +07:00
if (this.discordPlayer) {
this.discordPlayer.stop(true);
2022-01-30 20:09:54 +07:00
}
this.textChannel = undefined;
this.voiceConnection = undefined;
}
2022-02-27 17:15:20 +07:00
public async _fake_error_on_player() {
2022-03-03 22:18:34 +07:00
const stream = 'https://fakestream:42069/fake/stream/fake/audio/fake.mp3';
const resource = createAudioResource(stream);
2024-08-17 21:11:09 +07:00
this.discordPlayer.play(resource);
//this.player.emit('error', new AudioPlayerError(new Error('Music player was manually crashed'), null!));
2022-02-27 17:15:20 +07:00
}
2022-01-30 20:09:54 +07:00
}
class DiscordMusicPlayer {
public GuildQueue = new Map();
2022-06-06 14:59:53 +07:00
public getGuildInstance(guildId: Snowflake): DiscordMusicPlayerInstance | null {
2022-01-30 21:35:56 +07:00
if (!this.isGuildInstanceExists(guildId)) return null;
2022-01-30 20:09:54 +07:00
return this.GuildQueue.get(guildId);
}
public isGuildInstanceExists(guildId: Snowflake) {
2022-01-30 21:35:56 +07:00
return this.GuildQueue.has(guildId);
2022-01-30 20:09:54 +07:00
}
2022-06-06 14:59:53 +07:00
public createGuildInstance(guildId: Snowflake, voiceChannel: VoiceChannel | StageChannel) {
2022-01-30 20:09:54 +07:00
this.GuildQueue.set(guildId, new DiscordMusicPlayerInstance({ voiceChannel }));
}
2022-06-06 14:59:53 +07:00
public async destoryGuildInstance(guild: Guild | Snowflake) {
2022-01-30 20:09:54 +07:00
let guildId: Snowflake = guild instanceof Guild ? guild.id : guild;
2022-01-30 21:35:56 +07:00
if (this.isGuildInstanceExists(guildId)) {
2022-01-30 20:09:54 +07:00
await this.GuildQueue.get(guildId).destroy();
this.GuildQueue.delete(guildId);
}
}
public async searchYouTubeByQuery(query: string) {
2022-06-06 14:59:53 +07:00
const searched: YouTubeVideo[] = await playdl.search(query, {
source: { youtube: 'video' }
});
2023-04-23 18:54:32 +07:00
Logger.verbose(
LOGGING_TAG,
`Search YouTube by query: ${query}, Total result: ${searched.length}, ${JSON.stringify(searched)}`
);
2022-01-30 20:09:54 +07:00
if (searched.length == 0) return null;
return searched;
}
2022-07-01 22:04:37 +07:00
public async searchSpotifyBySpotifyLink(spotifyLink: SpotifyLink) {
2023-04-23 18:54:32 +07:00
if (playdl.is_expired()) {
Logger.debug(LOGGING_TAG, 'Spotify token expired, refreshing...');
await playdl.refreshToken();
}
2023-03-05 14:46:25 +07:00
if (spotifyLink.type != 'track') return;
2022-07-01 22:04:37 +07:00
// Fetch data from spotify
2023-03-05 14:46:25 +07:00
const searched: Spotify = await playdl
.spotify('https://open.spotify.com/track/' + spotifyLink.id)
.catch((err) => {
Logger.error(err.message);
throw new Error('Error while searching on Spotify');
});
2023-04-23 18:54:32 +07:00
Logger.verbose(LOGGING_TAG, `Search Spotify by link: ${spotifyLink.id}, ${JSON.stringify(searched)}`);
2023-03-05 14:46:25 +07:00
if (!(searched instanceof SpotifyTrack)) return;
2022-07-01 22:04:37 +07:00
if (!searched) return null;
const track = searched as unknown as SpotifyTrack;
return track;
}
2022-02-27 21:05:45 +07:00
public async getYouTubeSongsInPlayList(youtubeLink: string) {
2023-04-23 18:54:32 +07:00
const result = await playdl.playlist_info(youtubeLink, {
2022-02-27 21:40:59 +07:00
incomplete: true
});
2023-04-23 18:54:32 +07:00
Logger.verbose(LOGGING_TAG, `Get YouTube songs in playlist: ${youtubeLink}, ${JSON.stringify(result)}`);
return result;
2022-02-27 21:05:45 +07:00
}
2022-07-01 22:04:37 +07:00
public async getSpotifySongsInPlayList(spotifyLink: string) {
2023-04-23 18:54:32 +07:00
if (playdl.is_expired()) {
Logger.debug(LOGGING_TAG, 'Spotify token expired, refreshing...');
await playdl.refreshToken();
}
2022-07-15 14:57:40 +07:00
const result = await playdl.spotify(spotifyLink).catch((err) => {
Logger.error(err.message);
throw new Error('Error while searching on Spotify');
});
2022-07-01 22:04:37 +07:00
2023-04-23 18:54:32 +07:00
Logger.verbose(LOGGING_TAG, `Get Spotify songs in playlist: ${spotifyLink}, ${JSON.stringify(result)}`);
2023-03-05 14:46:25 +07:00
if (!(result.type == 'playlist' || result.type == 'album')) throw new Error('Not a spotify playlist');
2022-07-01 22:04:37 +07:00
return result as unknown as SpotifyPlaylist;
}
2022-01-30 20:09:54 +07:00
public isYouTubeLink(link: string): boolean {
try {
this.parseYouTubeLink(link);
return true;
} catch (err) {
return false;
}
}
2022-07-01 22:04:37 +07:00
public isSpotifyLink(link: string): boolean {
try {
this.parseSpotifyLink(link);
return true;
} catch (err) {
return false;
}
}
2022-01-30 20:09:54 +07:00
public parseYouTubeLink(query: string): YouTubeLink {
2022-06-06 14:59:53 +07:00
if (
query.startsWith('https://www.youtube.com/watch?v=') ||
2022-07-12 12:27:52 +07:00
query.startsWith('http://www.youtube.com/watch?v=') ||
query.startsWith('https://music.youtube.com/watch?v=') ||
query.startsWith('https://music.youtube.com/watch?v=')
2022-06-06 14:59:53 +07:00
) {
2022-01-30 20:09:54 +07:00
let data = this.parseURLQuery(query);
2022-06-06 14:59:53 +07:00
if (!data.v) throw new Error('YouTube link is invalid');
2022-01-30 20:09:54 +07:00
return {
videoId: data.v,
2022-06-06 14:59:53 +07:00
list: data.list ? (data.list !== 'RDMM' ? data.list : undefined) : undefined
};
} else if (query.startsWith('https://youtu.be/') || query.startsWith('http://youtu.be/')) {
2022-01-30 20:09:54 +07:00
//Get youtube video id after the url
let videoId = query.split('/')[3];
if (!videoId) throw new Error('YouTube link is invalid');
if (query.split('/')[4]) throw new Error('YouTube link is invalid');
2023-04-23 19:50:43 +07:00
return {
videoId: videoId
};
} else if (
query.startsWith('https://www.youtube.com/shorts/') ||
query.startsWith('http://www.youtube.com/shorts/')
) {
let videoId = query.split('/')[4];
if (!videoId) throw new Error('YouTube link is invalid');
if (query.split('/')[5]) throw new Error('YouTube link is invalid');
2022-01-30 20:09:54 +07:00
return {
videoId: videoId
2022-06-06 14:59:53 +07:00
};
} else if (
query.startsWith('https://www.youtube.com/playlist?list=') ||
query.startsWith('http://www.youtube.com/playlist?list=')
) {
2022-02-27 21:40:59 +07:00
let listId = query.split('?list=')[1];
return {
2022-06-06 14:59:53 +07:00
videoId: '',
2022-02-27 21:40:59 +07:00
list: listId
2022-06-06 14:59:53 +07:00
};
} else {
2022-01-30 20:09:54 +07:00
throw new Error('YouTube link is invalid');
}
}
2022-07-01 22:04:37 +07:00
public parseSpotifyLink(query: string): SpotifyLink {
if (query.startsWith('https://open.spotify.com/track/')) {
let id = query.split('/')[4].split(/[?#]/)[0];
return {
id: id,
type: 'track',
url: query.split(/[?#]/)[0]
};
2023-03-05 14:46:25 +07:00
} else if (query.startsWith('https://open.spotify.com/album/')) {
2022-07-01 22:04:37 +07:00
let id = query.split('/')[4].split(/[?#]/)[0];
return {
id: id,
type: 'album',
url: query.split(/[?#]/)[0]
};
2023-03-05 14:46:25 +07:00
} else if (query.startsWith('https://open.spotify.com/playlist/')) {
2022-07-01 22:04:37 +07:00
let id = query.split('/')[4].split(/[?#]/)[0];
return {
id: id,
type: 'playlist',
url: query.split(/[?#]/)[0]
};
2023-03-05 14:46:25 +07:00
} else {
2022-07-01 22:04:37 +07:00
throw new Error('Spotify link is invalid');
}
}
2022-01-30 20:09:54 +07:00
private parseURLQuery(query: string) {
let queryObject: any = {};
if (query.indexOf('?') >= 0) {
let queryString = query.split('?')[1];
let queryArray = queryString.split('&');
for (let i = 0; i < queryArray.length; i++) {
let queryPair = queryArray[i].split('=');
queryObject[queryPair[0]] = queryPair[1];
}
}
return queryObject;
}
}
const DiscordMusicPlayer_Instance = new DiscordMusicPlayer();
2022-06-06 14:59:53 +07:00
export default DiscordMusicPlayer_Instance;