From f53fbf07640e78ae4e3cc2b6009eb8d5c9104788 Mon Sep 17 00:00:00 2001 From: Emmanuel Lobo <76094069+UnschooledGamer@users.noreply.github.com> Date: Fri, 28 Jun 2024 20:51:11 +0530 Subject: [PATCH] Fix User Reported Bugs (#4) * fix(Track): avoid fetching thumbnail on initialisation, thumbnail rate limit - bump deps. * fix(Track): correct the this context * add test file * feat(Track): cache thumbnail if it exist * feat(Track): avoid fetching thumbnail each call, cache it * refactor: :art: Enforce NAME/Version Format For Client-Name Header * refactor: collections/maps This commit refactors the discord.js collections used in Riffy, replacing it with Node.js/V8 Map. * fix: :bug: Fixes the cacheThumbnail property that was not working * Corrects The format issue in author property package.json, Replaced it with contributors field. * Sets the correct condition for player#destroy * Fixed clearFilters() and added API to fetch songs for autoplay * fix(Track): clubs non-track result/response in tracks and throws Error in v4 Lavalink * Updated some test commands * Update Types * fix: node regions support feat: exception object in resolve method and types fix: leastUsedNodes sorting * fix(Riffy): missing 'tracks' in resolve track method's response This commit adds the 'tracks' that was accidentally removed and updates types * feat(riffy): add 'node' property in resolve method, for accurate selection of the node * fix: Riffy resolve 'node' property error, fix: headers not getting show in debug, update: 'nodes' types. * Fix: [Better Handle No-Audio] Set Player#connected to `true` after Voice state & Server event is Received * refactor: Connection & Player Debug Messages * Fix(Rest): return promises with await * feat(Node): Version Checks & Bump pkgs(deps) * feat(Node) Adds Version checks * bump deps * feat `includeHeaders` in rest makeRequest --------- Co-authored-by: ThePedroo Co-authored-by: FlameFace --- build/functions/autoPlay.js | 72 ++--- build/index.d.ts | 77 ++--- build/structures/Connection.js | 5 + build/structures/Filters.js | 116 ++++++- build/structures/Node.js | 163 ++++++---- build/structures/Player.js | 21 +- build/structures/Rest.js | 83 +++-- build/structures/Riffy.js | 396 ++++++++++++----------- build/structures/Track.js | 35 ++- package-lock.json | 552 +++++++++++++-------------------- package.json | 14 +- test/autoplay-test.js | 9 + test/track-test.js | 35 +++ test/v3.js | 183 +++++------ test/v4.js | 500 +++++++++++++++++------------ 15 files changed, 1228 insertions(+), 1033 deletions(-) create mode 100644 test/autoplay-test.js create mode 100644 test/track-test.js diff --git a/build/functions/autoPlay.js b/build/functions/autoPlay.js index f1a7dd4..acea17b 100644 --- a/build/functions/autoPlay.js +++ b/build/functions/autoPlay.js @@ -1,47 +1,49 @@ const undici = require('undici'); -const { JSDOM } = require('jsdom'); -async function scAutoPlay(url) { - const res = await undici.fetch(`${url}/recommended`); - - if (res.status !== 200) { - throw new Error(`Failed to fetch URL. Status code: ${res.status}`); - } - - const html = await res.text(); - - const dom = new JSDOM(html); - const document = dom.window.document; - - const secondNoscript = document.querySelectorAll('noscript')[1]; - const sectionElement = secondNoscript.querySelector('section'); - const articleElements = sectionElement.querySelectorAll('article'); +/** + * + * @param {string} url soundcloud song url without parameters + * @returns + */ + +async function soundcloud(url) { + const res = await undici.fetch(`https://riffy.unburn.tech/api/soundcloud`, { + method: "POST", + body: JSON.stringify({ + "url": url + }) + }); - articleElements.forEach(articleElement => { - const h2Element = articleElement.querySelector('h2[itemprop="name"]'); + const output = await res.json(); - const aElement = h2Element.querySelector('a[itemprop="url"]'); - const href = `https://soundcloud.com${aElement.getAttribute('href')}` + if (output.status !== 200) { + throw new Error(`Failed to fetch URL. Status code: ${output.status}`); + } - return href; - }); + return output; } -async function spAutoPlay(track_id) { - const data = await undici.fetch("https://open.spotify.com/get_access_token?reason=transport&productType=embed"); - - const body = await data.json(); +/** + * + * @param {string} track_id spotify song track id + * @returns + */ + +async function spotify(track_id) { + const res = await undici.fetch(`https://riffy.unburn.tech/api/spotify`, { + method: "POST", + body: JSON.stringify({ + "track_id": track_id + }) + }); - const res = await undici.fetch(`https://api.spotify.com/v1/recommendations?limit=10&seed_tracks=${track_id}`, { - headers: { - Authorization: `Bearer ${body.accessToken}`, - 'Content-Type': 'application/json', - }, - }) + const output = await res.json(); - const json = await res.json(); + if (output.status !== 200) { + throw new Error(`Failed to fetch URL. Status code: ${output.status}`); + } - return json.tracks[Math.floor(Math.random() * json.tracks.length)].id + return output; } -module.exports = { scAutoPlay, spAutoPlay }; \ No newline at end of file +module.exports = { soundcloud, spotify }; \ No newline at end of file diff --git a/build/index.d.ts b/build/index.d.ts index 785ef24..0bca589 100644 --- a/build/index.d.ts +++ b/build/index.d.ts @@ -1,5 +1,4 @@ import { EventEmitter } from "events"; -import { Collection } from "@discordjs/collection"; export declare class Track { constructor(data: any, requester: any, node: Node); @@ -63,12 +62,12 @@ export declare class Rest extends EventEmitter { public parseResponse(req: any): Promise; } -export declare class Queue extends Array{ +export declare class Queue extends Array{ get size(): Number; - get first(): T | null; + get first(): Track | null; - add(track: T): this; - remove(index: Number): T; + add(track: Track): this; + remove(index: Number): Track; clear(): void; shuffle(): void; } @@ -107,7 +106,7 @@ export declare class Player extends EventEmitter { public loop: String; public filters: Filters; public data: {}; - public queue: Queue; + public queue: Queue; public position: Number; public current: Track; public previous: Track | null; @@ -153,8 +152,14 @@ export declare class Player extends EventEmitter { private send(data: any): void; } -export type SearchPlatform = "ytsearch" | "ytmsearch" | "scsearch" | "spsearch" | "amsearch" | "dzsearch" | "ymsearch"; +export type SearchPlatform = "ytsearch" | "ytmsearch" | "scsearch" | "spsearch" | "amsearch" | "dzsearch" | "ymsearch" | string; export type Version = "v3" | "v4"; + +export type LavalinkTrackLoadException = { + message: string | null, + severity: "common" | "suspicious" | "fault", + cause: string +} export type nodeResponse = { /** * Array of Loaded Tracks @@ -175,6 +180,8 @@ export type nodeResponse = { * Plugin Info */ pluginInfo?: any; + + exception: LavalinkTrackLoadException | null } export type RiffyOptions = { @@ -190,31 +197,25 @@ export type RiffyOptions = { defaultSearchPlatform?: SearchPlatform; restVersion?: Version; plugins?: Array; -} +} & Exclude type k = String; type v = any; export declare class Riffy extends EventEmitter { - constructor(client: any, nodes: { - name?: String; - host: String; - port: Number; - password: String; - secure: Boolean; - }, options: RiffyOptions); + constructor(client: any, nodes: LavalinkNode[], options: RiffyOptions); public client: any; public nodes: Array; - public nodeMap: Collection; - public players: Collection; + public nodeMap: Map; + public players: Map; public options: RiffyOptions; public clientId: String; public initiated: Boolean; public send: RiffyOptions["send"]; public defaultSearchPlatform: String; - public restVersion: NodeOptions["restVersion"]; + public restVersion: RiffyOptions["restVersion"]; - public readonly leastUsedNodes: Array; + public readonly leastUsedNodes: Array; public init(clientId: String): this; @@ -231,6 +232,11 @@ export declare class Riffy extends EventEmitter { voiceChannel: String; textChannel: String; deaf?: Boolean; + mute?: boolean; + /** + * @description voice region (rtc Region) used for filtering node based onit + * */ + region: string; }): Player; public createPlayer(node: Node, options: PlayerOptions): Player; @@ -241,6 +247,7 @@ export declare class Riffy extends EventEmitter { query: String; source?: String; requester: any; + node: string | Node }): Promise; @@ -288,29 +295,26 @@ export type LavalinkNode = { password: String; /** * Is node connection secured by SSL ? + * @default false */ - secure: Boolean; -} + secure?: Boolean; + + /** + * Voice Regions for the Node + */ + regions?: string[]; + +} & Partial export type NodeOptions = { /** * The rest version of the node */ - restVersion: "v3" | "v4"; - /** - * The send function of the node - */ - send: (payload: { - op: Number; - d: { - guild_id: String; - channel_id: String; - self_deaf: Boolean; - self_mute: Boolean; - } - }) => void; + restVersion: Version; + /** - * The resume key of the node + * The resume key of the node + * Ignored if node `restVersion` is not `v3` */ resumeKey?: String; /** @@ -351,10 +355,9 @@ export declare class Node { public restUrl: String; private ws: null; - public send: NodeOptions["send"]; public resumeKey: NodeOptions["resumeKey"]; public sessionId: NodeOptions["sessionId"]; - public region: String | null; + public regions: string[] | null; public resumeTimeout: NodeOptions["resumeTimeout"]; public autoResume: NodeOptions["autoResume"]; public reconnectTimeout: NodeOptions["reconnectTimeout"]; diff --git a/build/structures/Connection.js b/build/structures/Connection.js index 1356d3b..727a05b 100644 --- a/build/structures/Connection.js +++ b/build/structures/Connection.js @@ -20,9 +20,12 @@ class Connection { const { endpoint, token } = data; if (!endpoint) throw new Error("Session not found"); + this.player.riffy.emit("debug", `[Player ${this.player.guildId} - CONNECTION] Received voice server, Updating LavaLink Voice Data.`) + this.voice.endpoint = endpoint; this.voice.token = token; this.region = endpoint.split(".").shift()?.replace(/[0-9]/g, "") || null; + this.player.connected = true; if (this.player.paused) { this.player.riffy.emit( @@ -39,6 +42,8 @@ class Connection { setStateUpdate(data) { const { session_id, channel_id, self_deaf, self_mute } = data; + this.player.riffy.emit("debug", `[Player ${this.player.guildId} - CONNECTION] Received Voice State Update Informing the player ${channel_id !== null ? `Connected to ${this.voiceChannel}` : `Disconnected from ${this.voiceChannel}`}`) + // If player is manually disconnected from VC if(channel_id == null) { this.player.destroy(); diff --git a/build/structures/Filters.js b/build/structures/Filters.js index 0a5dfb3..1de9b74 100644 --- a/build/structures/Filters.js +++ b/build/structures/Filters.js @@ -18,12 +18,25 @@ class Filters { this._8d = options._8d || null; } + /** + * + * @param {string[]} band + * @returns + */ + setEqualizer(band) { this.equalizer = band; this.updateFilters(); return this; } + /** + * + * @param {boolean} enabled + * @param {*} options + * @returns + */ + setKaraoke(enabled, options = {}) { if (!this.player) return; @@ -44,6 +57,13 @@ class Filters { } } + /** + * + * @param {boolean} enabled + * @param {*} options + * @returns + */ + setTimescale(enabled, options = {}) { if (!this.player) return; @@ -63,6 +83,13 @@ class Filters { } } + /** + * + * @param {boolean} enabled + * @param {*} options + * @returns + */ + setTremolo(enabled, options = {}) { if (!this.player) return; @@ -81,6 +108,13 @@ class Filters { } } + /** + * + * @param {boolean} enabled + * @param {*} options + * @returns + */ + setVibrato(enabled, options = {}) { if (!this.player) return; @@ -99,6 +133,13 @@ class Filters { } } + /** + * + * @param {boolean} enabled + * @param {*} options + * @returns + */ + setRotation(enabled, options = {}) { if (!this.player) return; @@ -116,6 +157,13 @@ class Filters { } } + /** + * + * @param {boolean} enabled + * @param {*} options + * @returns + */ + setDistortion(enabled, options = {}) { if (!this.player) return; @@ -140,6 +188,13 @@ class Filters { } } + /** + * + * @param {boolean} enabled + * @param {*} options + * @returns + */ + setChannelMix(enabled, options = {}) { if (!this.player) return; @@ -160,6 +215,13 @@ class Filters { } } + /** + * + * @param {boolean} enabled + * @param {*} options + * @returns + */ + setLowPass(enabled, options = {}) { if (!this.player) return; @@ -177,10 +239,17 @@ class Filters { } } + /** + * + * @param {boolean} enabled + * @param {*} options + * @returns + */ + setBassboost(enabled, options = {}) { if (!this.player) return; - if (enabled == true) { + if (enabled) { if (options.value < 0 || options.value > 5) throw new Error("Bassboost value must be between 0 and 5"); this.bassboost = options.value || 5; @@ -199,7 +268,7 @@ class Filters { setSlowmode(enabled, options = {}) { if (!this.player) return; - if (enabled == true) { + if (enabled) { this.slowmode = true; this.setTimescale(true, { @@ -211,10 +280,17 @@ class Filters { } } + /** + * + * @param {boolean} enabled + * @param {*} options + * @returns + */ + setNightcore(enabled, options = {}) { if (!this.player) return; - if (enabled == true) { + if (enabled) { if (!this.player) return; this.nightcore = enabled; @@ -222,15 +298,20 @@ class Filters { rate: options.rate || 1.5 }) - if (enabled) { - this.vaporwave = false; - } + this.vaporwave = false; } else { this.nightcore = null; this.setTimescale(false) } } + /** + * + * @param {boolean} enabled + * @param {*} options + * @returns + */ + setVaporwave(enabled, options = {}) { if (!this.player) return; @@ -250,6 +331,13 @@ class Filters { } } + /** + * + * @param {boolean} enabled + * @param {*} options + * @returns + */ + set8D(enabled, options = {}) { if (!this.player) return; @@ -265,16 +353,22 @@ class Filters { } } - clearFilters() { + async clearFilters() { this.player.filters = new Filters(this.player); - this.updateFilters(); + + if (this.nightcore) this.setNightcore(false) + if (this.equalizer.length !== 0) this.setEqualizer([]) + if (this._8d) this.set8D(false) + if (this.slowmode) this.setSlowmode(false) + + await this.updateFilters(); return this; } - updateFilters() { + async updateFilters() { const { equalizer, karaoke, timescale, tremolo, vibrato, rotation, distortion, channelMix, lowPass, volume } = this; - this.player.node.rest.updatePlayer({ + await this.player.node.rest.updatePlayer({ guildId: this.player.guildId, data: { filters: { volume, equalizer, karaoke, timescale, tremolo, vibrato, rotation, distortion, channelMix, lowPass } @@ -285,4 +379,4 @@ class Filters { } } -module.exports = { Filters }; +module.exports = { Filters }; \ No newline at end of file diff --git a/build/structures/Node.js b/build/structures/Node.js index b066eff..ce0f212 100644 --- a/build/structures/Node.js +++ b/build/structures/Node.js @@ -2,8 +2,12 @@ const Websocket = require("ws"); const { Rest } = require("./Rest"); class Node { + /** + * @param {import("./Riffy").Riffy} riffy + * @param {} node + */ constructor(riffy, node, options) { - this.riffy = riffy; + this.riffy = riffy this.name = node.name || node.host; this.host = node.host || "localhost"; this.port = node.port || 2333; @@ -21,8 +25,13 @@ class Node { this.restUrl = `http${this.secure ? "s" : ""}://${this.host}:${this.port}`; this.ws = null; - this.send = options.send; - this.region = node.region || null; + this.regions = node.regions; + /** + * Lavalink Info fetched While connecting. + * @todo Add Types + * + */ + this.info = {}; this.stats = { players: 0, playingPlayers: 0, @@ -44,6 +53,7 @@ class Node { deficit: 0, }, }; + this.connected = false; this.resumeKey = options.resumeKey || null; @@ -60,10 +70,89 @@ class Node { connect() { if (this.ws) this.ws.close(); + + this.riffy.emit('debug', this.name, `Checking Node Version`); + + /** + * @todo Probably to wait until version checks are completed before continuing to connnect to Lavalink. + * @todo Add option to skip the version checks in-case needed. + */ + (async () => { + const requestOpts = (version = this.restVersion) => { + return { + method: "GET", + endpoint: `/${version}/info`, + undefined, + includeHeaders: true + }; + } + + await Promise.all([this.rest.makeRequest(...Object.values(requestOpts())), this.rest.makeRequest(...Object.values(requestOpts(this.restVersion == "v3" ? "v4" : "v3")))]).then(([restVersionRequest, flippedRestRequest]) => { + /** + * Lavalink Node's Version that was fetched, checks and uses the succeeded request + * Uses `lavalink-api-version` header if `major` property isn't available/is `0` in the request, it can use either one variable. Defaults to `0` if `lavalink-api-version` isn't available. + */ + const nodeFetchedVersionObj = Object.assign( + ( + ("version" in restVersionRequest && restVersionRequest) || + flippedRestRequest + ).version, + { + major: !(restVersionRequest?.version || flippedRestRequest?.version)?.major + ? Number( + (restVersionRequest || flippedRestRequest).headers.get("lavalink-api-version") + ) || 0 + : (restVersionRequest?.version || flippedRestRequest?.version)?.major, + } + ); + + if(restVersionRequest?.status == 404) this.riffy.emit( + "debug", + `[Node (${this.name}) - Version Check] ${ + this.restVersion + } set By User/Defaulted Version Check Failed, attempted ${ + this.restVersion == "v3" ? "v4" : "v3" + } For version Checking` + ); + + if(flippedRestRequest?.status === 404 && restVersionRequest?.status === 404) { + this.riffy.emit("debug", `[Node (${this.name}) - Version Check] Both Version Checks failed, Disconnecting Gracefully & Throwing Error`) + + // Disconnect Websocket & Destroy the players(if any created - Just incase) + this.destroy() + + throw new Error(`${this.name}(${this.host}) is using unsupported Lavalink Version, Supported Lavalink Versions are v3 and v4.`) + } + + if(restVersionRequest?.status !== 404 || flippedRestRequest?.status !== 404) { + this.riffy.emit( + "debug", + `[Node (${this.name}) - Version Check] Check ${restVersionRequest?.status === 404 ? "Un" : ""}successful Lavalink Server uses ${nodeFetchedVersionObj.semver} ${restVersionRequest.status === 404 ? `Doesn't match with restVersion: ${this.restVersion}, Provided in Riffy Options` : ""}` + ); + + // If defaulted/user-specified fails Graceful Destroy/close the node's connection. + if(restVersionRequest?.status === 404) { + this.riffy.emit("debug", `[Node (${this.name}) - Version Check] Disconnecting Gracefully & Throwing Error`) + + // Disconnect Websocket & Destroy the players(if any created - Just incase) + this.destroy() + + throw new Error(`${this.name} is specified/defaulted to use ${this.restVersion}, but found using Lavalink version v${nodeFetchedVersionObj.major}, TIP: Set 'restVersion' property to v${nodeFetchedVersionObj.major}`); + } + } + + const { headers, ...restVersionRequestWithoutHeaders } = restVersionRequest; + + // If `restVersionRequest` isn't failed then update the `info` or set it back to empty Object. + this.info = !("status" in restVersionRequest) ? restVersionRequestWithoutHeaders : {}; + }) + + })() + const headers = { "Authorization": this.password, "User-Id": this.riffy.clientId, - "Client-Name": "Riffy", + "Client-Name": `Riffy/${this.riffy.version}`, }; if (this.restVersion === "v4") { @@ -90,54 +179,11 @@ class Node { open() { if (this.reconnectTimeout) clearTimeout(this.reconnectTimeout); - this.riffy.emit("nodeConnect", this); this.connected = true; this.riffy.emit('debug', this.name, `Connection with Lavalink established on ${this.wsUrl}`); /** @todo Add Version Checking of Node */ - // this.riffy.emit('debug', this.name, `Checking Node Version`) - - // (async () => { - // const requestOpts = (version = this.restVersion) => { - // return { - // method: "GET", - // endpoint: `/${version}/stats`, - // }; - // } - - // await Promise.all([this.rest.makeRequest(requestOpts), this.rest.makeRequest(requestOpts(this.restVersion == "v3" ? "v4" : "v3"))]).then((restVersionRequest, flippedRestRequest) => { - - // if(restVersionRequest == null) this.riffy.emit( - // "debug", - // `[Node (${this.name}) - Version Check] ${ - // this.restVersion - // } set By User/Defaulted Version Check Failed, attempted ${ - // this.restVersion == "v3" ? "v4" : "v3" - // } For version Checking` - // ); - - // if(flippedRestRequest == null && restVersionRequest == null) { - // this.riffy.emit("debug", `[Node (${this.name}) - Version Check] Both Version Checks failed, Disconnecting Gracefully & Throwing Error`) - - // // Disconnect Websocket & Destroy the players(if any created - Just incase) - // this.destroy() - - // throw new Error(`${this.name}(${this.host}) is using unsupported Lavalink Version, Supported Lavalink Versions are v3 and v4.`) - // } - - // if(restVersionRequest !== null || flippedRestRequest !== null) { - // this.riffy.emit( - // "debug", - // `[Node (${this.name}) - Version Check] Check ${restVersionRequest == null ? "Un" : ""}successful Lavalink Server uses ${(restVersionRequest || flippedRestRequest).version.string} ${restVersionRequest == null && this.restVersion !== `v${restVersionRequest.version.major}` ? `Doesn't match with restVersion: ${this.restVersion}, Provided in Riffy Options` : ""}` - // ); - - // } - - // }) - - // })() - if (this.autoResume) { for (const player of this.riffy.players.values()) { if (player.node === this) { @@ -172,6 +218,8 @@ class Node { this.rest.setSessionId(payload.sessionId); this.sessionId = payload.sessionId; } + + this.riffy.emit("nodeConnect", this); this.riffy.emit("debug", this.name, `Ready Payload received ${JSON.stringify(payload)}`); @@ -194,7 +242,7 @@ class Node { close(event, reason) { this.riffy.emit("nodeDisconnect", this, { event, reason }); - this.riffy.emit("debug", `Connection with Lavalink closed with Error code : ${event || "Unknown code"}`); + this.riffy.emit("debug", `Connection with Lavalink closed with Error code : ${event || "Unknown code"}, reason: ${reason || "Unknown reason"}`); this.connected = false; this.reconnect(); @@ -220,8 +268,11 @@ class Node { destroy() { if (!this.connected) return; - const player = this.riffy.players.filter((player) => player.node === this); - if (player.size) player.forEach((player) => player.destroy()); + this.riffy.players.forEach((player) => { + if (player.node !== this) return; + + player.destroy() + }); if (this.ws) this.ws.close(1000, "destroy"); this.ws.removeAllListeners(); @@ -235,15 +286,7 @@ class Node { this.riffy.nodeMap.delete(this.name); this.connected = false; - } - - send(payload) { - const data = JSON.stringify(payload); - this.ws.send(data, (error) => { - if (error) return error; - return null; - }); - } + } disconnect() { if (!this.connected) return; @@ -277,4 +320,4 @@ class Node { } } -module.exports = { Node }; \ No newline at end of file +module.exports = { Node }; diff --git a/build/structures/Player.js b/build/structures/Player.js index f8d4c9f..259b19d 100644 --- a/build/structures/Player.js +++ b/build/structures/Player.js @@ -2,7 +2,7 @@ const { EventEmitter } = require("events"); const { Connection } = require("./Connection"); const { Filters } = require("./Filters"); const { Queue } = require("./Queue"); -const { scAutoPlay, spAutoPlay } = require('../functions/autoPlay'); +const { soundcloud, spotify } = require('../functions/autoPlay'); class Player extends EventEmitter { constructor(riffy, node, options) { @@ -46,7 +46,7 @@ class Player extends EventEmitter { } async play() { - if (!this.connected) throw new Error("Player connection is not initiated. Kindly user Riffy.createConnection() and establish a connection"); + if (!this.connected) throw new Error("Player connection is not initiated. Kindly use Riffy.createConnection() and establish a connection, TIP: Check if Guild Voice States is set & 'updateVoiceState' is used in the raw(Gateway Raw) event"); if (!this.queue.length) return; this.current = this.queue.shift(); @@ -113,7 +113,7 @@ class Player extends EventEmitter { } } else if (player.previous.info.sourceName === "soundcloud") { try { - scAutoPlay(player.previous.info.uri).then(async (data) => { + soundcloud(player.previous.info.uri).then(async (data) => { let response = await this.riffy.resolve({ query: data, source: "scsearch", requester: player.previous.info.requester }); if (this.node.rest.version === "v4") { @@ -134,7 +134,7 @@ class Player extends EventEmitter { } } else if (player.previous.info.sourceName === "spotify") { try { - spAutoPlay(player.previous.info.identifier).then(async (data) => { + spotify(player.previous.info.identifier).then(async (data) => { const response = await this.riffy.resolve({ query: `https://open.spotify.com/track/${data}`, requester: player.previous.info.requester }); if (this.node.rest.version === "v4") { @@ -165,8 +165,7 @@ class Player extends EventEmitter { self_mute: mute, }); - this.connected = true; - this.riffy.emit("debug", this.guildId, "Player has been connected"); + this.riffy.emit("debug", this.guildId, `Player has informed the Discord Gateway to Establish Voice Connectivity in ${voiceChannel} Voice Channel, Awaiting Confirmation(Via Voice State Update & Voice Server Update events)`); } stop() { @@ -287,11 +286,11 @@ class Player extends EventEmitter { const track = this.current; - if (this.node.rest.version === "v4") { - track.info.thumbnail = await track.info.thumbnail; - } else { - track.info.thumbnail = await track.info.thumbnail; - } + // if (this.node.rest.version === "v4") { + // track.info.thumbnail = await track.info.thumbnail; + // } else { + // track.info.thumbnail = await track.info.thumbnail; + // } switch (payload.type) { case "TrackStartEvent": diff --git a/build/structures/Rest.js b/build/structures/Rest.js index e048735..91a62de 100644 --- a/build/structures/Rest.js +++ b/build/structures/Rest.js @@ -1,8 +1,7 @@ -// destructured, named undiciFetch for Better readability +// destructured, named undiciFetch for Better readability const { fetch: undiciFetch, Response } = require("undici"); const nodeUtil = require("node:util") - class Rest { constructor(riffy, options) { this.riffy = riffy; @@ -19,7 +18,7 @@ class Rest { this.sessionId = sessionId; } - async makeRequest(method, endpoint, body = null) { + async makeRequest(method, endpoint, body = null, includeHeaders = false) { const headers = { "Content-Type": "application/json", Authorization: this.password, @@ -38,9 +37,14 @@ class Rest { // Parses The Request const data = await this.parseResponse(response); - // Emit apiResponse event with important data and Response + // Emit apiResponse event with important data and Response this.riffy.emit("apiResponse", endpoint, response); + const headersJson = {}; + for (const [name, value] of response.headers) { + headersJson[name] = value; + } + this.riffy.emit( "debug", `[Rest] ${requestOptions.method} ${ @@ -52,7 +56,7 @@ class Rest { }` ); - return data; + return Object.assign(data, includeHeaders ? { headers: response.headers } : {}); } async getPlayers() { @@ -64,49 +68,59 @@ class Rest { async updatePlayer(options) { // destructure data as requestBody for ease of use. - const { data: requestBody } = options - - if((typeof requestBody.track !== "undefined" && requestBody.track.encoded && requestBody.track.identifier) || requestBody.encodedTrack && requestBody.identifier) throw new Error( - `${ - typeof requestBody.track !== "undefined" - ? `encoded And identifier` - : `encodedTrack And identifier` - } are mutually exclusive (Can't be provided together) in Update Player Endpoint` - ); - - if(this.version === "v3" && options.data?.track) { - - const { track, ...otherRequestData } = requestBody + const { data: requestBody } = options; - requestBody = { ...otherRequestData } - - Object.assign(options.data, typeof options.data.track.encoded !== "undefined" ? { encodedTrack: requestBody.track.encoded} : { identifier: requestBody.track.identifier}) + if ( + (typeof requestBody.track !== "undefined" && + requestBody.track.encoded && + requestBody.track.identifier) || + (requestBody.encodedTrack && requestBody.identifier) + ) + throw new Error( + `${ + typeof requestBody.track !== "undefined" + ? `encoded And identifier` + : `encodedTrack And identifier` + } are mutually exclusive (Can't be provided together) in Update Player Endpoint` + ); + + if (this.version === "v3" && options.data?.track) { + const { track, ...otherRequestData } = requestBody; + + requestBody = { ...otherRequestData }; + + Object.assign( + options.data, + typeof options.data.track.encoded !== "undefined" + ? { encodedTrack: requestBody.track.encoded } + : { identifier: requestBody.track.identifier } + ); } - return this.makeRequest( + return await this.makeRequest( "PATCH", `/${this.version}/sessions/${this.sessionId}/players/${options.guildId}?noReplace=false`, options.data - ) + ); } async destroyPlayer(guildId) { - return this.makeRequest( + return await this.makeRequest( "DELETE", `/${this.version}/sessions/${this.sessionId}/players/${guildId}` ); } async getTracks(identifier) { - return this.makeRequest( + return await this.makeRequest( "GET", `/${this.version}/loadtracks?identifier=${encodeURIComponent(identifier)}` - ) + ); } async decodeTrack(track, node) { if (!node) node = this.leastUsedNodes[0]; - return this.makeRequest( + return await this.makeRequest( `GET`, `/${this.version}/decodetrack?encodedTrack=${encodeURIComponent(track)}` ); @@ -121,11 +135,11 @@ class Rest { } async getStats() { - return this.makeRequest("GET", `/${this.version}/stats`); + return await this.makeRequest("GET", `/${this.version}/stats`); } async getInfo() { - return this.makeRequest("GET", `/${this.version}/info`); + return await this.makeRequest("GET", `/${this.version}/info`); } async getRoutePlannerStatus() { @@ -135,7 +149,7 @@ class Rest { ); } async getRoutePlannerAddress(address) { - return this.makeRequest( + return await this.makeRequest( `POST`, `/${this.version}/routeplanner/free/address`, { address } @@ -155,10 +169,15 @@ class Rest { try { return await req.json(); } catch (e) { - this.riffy.emit("debug", `[Rest - Error] There was an Error for ${new URL(req.url).pathname} ${e}`) + this.riffy.emit( + "debug", + `[Rest - Error] There was an Error for ${ + new URL(req.url).pathname + } ${e}` + ); return null; } } } -module.exports = { Rest }; \ No newline at end of file +module.exports = { Rest }; diff --git a/build/structures/Riffy.js b/build/structures/Riffy.js index 8352ce2..fce074b 100644 --- a/build/structures/Riffy.js +++ b/build/structures/Riffy.js @@ -1,206 +1,232 @@ const { EventEmitter } = require("events"); const { Node } = require("./Node"); -const { Plugin } = require("./Plugins"); const { Player } = require("./Player"); const { Track } = require("./Track"); -const { Collection } = require("@discordjs/collection"); +const { version: pkgVersion } = require("../../package.json") const versions = ["v3", "v4"]; class Riffy extends EventEmitter { - constructor(client, nodes, options) { - super(); - if (!client) throw new Error("Client is required to initialize Riffy"); - if (!nodes) throw new Error("Nodes are required to initialize Riffy"); - if (!options.send) throw new Error("Send function is required to initialize Riffy"); - - this.client = client; - this.nodes = nodes; - this.nodeMap = new Collection(); - this.nodeByRegion = null; - this.players = new Collection(); - this.options = options; - this.clientId = null; - this.initiated = false; - this.send = options.send || null; - this.defaultSearchPlatform = options.defaultSearchPlatform || "ytmsearch"; - this.restVersion = options.restVersion || "v3"; - this.tracks = []; - this.loadType = null; - this.playlistInfo = null; - this.pluginInfo = null; - this.plugins = options.plugins; - - if (this.restVersion && !versions.includes(this.restVersion)) throw new RangeError(`${this.restVersion} is not a valid version`); + constructor(client, nodes, options) { + super(); + if (!client) throw new Error("Client is required to initialize Riffy"); + if (!nodes) throw new Error("Nodes are required to initialize Riffy"); + if (!options.send) throw new Error("Send function is required to initialize Riffy"); + + this.client = client; + this.nodes = nodes; + this.nodeMap = new Map(); + this.players = new Map(); + this.options = options; + this.clientId = null; + this.initiated = false; + this.send = options.send || null; + this.defaultSearchPlatform = options.defaultSearchPlatform || "ytmsearch"; + this.restVersion = options.restVersion || "v3"; + this.tracks = []; + this.loadType = null; + this.playlistInfo = null; + this.pluginInfo = null; + this.plugins = options.plugins; + /** + * @description Package Version Of Riffy + */ + this.version = pkgVersion; + + if (this.restVersion && !versions.includes(this.restVersion)) throw new RangeError(`${this.restVersion} is not a valid version`); + } + + get leastUsedNodes() { + return [...this.nodeMap.values()] + .filter((node) => node.connected) + .sort((a, b) => a.rest.calls - b.rest.calls); + } + + init(clientId) { + if (this.initiated) return this; + this.clientId = clientId; + this.nodes.forEach((node) => this.createNode(node)); + this.initiated = true; + + if (this.plugins) { + this.plugins.forEach((plugin) => { + plugin.load(this); + }); } - - get leastUsedNodes() { - return [...this.nodeMap.values()] - .filter((node) => node.connected) - .sort((a, b) => a.rest.calls - b.rest.calls); - } - - init(clientId) { - if (this.initiated) return this; - this.clientId = clientId; - this.nodes.forEach((node) => this.createNode(node)); - this.initiated = true; - - if (this.plugins) { - this.plugins.forEach((plugin) => { - plugin.load(this); - }); - } + } + + createNode(options) { + const node = new Node(this, options, this.options); + this.nodeMap.set(options.name || options.host, node); + node.connect(); + + this.emit("nodeCreate", node); + return node; + } + + destroyNode(identifier) { + const node = this.nodeMap.get(identifier); + if (!node) return; + node.disconnect(); + this.nodeMap.delete(identifier); + this.emit("nodeDestroy", node); + } + + updateVoiceState(packet) { + if (!["VOICE_STATE_UPDATE", "VOICE_SERVER_UPDATE"].includes(packet.t)) return; + const player = this.players.get(packet.d.guild_id); + if (!player) return; + + if (packet.t === "VOICE_SERVER_UPDATE") { + player.connection.setServerUpdate(packet.d); + } else if (packet.t === "VOICE_STATE_UPDATE") { + if (packet.d.user_id !== this.clientId) return; + player.connection.setStateUpdate(packet.d); } - - createNode(options) { - const node = new Node(this, options, this.options); - this.nodeMap.set(options.name || options.host, node); - node.connect(); - - this.emit("nodeCreate", node); - return node; - } - - destroyNode(identifier) { - const node = this.nodeMap.get(identifier); - if (!node) return; - node.disconnect(); - this.nodeMap.delete(identifier); - this.emit("nodeDestroy", node); + } + + fetchRegion(region) { + const nodesByRegion = [...this.nodeMap.values()] + .filter((node) => node.connected && node.regions?.includes(region?.toLowerCase())) + .sort((a, b) => { + const aLoad = a.stats.cpu + ? (a.stats.cpu.systemLoad / a.stats.cpu.cores) * 100 + : 0; + const bLoad = b.stats.cpu + ? (b.stats.cpu.systemLoad / b.stats.cpu.cores) * 100 + : 0; + return aLoad - bLoad; + }); + + return nodesByRegion; + } + + createConnection(options) { + if (!this.initiated) throw new Error("You have to initialize Riffy in your ready event"); + + const player = this.players.get(options.guildId); + if (player) return player; + + if (this.leastUsedNodes.length === 0) throw new Error("No nodes are available"); + + let node; + if (options.region) { + const region = this.fetchRegion(options.region)[0]; + node = this.nodeMap.get(region.name || this.leastUsedNodes[0].name); + } else { + node = this.nodeMap.get(this.leastUsedNodes[0].name); } - updateVoiceState(packet) { - if (!["VOICE_STATE_UPDATE", "VOICE_SERVER_UPDATE"].includes(packet.t)) return; - const player = this.players.get(packet.d.guild_id); - if (!player) return; - - if (packet.t === "VOICE_SERVER_UPDATE") { - player.connection.setServerUpdate(packet.d); - } else if (packet.t === "VOICE_STATE_UPDATE") { - if (packet.d.user_id !== this.clientId) return; - player.connection.setStateUpdate(packet.d); + if (!node) throw new Error("No nodes are available"); + + return this.createPlayer(node, options); + } + + fetchRegion(region) { + const nodesByRegion = [...this.nodeMap.values()] + .filter((node) => node.connected && node.regions == region?.toLowerCase()) + .sort((a, b) => b.rest.calls - a.rest.calls); + + return nodesByRegion; + } + + createPlayer(node, options) { + const player = new Player(this, node, options); + this.players.set(options.guildId, player); + + player.connect(options); + + this.emit("playerCreate", player); + return player; + } + + destroyPlayer(guildId) { + const player = this.players.get(guildId); + if (!player) return; + player.destroy(); + this.players.delete(guildId); + + this.emit("playerDestroy", player); + } + + removeConnection(guildId) { + this.players.get(guildId)?.destroy(); + this.players.delete(guildId); + } + + /** + * @param {Object} param0 + * @param {string} param0.query used for searching as a search Query + * @param {*} param0.source A source to search the query on example:ytmsearch for youtube music + * @param {*} param0.requester the requester who's requesting + * @param {string? | Node?} param0.node the node to request the query on either use node identifier/name or the node class itself + * @returns -- returned properties values are nullable if lavlink doesn't give them + * */ + async resolve({ query, source, requester, node }) { + try { + if (!this.initiated) throw new Error("You have to initialize Riffy in your ready event"); + + if(node && (typeof node !== "string" && !(node instanceof Node))) throw new Error(`'node' property must either be an node identifier/name('string') or an Node/Node Class, But Received: ${typeof node}`) + + const sources = source || this.defaultSearchPlatform; + + const requestNode = (node && typeof node === 'string' ? this.nodeMap.get(node) : node) || this.leastUsedNodes[0]; + if (!requestNode) throw new Error("No nodes are available."); + + const regex = /^https?:\/\//; + const identifier = regex.test(query) ? query : `${sources}:${query}`; + + let response = await requestNode.rest.makeRequest(`GET`, `/${requestNode.rest.version}/loadtracks?identifier=${encodeURIComponent(identifier)}`); + + // for resolving identifiers - Only works in Spotify and Youtube + if (response.loadType === "empty" || response.loadType === "NO_MATCHES") { + response = await requestNode.rest.makeRequest(`GET`, `/${requestNode.rest.version}/loadtracks?identifier=https://open.spotify.com/track/${query}`); + if (response.loadType === "empty" || response.loadType === "NO_MATCHES") { + response = await requestNode.rest.makeRequest(`GET`, `/${requestNode.rest.version}/loadtracks?identifier=https://www.youtube.com/watch?v=${query}`); } - } - - fetchRegion(region) { - const nodesByRegion = [...this.nodeMap.values()] - .filter((node) => node.connected && node.regions == region?.toLowerCase()) - .sort((a, b) => b.rest.calls - a.rest.calls); - - return nodesByRegion; - } - - createConnection(options) { - if (!this.initiated) throw new Error("You have to initialize Riffy in your ready event"); - - const player = this.players.get(options.guildId); - if (player) return player; - - if (this.leastUsedNodes.length === 0) throw new Error("No nodes are available"); + } - let node; - if (options.region) { - let node = this.fetchRegion(options.region)[0]; - if (!node) throw new Error("No nodes are available in the specified region."); - - this.nodeByRegion = node; + if (requestNode.rest.version === "v4") { + if (response.loadType === "track") { + this.tracks = response.data ? [new Track(response.data, requester, requestNode)] : []; + } else if (response.loadType === "playlist") { + this.tracks = response.data?.tracks ? response.data.tracks.map((track) => new Track(track, requester, requestNode)) : []; } else { - node = this.nodeMap.get(this.leastUsedNodes[0].name); - } - - if (!node) throw new Error("No nodes are available"); - - return this.createPlayer(node, options); - } - - createPlayer(node, options) { - const player = new Player(this, node, options); - this.players.set(options.guildId, player); - - player.connect(options); - - this.emit("playerCreate", player); - return player; - } - - destroyPlayer(guildId) { - const player = this.players.get(guildId); - if (!player) return; - player.destroy(); - this.players.delete(guildId); - - this.emit("playerDestroy", player); - } - - removeConnection(guildId) { - this.players.get(guildId)?.destroy(); - this.players.delete(guildId); - } - - async resolve({ query, source, requester }) { - try { - if (!this.initiated) throw new Error("You have to initialize Riffy in your ready event"); - - const sources = source || this.defaultSearchPlatform; - - const node = this.nodeByRegion || this.leastUsedNodes[0]; - if (!node) throw new Error("No nodes are available."); - - const regex = /^https?:\/\//; - const identifier = regex.test(query) ? query : `${sources}:${query}`; - - let response = await node.rest.makeRequest(`GET`, `/${node.rest.version}/loadtracks?identifier=${encodeURIComponent(identifier)}`); - - // for resolving identifiers - Only works in Spotify and Youtube - if (response.loadType === "empty" || response.loadType === "NO_MATCHES") { - response = await node.rest.makeRequest(`GET`, `/${node.rest.version}/loadtracks?identifier=https://open.spotify.com/track/${query}`); - if (response.loadType === "empty" || response.loadType === "NO_MATCHES") { - response = await node.rest.makeRequest(`GET`, `/${node.rest.version}/loadtracks?identifier=https://www.youtube.com/watch?v=${query}`); - } - } - - if (node.rest.version === "v4") { - if (response.loadType === "track") { - this.tracks = response.data ? [new Track(response.data, requester, node)] : []; - } else if (response.loadType === "playlist") { - this.tracks = response.data?.tracks ? response.data.tracks.map((track) => new Track(track, requester, node)) : []; - } else { - this.tracks = response.loadType === "search" && response.data ? response.data.map((track) => new Track(track, requester, node)) : []; - } - } else { - this.tracks = response.data?.tracks ? response.tracks.map((track) => new Track(track, requester, node)) : []; - } - - if ( - node.rest.version === "v4" && - this.loadType === "playlist" - ) { - this.playlistInfo = response.data?.info ?? null; - } else { - this.playlistInfo = response.playlistInfo ?? null; - } - - this.loadType = response.loadType ?? null - this.pluginInfo = response.pluginInfo ?? null; - - return { - loadType: this.loadType, - exception: this.loadType == "error" ? response.data : this.loadType == "LOAD_FAILED" ? response?.exception : null, - playlistInfo: this.playlistInfo, - pluginInfo: this.pluginInfo, - tracks: this.tracks, - }; - } catch (error) { - throw new Error(error); + this.tracks = response.loadType === "search" && response.data ? response.data.map((track) => new Track(track, requester, requestNode)) : []; } + } else { + this.tracks = response.data?.tracks ? response.tracks.map((track) => new Track(track, requester, requestNode)) : []; + } + + if ( + requestNode.rest.version === "v4" && + response.loadType === "playlist" + ) { + this.playlistInfo = response.data?.info ?? null; + } else { + this.playlistInfo = response.playlistInfo ?? null; + } + + this.loadType = response.loadType ?? null + this.pluginInfo = response.pluginInfo ?? null; + + return { + loadType: this.loadType, + exception: this.loadType == "error" ? response.data : this.loadType == "LOAD_FAILED" ? response.exception : null, + playlistInfo: this.playlistInfo, + pluginInfo: this.pluginInfo, + tracks: this.tracks, + }; + } catch (error) { + throw new Error(error); } + } - get(guildId) { - const player = this.players.get(guildId); - if (!player) throw new Error(`Player not found for ${guildId} guildId`); - return player; - } + get(guildId) { + const player = this.players.get(guildId); + if (!player) throw new Error(`Player not found for ${guildId} guildId`); + return player; + } } module.exports = { Riffy }; diff --git a/build/structures/Track.js b/build/structures/Track.js index 4848e18..fc21564 100644 --- a/build/structures/Track.js +++ b/build/structures/Track.js @@ -2,8 +2,12 @@ const { getImageUrl } = require("../functions/fetchImage"); const escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); class Track { + + #rawData = {}; + #cachedThumbnail = null; constructor(data, requester, node) { - this.track = data.encoded + this.rawData = data; + this.track = data.encoded; this.info = { identifier: data.info.identifier, seekable: data.info.isSeekable, @@ -15,21 +19,24 @@ class Track { uri: data.info.uri, requester, sourceName: data.info.sourceName, - }; - - if (node.rest.version === "v4") { - this.info.isrc = data.info.isrc + _cachedThumbnail: data.info.thumbnail ?? null, + get thumbnail() { + if (data.info.thumbnail) return data.info.thumbnail; - if (data.info.thumbnail) { - this.info.thumbnail = data.info.thumbnail - } else if (data.info.artworkUrl) { - this.info.thumbnail = data.info.artworkUrl - } else { - this.info.thumbnail = getImageUrl(this.info) + if (node.rest.version === "v4") { + if (data.info.artworkUrl) { + this._cachedThumbnail = data.info.artworkUrl; + return data.info.artworkUrl + } else { + return !this._cachedThumbnail ? (this._cachedThumbnail = getImageUrl(this)) : this._cachedThumbnail ?? null + } + } else { + return !this._cachedThumbnail + ? (this._cachedThumbnail = getImageUrl(this)) + : this._cachedThumbnail ?? null; + } } - } else { - this.info.thumbnail = getImageUrl(this.info) - } + }; } async resolve(riffy) { diff --git a/package-lock.json b/package-lock.json index b04b1df..601e94e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,333 +9,230 @@ "version": "1.0.4", "license": "MIT", "dependencies": { - "@discordjs/collection": "^2.0.0", - "jsdom": "^23.0.1", - "undici": ">=6.6.1", + "undici": ">=6.11.1", "ws": "^8.16.0" + }, + "devDependencies": { + "discord.js": "^14.14.1" } }, - "node_modules/@discordjs/collection": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.0.0.tgz", - "integrity": "sha512-YTWIXLrf5FsrLMycpMM9Q6vnZoR/lN2AWX23/Cuo8uOOtS8eHB2dyQaaGnaF8aZPYnttf2bkLMcXn/j6JUOi3w==", - "engines": { - "node": ">=18" - } - }, - "node_modules/@fastify/busboy": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz", - "integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==", - "engines": { - "node": ">=14" - } - }, - "node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "node_modules/@discordjs/builders": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.7.0.tgz", + "integrity": "sha512-GDtbKMkg433cOZur8Dv6c25EHxduNIBsxeHrsRoIM8+AwmEZ8r0tEpckx/sHwTLwQPOF3e2JWloZh9ofCaMfAw==", + "dev": true, "dependencies": { - "debug": "^4.3.4" + "@discordjs/formatters": "^0.3.3", + "@discordjs/util": "^1.0.2", + "@sapphire/shapeshift": "^3.9.3", + "discord-api-types": "0.37.61", + "fast-deep-equal": "^3.1.3", + "ts-mixer": "^6.0.3", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 14" + "node": ">=16.11.0" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "node_modules/@discordjs/collection": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", + "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", + "dev": true, + "engines": { + "node": ">=16.11.0" + } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/@discordjs/formatters": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.3.3.tgz", + "integrity": "sha512-wTcI1Q5cps1eSGhl6+6AzzZkBBlVrBdc9IUhJbijRgVjCNIIIZPgqnUj3ntFODsHrdbGU8BEG9XmDQmgEEYn3w==", + "dev": true, "dependencies": { - "delayed-stream": "~1.0.0" + "discord-api-types": "0.37.61" }, "engines": { - "node": ">= 0.8" + "node": ">=16.11.0" } }, - "node_modules/cssstyle": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", - "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==", + "node_modules/@discordjs/rest": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.2.0.tgz", + "integrity": "sha512-nXm9wT8oqrYFRMEqTXQx9DUTeEtXUDMmnUKIhZn6O2EeDY9VCdwj23XCPq7fkqMPKdF7ldAfeVKyxxFdbZl59A==", + "dev": true, "dependencies": { - "rrweb-cssom": "^0.6.0" + "@discordjs/collection": "^2.0.0", + "@discordjs/util": "^1.0.2", + "@sapphire/async-queue": "^1.5.0", + "@sapphire/snowflake": "^3.5.1", + "@vladfrangu/async_event_emitter": "^2.2.2", + "discord-api-types": "0.37.61", + "magic-bytes.js": "^1.5.0", + "tslib": "^2.6.2", + "undici": "5.27.2" }, "engines": { - "node": ">=14" + "node": ">=16.11.0" } }, - "node_modules/data-urls": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", - "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" - }, + "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.0.0.tgz", + "integrity": "sha512-YTWIXLrf5FsrLMycpMM9Q6vnZoR/lN2AWX23/Cuo8uOOtS8eHB2dyQaaGnaF8aZPYnttf2bkLMcXn/j6JUOi3w==", + "dev": true, "engines": { "node": ">=18" } }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/@discordjs/rest/node_modules/undici": { + "version": "5.27.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.27.2.tgz", + "integrity": "sha512-iS857PdOEy/y3wlM3yRp+6SNQQ6xU0mmZcwRSriqk+et/cwWAtwmIGf6WkoDN2EK/AMdCO/dfXzIwi+rFMrjjQ==", + "dev": true, "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" + "@fastify/busboy": "^2.0.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "engines": { - "node": ">=0.4.0" + "node": ">=14.0" } }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "node_modules/@discordjs/util": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.0.2.tgz", + "integrity": "sha512-IRNbimrmfb75GMNEjyznqM1tkI7HrZOf14njX7tCAAUetyZM1Pr8hX/EK2lxBCOgWDRmigbp24fD1hdMfQK5lw==", + "dev": true, "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "node": ">=16.11.0" } }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "node_modules/@discordjs/ws": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.0.2.tgz", + "integrity": "sha512-+XI82Rm2hKnFwAySXEep4A7Kfoowt6weO6381jgW+wVdTpMS/56qCvoXyFRY0slcv7c/U8My2PwIB2/wEaAh7Q==", + "dev": true, "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "@discordjs/collection": "^2.0.0", + "@discordjs/rest": "^2.1.0", + "@discordjs/util": "^1.0.2", + "@sapphire/async-queue": "^1.5.0", + "@types/ws": "^8.5.9", + "@vladfrangu/async_event_emitter": "^2.2.2", + "discord-api-types": "0.37.61", + "tslib": "^2.6.2", + "ws": "^8.14.2" }, "engines": { - "node": ">= 6" + "node": ">=16.11.0" } }, - "node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", - "dependencies": { - "whatwg-encoding": "^3.1.1" - }, + "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.0.0.tgz", + "integrity": "sha512-YTWIXLrf5FsrLMycpMM9Q6vnZoR/lN2AWX23/Cuo8uOOtS8eHB2dyQaaGnaF8aZPYnttf2bkLMcXn/j6JUOi3w==", + "dev": true, "engines": { "node": ">=18" } }, - "node_modules/http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, "engines": { - "node": ">= 14" + "node": ">=14" } }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, + "node_modules/@sapphire/async-queue": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.2.tgz", + "integrity": "sha512-7X7FFAA4DngXUl95+hYbUF19bp1LGiffjJtu7ygrZrbdCSsdDDBaSjB7Akw0ZbOu6k0xpXyljnJ6/RZUvLfRdg==", + "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=v14.0.0", + "npm": ">=7.0.0" } }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" - }, - "node_modules/jsdom": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-23.0.1.tgz", - "integrity": "sha512-2i27vgvlUsGEBO9+/kJQRbtqtm+191b5zAZrU/UezVmnC2dlDAFLgDYJvAEi94T4kjsRKkezEtLQTgsNEsW2lQ==", + "node_modules/@sapphire/shapeshift": { + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.9.6.tgz", + "integrity": "sha512-4+Na/fxu2SEepZRb9z0dbsVh59QtwPuBg/UVaDib3av7ZY14b14+z09z6QVn0P6Dv6eOU2NDTsjIi0mbtgP56g==", + "dev": true, "dependencies": { - "cssstyle": "^3.0.0", - "data-urls": "^5.0.0", - "decimal.js": "^10.4.3", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.2", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.7", - "parse5": "^7.1.2", - "rrweb-cssom": "^0.6.0", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.3", - "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^3.1.1", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0", - "ws": "^8.14.2", - "xml-name-validator": "^5.0.0" + "fast-deep-equal": "^3.1.3", + "lodash": "^4.17.21" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "canvas": "^2.11.2" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } + "node": ">=v18" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/@sapphire/snowflake": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.1.tgz", + "integrity": "sha512-BxcYGzgEsdlG0dKAyOm0ehLGm2CafIrfQTZGWgkfKYbj+pNNsorZ7EotuZukc2MT70E0UbppVbtpBrqpzVzjNA==", + "dev": true, "engines": { - "node": ">= 0.6" + "node": ">=v14.0.0", + "npm": ">=7.0.0" } }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "node_modules/@types/node": { + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", + "dev": true, "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" + "undici-types": "~5.26.4" } }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/nwsapi": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", - "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==" - }, - "node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "node_modules/@types/ws": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.9.tgz", + "integrity": "sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==", + "dev": true, "dependencies": { - "entities": "^4.4.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "engines": { - "node": ">=6" + "@types/node": "*" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" - }, - "node_modules/rrweb-cssom": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", - "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dependencies": { - "xmlchars": "^2.2.0" - }, + "node_modules/@vladfrangu/async_event_emitter": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.2.4.tgz", + "integrity": "sha512-ButUPz9E9cXMLgvAW8aLAKKJJsPu1dY1/l/E8xzLFuysowXygs6GBcyunK9rnGC4zTsnIc2mQo71rGw9U+Ykug==", + "dev": true, "engines": { - "node": ">=v12.22.7" + "node": ">=v14.0.0", + "npm": ">=7.0.0" } }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" - }, - "node_modules/tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } + "node_modules/discord-api-types": { + "version": "0.37.61", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.61.tgz", + "integrity": "sha512-o/dXNFfhBpYHpQFdT6FWzeO7pKc838QeeZ9d91CfVAtpr5XLK4B/zYxQbYgPdoMiTDvJfzcsLW5naXgmHGDNXw==", + "dev": true }, - "node_modules/tr46": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", - "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "node_modules/discord.js": { + "version": "14.14.1", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.14.1.tgz", + "integrity": "sha512-/hUVzkIerxKHyRKopJy5xejp4MYKDPTszAnpYxzVVv4qJYf+Tkt+jnT2N29PIPschicaEEpXwF2ARrTYHYwQ5w==", + "dev": true, "dependencies": { - "punycode": "^2.3.1" + "@discordjs/builders": "^1.7.0", + "@discordjs/collection": "1.5.3", + "@discordjs/formatters": "^0.3.3", + "@discordjs/rest": "^2.1.0", + "@discordjs/util": "^1.0.2", + "@discordjs/ws": "^1.0.2", + "@sapphire/snowflake": "3.5.1", + "@types/ws": "8.5.9", + "discord-api-types": "0.37.61", + "fast-deep-equal": "3.1.3", + "lodash.snakecase": "4.1.1", + "tslib": "2.6.2", + "undici": "5.27.2", + "ws": "8.14.2" }, "engines": { - "node": ">=18" + "node": ">=16.11.0" } }, "node_modules/undici": { @@ -346,76 +243,80 @@ "@fastify/busboy": "^2.0.0" }, "engines": { - "node": ">=18.0" + "node": ">=14.0" } }, - "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "node_modules/discord.js/node_modules/ws": { + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "dev": true, "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", - "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", - "dependencies": { - "xml-name-validator": "^5.0.0" + "node": ">=10.0.0" }, - "engines": { - "node": ">=18" - } - }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "dependencies": { - "iconv-lite": "0.6.3" + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" }, - "engines": { - "node": ">=18" + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", - "engines": { - "node": ">=18" - } + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "dev": true + }, + "node_modules/magic-bytes.js": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz", + "integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==", + "dev": true + }, + "node_modules/ts-mixer": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", + "dev": true + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true }, - "node_modules/whatwg-url": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", - "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", - "dependencies": { - "tr46": "^5.0.0", - "webidl-conversions": "^7.0.0" - }, + "node_modules/undici": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.9.0.tgz", + "integrity": "sha512-XPWfXzJedevUziHwun70EKNvGnxv4CnfraFZ4f/JV01+fcvMYzHE26r/j8AY/9c/70nkN4B1zX7E2Oyuqwz4+Q==", "engines": { - "node": ">=18" + "node": ">=18.0" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/ws": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", @@ -435,19 +336,6 @@ "optional": true } } - }, - "node_modules/xml-name-validator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", - "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", - "engines": { - "node": ">=18" - } - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" } } } diff --git a/package.json b/package.json index 7e89df8..0d2252d 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,7 @@ "build" ], "dependencies": { - "@discordjs/collection": "^2.0.0", - "jsdom": "^23.0.1", - "undici": ">=6.6.1", + "undici": ">=6.11.1", "ws": "^8.16.0" }, "license": "MIT", @@ -25,10 +23,10 @@ "riffyjs", "eris" ], - "author": [ - "@flameface", - "@unschooledgamers", - "@elitex" + "contributors": [ + "FlameFace (https://github.com/flameface)", + "Elitex (https://github.com/Elitex07)", + "UnschooledGamer (https://github.com/unschooledgamer/)" ], "repository": { "type": "git", @@ -41,4 +39,4 @@ "devDependencies": { "discord.js": "^14.15.3" } -} \ No newline at end of file +} diff --git a/test/autoplay-test.js b/test/autoplay-test.js new file mode 100644 index 0000000..f89514a --- /dev/null +++ b/test/autoplay-test.js @@ -0,0 +1,9 @@ +const { soundcloud, spotify } = require("../build/functions/autoPlay"); + +soundcloud("https://soundcloud.com/alanwalker/alan-walker-peder-elias-putri-ariani-who-i-am").then(x => { + console.log(x) +}) + +spotify("1zHzHVjNlhj2PwRlngEKEo").then(x => { + console.log(x) +}) \ No newline at end of file diff --git a/test/track-test.js b/test/track-test.js new file mode 100644 index 0000000..2e66b28 --- /dev/null +++ b/test/track-test.js @@ -0,0 +1,35 @@ +console.log(process.env) +const { Track } = require("../build/index") + + + +const testTrack = { + "encoded": "QAABRgMAPFJPWSBLTk9YIC0gTWVtb3J5IEJveCB8IER1YnN0ZXAgfCBOQ1MgLSBDb3B5cmlnaHQgRnJlZSBNdXNpYwARTm9Db3B5cmlnaHRTb3VuZHMAAAAAAAH36AALREZ5SUE3NkJ2RDAAAQAraHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1ERnlJQTc2QnZEMAEAm2h0dHBzOi8vaS55dGltZy5jb20vdmkvREZ5SUE3NkJ2RDAvbWF4cmVzZGVmYXVsdC5qcGc/c3FwPS1vYXltd0VtQ0lBS0VOQUY4cXVLcVFNYThBRUItQUgtQ1lBQzBBV0tBZ3dJQUJBQkdDd2dVaWhfTUE4PSZycz1BT240Q0xETjJsZDdUVU51Q2tvQVZEVVJfdnVqUzd6X3B3AAAHeW91dHViZQAAAAAAAAAA", + "info": { + "identifier": "DFyIA76BvD0", + "isSeekable": true, + "author": "NoCopyrightSounds", + "length": 129000, + "isStream": false, + "position": 0, + "title": "ROY KNOX - Memory Box | Dubstep | NCS - Copyright Free Music", + "uri": "https://www.youtube.com/watch?v=DFyIA76BvD0", + "sourceName": "youtube", + "artworkUrl": "https://i.ytimg.com/vi/DFyIA76BvD0/maxresdefault.jpg?sqp=-oaymwEmCIAKENAF8quKqQMa8AEB-AH-CYAC0AWKAgwIABABGCwgUih_MA8=&rs=AOn4CLDN2ld7TUNuCkoAVDUR_vujS7z_pw", + "isrc": null + }, + "pluginInfo": {}, + "userData": {} + } + +const track = new Track(testTrack, {}, { rest: { version: "v4"}}); + +console.log(track); +(async () => { +console.log("1", await track.info.thumbnail); +console.log("2", await track.info.thumbnail); +// console.log("2", await track.#cachedThumbnail, track.#rawData); +setTimeout(async () => { +console.log("3", await track._cachedThumbnail) +}, 5000) +})() diff --git a/test/v3.js b/test/v3.js index eb46e17..b4445a4 100644 --- a/test/v3.js +++ b/test/v3.js @@ -166,27 +166,16 @@ client.on("messageCreate", async (message) => { if (!player) return message.channel.send("No player found."); const loop = args[0]; - if (!loop) return message.channel.send("Please provide a valid loop option."); - - if (loop === "queue") { - if (player.loop === "queue") { - player.setLoop("none") - message.channel.send(`Queue loop is now disabled.`); - } else { - player.setLoop("queue") - message.channel.send(`Queue loop is now enabled.`); - } - } else if (loop === "track") { - if (player.loop === "track") { - player.setLoop("none") - message.channel.send(`Track loop is now disabled.`); - } else { - player.setLoop("track") - message.channel.send(`Track loop is now enabled.`); - } - } else { - return message.channel.send("Please provide a valid loop option."); - } + if (!loop || !["queue", "track"].includes(loop)) + return message.channel.send("Please provide a valid loop option: `queue` or `track`."); + + const toggleLoop = () => { + const loopType = player.loop === loop ? "none" : loop; + player.setLoop(loopType); + message.channel.send(`${loop.charAt(0).toUpperCase() + loop.slice(1)} loop is now ${loopType === "none" ? "disabled" : "enabled"}.`); + }; + + toggleLoop(); } if (command === "shuffle") { @@ -197,13 +186,6 @@ client.on("messageCreate", async (message) => { message.channel.send("Shuffled the queue."); } - if (command === '8d') { - const player = client.riffy.players.get(message.guild.id); - if (!player) return message.channel.send("No player found."); - - player.filters.setBassboost(true); - } - if (command === "remove") { const player = client.riffy.players.get(message.guild.id); if (!player) return message.channel.send("No player found."); @@ -229,50 +211,31 @@ client.on("messageCreate", async (message) => { const filter = args[0]; - if (filter === "8d") { - player.filters.set8D(true) - message.channel.send("8D filter enabled.") - } else if (filter === "bassboost") { - player.filters.setBassboost(true) - message.channel.send("Bassboost filter enabled.") - } else if (filter === "channelmix") { - player.filters.setChannelMix(true) - message.channel.send("Channelmix filter enabled.") - } else if (filter === "distortion") { - player.filters.setDistortion(true) - message.channel.send("Distortion filter enabled.") - } else if (filter === "karaoke") { - player.filters.setKaraoke(true) - message.channel.send("Karaoke filter enabled.") - } else if (filter === "lowpass") { - player.filters.setLowPass(true) - message.channel.send("Lowpass filter enabled.") - } else if (filter === "nightcore") { - player.filters.setNightcore(true) - message.channel.send("Nightcore filter enabled.") - } else if (filter === "rotate") { - player.filters.setRotation(true) - message.channel.send("Rotate filter enabled.") - } else if (filter === "slowmode") { - player.filters.setSlowmode(true) - message.channel.send("Slowmode filter enabled.") - } else if (filter === "timescale") { - player.filters.setTimescale(true) - message.channel.send("Timescale filter enabled.") - } else if (filter === "tremolo") { - player.filters.setTremolo(true) - message.channel.send("Tremolo filter enabled.") - } else if (filter === "vaporwave") { - player.filters.setVaporwave(true) - message.channel.send("Vaporwave filter enabled.") - } else if (filter === "vibrato") { - player.filters.setVibrato(true) - message.channel.send("Vibrato filter enabled.") + const filterActions = { + "8d": { method: "set8D", message: "8D filter enabled." }, + "bassboost": { method: "setBassboost", message: "Bassboost filter enabled." }, + "channelmix": { method: "setChannelMix", message: "Channelmix filter enabled." }, + "distortion": { method: "setDistortion", message: "Distortion filter enabled." }, + "karaoke": { method: "setKaraoke", message: "Karaoke filter enabled." }, + "lowpass": { method: "setLowPass", message: "Lowpass filter enabled." }, + "nightcore": { method: "setNightcore", message: "Nightcore filter enabled." }, + "rotate": { method: "setRotation", message: "Rotate filter enabled." }, + "slowmode": { method: "setSlowmode", message: "Slowmode filter enabled." }, + "timescale": { method: "setTimescale", message: "Timescale filter enabled." }, + "tremolo": { method: "setTremolo", message: "Tremolo filter enabled." }, + "vaporwave": { method: "setVaporwave", message: "Vaporwave filter enabled." }, + "vibrato": { method: "setVibrato", message: "Vibrato filter enabled." } + }; + + const action = filterActions[filter]; + if (action) { + player.filters[action.method](true); + message.channel.send(action.message); } else { - return message.channel.send("Please provide a valid filter option."); + message.channel.send("Please provide a valid filter option."); } - console.log(player.filters) + // console.log(player.filters); } if (command === "dfilter") { @@ -281,50 +244,50 @@ client.on("messageCreate", async (message) => { const filter = args[0]; - if (filter === "8d") { - player.filters.set8D(false) - message.channel.send("8D filter disabled.") - } else if (filter === "bassboost") { - player.filters.setBassboost(false) - message.channel.send("Bassboost filter disabled.") - } else if (filter === "channelmix") { - player.filters.setChannelMix(false) - message.channel.send("Channelmix filter disabled.") - } else if (filter === "distortion") { - player.filters.setDistortion(false) - message.channel.send("Distortion filter disabled.") - } else if (filter === "karaoke") { - player.filters.setKaraoke(false) - message.channel.send("Karaoke filter disabled.") - } else if (filter === "lowpass") { - player.filters.setLowPass(false) - message.channel.send("Lowpass filter disabled.") - } else if (filter === "nightcore") { - player.filters.setNightcore(false) - message.channel.send("Nightcore filter disabled.") - } else if (filter === "rotate") { - player.filters.setRotation(false) - message.channel.send("Rotate filter disabled.") - } else if (filter === "slowmode") { - player.filters.setSlowmode(false) - message.channel.send("Slowmode filter disabled.") - } else if (filter === "timescale") { - player.filters.setTimescale(false) - message.channel.send("Timescale filter disabled.") - } else if (filter === "tremolo") { - player.filters.setTremolo(false) - message.channel.send("Tremolo filter disabled.") - } else if (filter === "vaporwave") { - player.filters.setVaporwave(false) - message.channel.send("Vaporwave filter disabled.") - } else if (filter === "vibrato") { - player.filters.setVibrato(false) - message.channel.send("Vibrato filter disabled.") + const filterActions = { + "8d": { method: "set8D", message: "8D filter disabled." }, + "bassboost": { method: "setBassboost", message: "Bassboost filter disabled." }, + "channelmix": { method: "setChannelMix", message: "Channelmix filter disabled." }, + "distortion": { method: "setDistortion", message: "Distortion filter disabled." }, + "karaoke": { method: "setKaraoke", message: "Karaoke filter disabled." }, + "lowpass": { method: "setLowPass", message: "Lowpass filter disabled." }, + "nightcore": { method: "setNightcore", message: "Nightcore filter disabled." }, + "rotate": { method: "setRotation", message: "Rotate filter disabled." }, + "slowmode": { method: "setSlowmode", message: "Slowmode filter disabled." }, + "timescale": { method: "setTimescale", message: "Timescale filter disabled." }, + "tremolo": { method: "setTremolo", message: "Tremolo filter disabled." }, + "vaporwave": { method: "setVaporwave", message: "Vaporwave filter disabled." }, + "vibrato": { method: "setVibrato", message: "Vibrato filter disabled." } + }; + + const action = filterActions[filter]; + if (action) { + player.filters[action.method](false); + message.channel.send(action.message); } else { - return message.channel.send("Please provide a valid filter option."); + message.channel.send("Please provide a valid filter option."); } - console.log(player.filters) + // console.log(player.filters); + } + + if (command === "eval" && args[0]) { + try { + let evaled = await eval(args.join(" ")); + let string = inspect(evaled); + + if (string.includes(client.token)) + return message.reply("No token grabbing."); + + if (string.length > 2000) { + let output = new AttachmentBuilder(Buffer.from(string), { name: "result.js" }); + return message.channel.send({ files: [output] }); + } + + message.channel.send(`\`\`\`js\n${string}\n\`\`\``); + } catch (error) { + message.reply(`\`\`\`js\n${error}\n\`\`\``); + } } if (command === "eval" && args[0]) { try { diff --git a/test/v4.js b/test/v4.js index 6003ea4..f3299a2 100644 --- a/test/v4.js +++ b/test/v4.js @@ -3,37 +3,37 @@ const { Riffy } = require("../build/index.js"); const { inspect } = require("node:util") const client = new Client({ - intents: [ - "Guilds", - "GuildMessages", - "GuildVoiceStates", - "GuildMessageReactions", - "MessageContent", - "DirectMessages" - ] + intents: [ + "Guilds", + "GuildMessages", + "GuildVoiceStates", + "GuildMessageReactions", + "MessageContent", + "DirectMessages", + ], }); const nodes = [ - { - host: "localhost", - password: "youshallnotpass", - port: 2333, - secure: false - }, + { + host: "localhost", + password: "youshallnotpass", + port: 2333, + secure: false, + }, ]; client.riffy = new Riffy(client, nodes, { - send: (payload) => { - const guild = client.guilds.cache.get(payload.d.guild_id); - if (guild) guild.shard.send(payload); - }, - defaultSearchPlatform: "ytmsearch", - restVersion: "v4" + send: (payload) => { + const guild = client.guilds.cache.get(payload.d.guild_id); + if (guild) guild.shard.send(payload); + }, + defaultSearchPlatform: "ytmsearch", + restVersion: "v4", }); client.on("ready", () => { - client.riffy.init(client.user.id); - console.log(`Logged in as ${client.user.tag}`); + client.riffy.init(client.user.id); + console.log(`Logged in as ${client.user.tag}`); }); client.on("messageCreate", async (message) => { @@ -151,165 +151,240 @@ client.on("messageCreate", async (message) => { message.channel.send({ embeds: [embed] }); } - - if (command === "loop") { - const player = client.riffy.players.get(message.guild.id); - if (!player) return message.channel.send("No player found."); - - const loop = args[0]; - if (!loop) return message.channel.send("Please provide a valid loop option."); - - if (loop === "queue") { - if (player.loop === "queue") { - player.setLoop("none") - message.channel.send(`Queue loop is now disabled.`); - } else { - player.setLoop("queue") - message.channel.send(`Queue loop is now enabled.`); - } - } else if (loop === "track") { - if (player.loop === "track") { - player.setLoop("none") - message.channel.send(`Track loop is now disabled.`); - } else { - player.setLoop("track") - message.channel.send(`Track loop is now enabled.`); - } - } else { - return message.channel.send("Please provide a valid loop option."); - } - } - - if (command === "shuffle") { - const player = client.riffy.players.get(message.guild.id); - if (!player) return message.channel.send("No player found."); - - player.queue.shuffle(); - message.channel.send("Shuffled the queue."); - } - - if (command === "remove") { - const player = client.riffy.players.get(message.guild.id); - if (!player) return message.channel.send("No player found."); - - const index = parseInt(args[0]); - if (!index || isNaN(index)) return message.channel.send("Please provide a valid number."); - - const removed = player.queue.remove(index); - message.channel.send(`Removed: \`${removed.info.title}\` from the queue.`); - } - - if (command === "clear") { - const player = client.riffy.players.get(message.guild.id); - if (!player) return message.channel.send("No player found."); - - player.queue.clear(); - message.channel.send("Cleared the queue."); + } + + if (command === "skip") { + const player = client.riffy.players.get(message.guild.id); + if (!player) return message.channel.send("No player found."); + + player.stop(); + message.channel.send("Skipped the current song."); + } + + if (command === "stop") { + const player = client.riffy.players.get(message.guild.id); + if (!player) return message.channel.send("No player found."); + + player.destroy(); + message.channel.send("Stopped the player."); + } + + if (command === "pause") { + const player = client.riffy.players.get(message.guild.id); + if (!player) return message.channel.send("No player found."); + + player.pause(true); + message.channel.send("Paused the player."); + } + + if (command === "resume") { + const player = client.riffy.players.get(message.guild.id); + if (!player) return message.channel.send("No player found."); + + player.pause(false); + message.channel.send("Resumed the player."); + } + + if (command === "volume") { + const player = client.riffy.players.get(message.guild.id); + if (!player) return message.channel.send("No player found."); + + const volume = parseInt(args[0]); + if (!volume || isNaN(volume)) + return message.channel.send("Please provide a valid number."); + + player.setVolume(volume); + message.channel.send(`Set the player volume to: \`${volume}\`.`); + } + + if (command === "queue") { + const player = client.riffy.players.get(message.guild.id); + if (!player) return message.channel.send("No player found."); + + const queue = player.queue; + if (!queue.length) return message.channel.send("No songs in queue."); + + const embed = { + title: "Queue", + description: queue + .map((track, i) => { + return `${i + 1}) ${track.info.title} | ${track.info.author}`; + }) + .join("\n"), + }; + + message.channel.send({ embeds: [embed] }); + } + + if (command === "nowplaying") { + const player = client.riffy.players.get(message.guild.id); + if (!player) return message.channel.send("No player found."); + + console.log(player); + const track = player.current; + + if (!track) return message.channel.send("No song currently playing."); + + const embed = { + title: "Now Playing", + description: `${track.info.title} | ${track.info.author}`, + }; + + message.channel.send({ embeds: [embed] }); + } + + if (command === "loop") { + const player = client.riffy.players.get(message.guild.id); + if (!player) return message.channel.send("No player found."); + + const loop = args[0]; + if (!loop || !["queue", "track"].includes(loop)) + return message.channel.send( + "Please provide a valid loop option: `queue` or `track`." + ); + + const toggleLoop = () => { + const loopType = player.loop === loop ? "none" : loop; + player.setLoop(loopType); + message.channel.send( + `${loop.charAt(0).toUpperCase() + loop.slice(1)} loop is now ${ + loopType === "none" ? "disabled" : "enabled" + }.` + ); + }; + + toggleLoop(); + } + + if (command === "shuffle") { + const player = client.riffy.players.get(message.guild.id); + if (!player) return message.channel.send("No player found."); + + player.queue.shuffle(); + message.channel.send("Shuffled the queue."); + } + + if (command === "remove") { + const player = client.riffy.players.get(message.guild.id); + if (!player) return message.channel.send("No player found."); + + const index = parseInt(args[0]); + if (!index || isNaN(index)) + return message.channel.send("Please provide a valid number."); + + const removed = player.queue.remove(index); + message.channel.send(`Removed: \`${removed.info.title}\` from the queue.`); + } + + if (command === "clear") { + const player = client.riffy.players.get(message.guild.id); + if (!player) return message.channel.send("No player found."); + + player.queue.clear(); + message.channel.send("Cleared the queue."); + } + + if (command === "filter") { + const player = client.riffy.players.get(message.guild.id); + if (!player) return message.channel.send("No player found."); + + const filter = args[0]; + + const filterActions = { + "8d": { method: "set8D", message: "8D filter enabled." }, + bassboost: { + method: "setBassboost", + message: "Bassboost filter enabled.", + }, + channelmix: { + method: "setChannelMix", + message: "Channelmix filter enabled.", + }, + distortion: { + method: "setDistortion", + message: "Distortion filter enabled.", + }, + karaoke: { method: "setKaraoke", message: "Karaoke filter enabled." }, + lowpass: { method: "setLowPass", message: "Lowpass filter enabled." }, + nightcore: { + method: "setNightcore", + message: "Nightcore filter enabled.", + }, + rotate: { method: "setRotation", message: "Rotate filter enabled." }, + slowmode: { method: "setSlowmode", message: "Slowmode filter enabled." }, + timescale: { + method: "setTimescale", + message: "Timescale filter enabled.", + }, + tremolo: { method: "setTremolo", message: "Tremolo filter enabled." }, + vaporwave: { + method: "setVaporwave", + message: "Vaporwave filter enabled.", + }, + vibrato: { method: "setVibrato", message: "Vibrato filter enabled." }, + }; + + const action = filterActions[filter]; + if (action) { + player.filters[action.method](true); + message.channel.send(action.message); + } else { + message.channel.send("Please provide a valid filter option."); } - if (command === "filter") { - const player = client.riffy.players.get(message.guild.id); - if (!player) return message.channel.send("No player found."); - - const filter = args[0]; - - if (filter === "8d") { - player.filters.set8D(true) - message.channel.send("8D filter enabled.") - } else if (filter === "bassboost") { - player.filters.setBassboost(true) - message.channel.send("Bassboost filter enabled.") - } else if (filter === "channelmix") { - player.filters.setChannelMix(true) - message.channel.send("Channelmix filter enabled.") - } else if (filter === "distortion") { - player.filters.setDistortion(true) - message.channel.send("Distortion filter enabled.") - } else if (filter === "karaoke") { - player.filters.setKaraoke(true) - message.channel.send("Karaoke filter enabled.") - } else if (filter === "lowpass") { - player.filters.setLowPass(true) - message.channel.send("Lowpass filter enabled.") - } else if (filter === "nightcore") { - player.filters.setNightcore(true) - message.channel.send("Nightcore filter enabled.") - } else if (filter === "rotate") { - player.filters.setRotation(true) - message.channel.send("Rotate filter enabled.") - } else if (filter === "slowmode") { - player.filters.setSlowmode(true) - message.channel.send("Slowmode filter enabled.") - } else if (filter === "timescale") { - player.filters.setTimescale(true) - message.channel.send("Timescale filter enabled.") - } else if (filter === "tremolo") { - player.filters.setTremolo(true) - message.channel.send("Tremolo filter enabled.") - } else if (filter === "vaporwave") { - player.filters.setVaporwave(true) - message.channel.send("Vaporwave filter enabled.") - } else if (filter === "vibrato") { - player.filters.setVibrato(true) - message.channel.send("Vibrato filter enabled.") - } else { - return message.channel.send("Please provide a valid filter option."); - } - - console.log(player.filters) + // console.log(player.filters); + } + + if (command === "dfilter") { + const player = client.riffy.players.get(message.guild.id); + if (!player) return message.channel.send("No player found."); + + const filter = args[0]; + + const filterActions = { + "8d": { method: "set8D", message: "8D filter disabled." }, + bassboost: { + method: "setBassboost", + message: "Bassboost filter disabled.", + }, + channelmix: { + method: "setChannelMix", + message: "Channelmix filter disabled.", + }, + distortion: { + method: "setDistortion", + message: "Distortion filter disabled.", + }, + karaoke: { method: "setKaraoke", message: "Karaoke filter disabled." }, + lowpass: { method: "setLowPass", message: "Lowpass filter disabled." }, + nightcore: { + method: "setNightcore", + message: "Nightcore filter disabled.", + }, + rotate: { method: "setRotation", message: "Rotate filter disabled." }, + slowmode: { method: "setSlowmode", message: "Slowmode filter disabled." }, + timescale: { + method: "setTimescale", + message: "Timescale filter disabled.", + }, + tremolo: { method: "setTremolo", message: "Tremolo filter disabled." }, + vaporwave: { + method: "setVaporwave", + message: "Vaporwave filter disabled.", + }, + vibrato: { method: "setVibrato", message: "Vibrato filter disabled." }, + }; + + const action = filterActions[filter]; + if (action) { + player.filters[action.method](false); + message.channel.send(action.message); + } else { + message.channel.send("Please provide a valid filter option."); } - if (command === "dfilter") { - const player = client.riffy.players.get(message.guild.id); - if (!player) return message.channel.send("No player found."); - - const filter = args[0]; - - if (filter === "8d") { - player.filters.set8D(false) - message.channel.send("8D filter disabled.") - } else if (filter === "bassboost") { - player.filters.setBassboost(false) - message.channel.send("Bassboost filter disabled.") - } else if (filter === "channelmix") { - player.filters.setChannelMix(false) - message.channel.send("Channelmix filter disabled.") - } else if (filter === "distortion") { - player.filters.setDistortion(false) - message.channel.send("Distortion filter disabled.") - } else if (filter === "karaoke") { - player.filters.setKaraoke(false) - message.channel.send("Karaoke filter disabled.") - } else if (filter === "lowpass") { - player.filters.setLowPass(false) - message.channel.send("Lowpass filter disabled.") - } else if (filter === "nightcore") { - player.filters.setNightcore(false) - message.channel.send("Nightcore filter disabled.") - } else if (filter === "rotate") { - player.filters.setRotation(false) - message.channel.send("Rotate filter disabled.") - } else if (filter === "slowmode") { - player.filters.setSlowmode(false) - message.channel.send("Slowmode filter disabled.") - } else if (filter === "timescale") { - player.filters.setTimescale(false) - message.channel.send("Timescale filter disabled.") - } else if (filter === "tremolo") { - player.filters.setTremolo(false) - message.channel.send("Tremolo filter disabled.") - } else if (filter === "vaporwave") { - player.filters.setVaporwave(false) - message.channel.send("Vaporwave filter disabled.") - } else if (filter === "vibrato") { - player.filters.setVibrato(false) - message.channel.send("Vibrato filter disabled.") - } else { - return message.channel.send("Please provide a valid filter option."); - } - - console.log(player.filters) - } + // console.log(player.filters); + } if (command === "eval" && args[0]) { try { @@ -333,40 +408,69 @@ client.on("messageCreate", async (message) => { } }) -client.riffy.on("nodeConnect", node => { - console.log(`Node "${node.name}" connected.`) -}) +client.riffy.on("nodeConnect", (node) => { + console.log( + `Node "${node.name}" connected, with sessionId ${node.sessionId}` + ); +}); client.riffy.on("nodeError", (node, error) => { - console.log(`Node "${node.name}" encountered an error: ${error}`) -}) + console.log(`Node "${node.name}" encountered an error: ${error}`); +}); client.riffy.on("nodeReconnect", (node) => { - console.log(`Node "${node.name}" reconnecting.`) -}) + console.log(`Node "${node.name}" reconnecting.`); +}); client.riffy.on("trackStart", async (player, track) => { - const channel = client.channels.cache.get(player.textChannel); + const channel = client.channels.cache.get(player.textChannel); - channel.send(`Now playing: \`${track.info.title}\` by \`${track.info.author}\`.`); + channel.send( + `Now playing: \`${track.info.title}\` by \`${track.info.author}\`.` + ); }); client.riffy.on("queueEnd", async (player) => { - const channel = client.channels.cache.get(player.textChannel); + const channel = client.channels.cache.get(player.textChannel); - const autoplay = false; + const autoplay = false; - if (autoplay) { - player.autoplay(player) - } else { - player.destroy(); - channel.send("Queue has ended."); - } -}) + if (autoplay) { + player.autoplay(player); + } else { + player.destroy(); + channel.send("Queue has ended."); + } +}); + +process.on("uncaughtException", (err, origin) => + console.log( + `[UNCAUGHT ERRORS Reporting - Exception] >> origin: ${origin} | Error: ${err.stack ?? err}` + ) +); +process.on("unhandledRejection", (err, _) => + console.log( + `[unhandled ERRORS Reporting - Rejection] >> ${err}, Promise: ignored/not included` + ) +); client.on("raw", (d) => { - if (![GatewayDispatchEvents.VoiceStateUpdate, GatewayDispatchEvents.VoiceServerUpdate,].includes(d.t)) return; - client.riffy.updateVoiceState(d); + if ( + ![ + GatewayDispatchEvents.VoiceStateUpdate, + GatewayDispatchEvents.VoiceServerUpdate, + ].includes(d.t) + ) + return; + client.riffy.updateVoiceState(d); +}); + +client.on("debug", (...args) => { + console.log(`[DEBUG - d.js]`, ...args); +}); + +client.riffy.on("debug", (...m) => { + console.log(`[DEBUG - RIFFY]`, ...m); }); -client.login(""); \ No newline at end of file +client.login("");