From fac26620252156e60ac03dd64fd4f98b65132d65 Mon Sep 17 00:00:00 2001 From: Snowflake107 Date: Thu, 10 Jun 2021 01:12:29 +0545 Subject: [PATCH] feat: base --- package.json | 1 + src/Player.ts | 19 +++-- src/Structures/Playlist.ts | 7 +- src/Structures/Track.ts | 2 +- src/VoiceNative/VoiceAdapter.ts | 89 +++++++++++++++++++++ src/VoiceNative/VoiceSubscriptionManager.ts | 1 + src/utils/Constants.ts | 4 +- src/utils/PlayerError.ts | 2 +- src/utils/Util.ts | 9 ++- 9 files changed, 116 insertions(+), 18 deletions(-) create mode 100644 src/VoiceNative/VoiceAdapter.ts create mode 100644 src/VoiceNative/VoiceSubscriptionManager.ts diff --git a/package.json b/package.json index 875afb6..fffec43 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "@discordjs/opus": "^0.5.0", "@types/node": "^14.14.41", "@types/ws": "^7.4.1", + "discord-api-types": "^0.18.1", "discord.js": "^13.0.0-dev.dda5ee2e9f0839d3e42d25114ae1b47355cdfd27", "discord.js-docgen": "discordjs/docgen#ts-patch", "jsdoc-babel": "^0.5.0", diff --git a/src/Player.ts b/src/Player.ts index 76e6820..d10a9f7 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -30,10 +30,15 @@ export class Player extends EventEmitter { return new Promise((resolve) => { if (this.queues.has(message.guild.id)) return this.queues.get(message.guild.id); const channel = message.member.voice?.channel; - if (!channel) return void this.emit( - PlayerEvents.ERROR, - new PlayerError('Voice connection is not available in this server!', PlayerErrorEventCodes.NOT_CONNECTED, message) - ); + if (!channel) + return void this.emit( + PlayerEvents.ERROR, + new PlayerError( + 'Voice connection is not available in this server!', + PlayerErrorEventCodes.NOT_CONNECTED, + message + ) + ); const queue = new Queue(this, message.guild); void this.queues.set(message.guild.id, queue); @@ -57,7 +62,7 @@ export class Player extends EventEmitter { }); return queue; - }) + }); } public getQueue(message: Message) { @@ -81,7 +86,7 @@ export class Player extends EventEmitter { if (query instanceof Track) track = query; else { if (ytdl.validateURL(query)) { - const info = await ytdl.getBasicInfo(query).catch(() => { }); + const info = await ytdl.getBasicInfo(query).catch(() => {}); if (!info) return void this.emit(PlayerEvents.NO_RESULTS, message, query); if (info.videoDetails.isLiveContent && !queue.options.enableLive) return void this.emit( @@ -194,4 +199,4 @@ export class Player extends EventEmitter { } } -export default Player; \ No newline at end of file +export default Player; diff --git a/src/Structures/Playlist.ts b/src/Structures/Playlist.ts index 9ddadf3..804776c 100644 --- a/src/Structures/Playlist.ts +++ b/src/Structures/Playlist.ts @@ -1,12 +1,11 @@ -import Player from "../Player"; +import Player from '../Player'; export class Playlist { player: Player; constructor(player: Player, data: any) { - Object.defineProperty(this, "player", { value: player }); + Object.defineProperty(this, 'player', { value: player }); } - } -export default Playlist; \ No newline at end of file +export default Playlist; diff --git a/src/Structures/Track.ts b/src/Structures/Track.ts index 4142061..ed146b5 100644 --- a/src/Structures/Track.ts +++ b/src/Structures/Track.ts @@ -4,7 +4,7 @@ import { Player } from '../Player'; export class Track { readonly player: Player; readonly message: Message; - + constructor(player: Player, data: any) { Object.defineProperty(this, 'player', { value: player, enumerable: false }); } diff --git a/src/VoiceNative/VoiceAdapter.ts b/src/VoiceNative/VoiceAdapter.ts new file mode 100644 index 0000000..7bab0a6 --- /dev/null +++ b/src/VoiceNative/VoiceAdapter.ts @@ -0,0 +1,89 @@ +import { DiscordGatewayAdapterCreator, DiscordGatewayAdapterLibraryMethods } from '@discordjs/voice'; +import { + VoiceChannel, + Snowflake, + Client, + Constants, + WebSocketShard, + Guild, + StageChannel, + Collection +} from 'discord.js'; +import { GatewayVoiceServerUpdateDispatchData, GatewayVoiceStateUpdateDispatchData } from 'discord-api-types/v8'; + +class VoiceAdapter { + public client: Client; + public adapters = new Collection(); + public clients = new Set(); + public guilds = new Collection>(); + + constructor(client: Client) { + this.client = client; + + Object.defineProperty(this, 'client', { + enumerable: false, + writable: true, + configurable: true + }); + } + + trackVoiceState() { + if (this.clients.has(this.client)) return; + this.clients.add(this.client); + + this.client.ws.on('VOICE_STATE_UPDATE', (data: GatewayVoiceServerUpdateDispatchData) => { + this.adapters.get(data.guild_id)?.onVoiceServerUpdate(data); + }); + + this.client.ws.on(Constants.WSEvents.VOICE_STATE_UPDATE, (payload: GatewayVoiceStateUpdateDispatchData) => { + if (payload.guild_id && payload.session_id && payload.user_id === this.client.user?.id) { + this.adapters.get(payload.guild_id)?.onVoiceStateUpdate(payload); + } + }); + } + + cleanupGuilds(shard: WebSocketShard) { + const guilds = this.guilds.get(shard); + if (guilds) { + for (const guildID of guilds.values()) { + this.adapters.get(guildID)?.destroy(); + } + } + } + + trackGuild(guild: Guild) { + let guilds = this.guilds.get(guild.shard); + if (!guilds) { + const cleanup = () => this.cleanupGuilds(guild.shard); + guild.shard.on('close', cleanup); + guild.shard.on('destroyed', cleanup); + guilds = new Set(); + this.guilds.set(guild.shard, guilds); + } + + guilds.add(guild.id); + } +} + +export default function createAdapter(channel: VoiceChannel | StageChannel): DiscordGatewayAdapterCreator { + return (methods) => { + const adapter = new VoiceAdapter(channel.client); + adapter.adapters.set(channel.guild.id, methods); + adapter.trackVoiceState(); + adapter.trackGuild(channel.guild); + + return { + sendPayload(data) { + if (channel.guild.shard.status === Constants.Status.READY) { + channel.guild.shard.send(data); + return true; + } + + return false; + }, + destroy() { + return adapter.adapters.delete(channel.guild.id); + } + }; + }; +} diff --git a/src/VoiceNative/VoiceSubscriptionManager.ts b/src/VoiceNative/VoiceSubscriptionManager.ts new file mode 100644 index 0000000..d1e61ea --- /dev/null +++ b/src/VoiceNative/VoiceSubscriptionManager.ts @@ -0,0 +1 @@ +class VoiceSubscriptionManager {} diff --git a/src/utils/Constants.ts b/src/utils/Constants.ts index dd36a0f..26f2ce5 100644 --- a/src/utils/Constants.ts +++ b/src/utils/Constants.ts @@ -17,7 +17,7 @@ export enum PlayerEvents { SEARCH_RESULTS = 'searchResults', TRACK_ADD = 'trackAdd', TRACK_START = 'trackStart' -}; +} export enum PlayerErrorEventCodes { DEFAULT = 'PlayerError', @@ -28,7 +28,7 @@ export enum PlayerErrorEventCodes { PARSE_ERROR = 'ParseError', VIDEO_UNAVAILABLE = 'VideoUnavailable', MUSIC_STARTING = 'MusicStarting' -}; +} export const PlayerOptions: DP_OPTIONS = { leaveOnEnd: true, diff --git a/src/utils/PlayerError.ts b/src/utils/PlayerError.ts index 5185fb1..e541396 100644 --- a/src/utils/PlayerError.ts +++ b/src/utils/PlayerError.ts @@ -1,4 +1,4 @@ -import { Message } from "discord.js"; +import { Message } from 'discord.js'; export default class PlayerError extends Error { discordMessage: Message; diff --git a/src/utils/Util.ts b/src/utils/Util.ts index 7826f5d..6da2765 100644 --- a/src/utils/Util.ts +++ b/src/utils/Util.ts @@ -7,12 +7,15 @@ import { validateURL as SoundcloudValidateURL } from 'soundcloud-scraper'; import { VoiceChannel } from 'discord.js'; const spotifySongRegex = /https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(?:track\/|\?uri=spotify:track:)((\w|-){22})/; -const spotifyPlaylistRegex = /https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(?:playlist\/|\?uri=spotify:playlist:)((\w|-){22})/; +const spotifyPlaylistRegex = + /https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(?:playlist\/|\?uri=spotify:playlist:)((\w|-){22})/; const spotifyAlbumRegex = /https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(?:album\/|\?uri=spotify:album:)((\w|-){22})/; -const vimeoRegex = /(http|https)?:\/\/(www\.|player\.)?vimeo\.com\/(?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|video\/|)(\d+)(?:|\/\?)/; +const vimeoRegex = + /(http|https)?:\/\/(www\.|player\.)?vimeo\.com\/(?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|video\/|)(\d+)(?:|\/\?)/; const facebookRegex = /(https?:\/\/)(www\.|m\.)?(facebook|fb).com\/.*\/videos\/.*/; const reverbnationRegex = /https:\/\/(www.)?reverbnation.com\/(.+)\/song\/(.+)/; -const attachmentRegex = /^(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/; +const attachmentRegex = + /^(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/; export class Util { /**