Compare commits

...

10 commits

Author SHA1 Message Date
Jonny_Bro (Nikita)
6f189cc523 v5.3.14
Waiting for proper fix...
2023-03-15 23:39:47 +05:00
Jonny_Bro (Nikita)
d7ce7acb22 v5.3.13 - 1 minute connection bug is fixed!
Update @discordjs/voice to 0.15.0 or @latest
2023-03-14 13:32:09 +05:00
Jonny_Bro (Nikita)
5dc2774e0f v5.3.12
Temporary fix for voice connection issues
2023-03-02 22:10:25 +05:00
Jonny_Bro (Nikita)
2f9c48b927 rule for formatter 2023-03-01 23:48:36 +05:00
Jonny_Bro (Nikita)
83c6179e1e format with tabs 2023-03-01 23:48:04 +05:00
JonnyBro
241a22b5d1 pretty 2023-02-22 13:48:29 +05:00
JonnyBro
ca53b6da0c v5.3.11
Somewhat working Spotify support
2023-02-22 13:47:18 +05:00
JonnyBro
641ef8ee85 v5.3.10
fixed current track to not add twice to previousTracks
fixed bot disconnection on member move
Thanks PR's from original repo!
2023-01-04 00:22:41 +05:00
JonnyBro
5d57f6589a pretty 2022-10-31 14:16:46 +05:00
JonnyBro
e2fad021d6 v5.3.9 2022-10-31 14:16:33 +05:00
18 changed files with 2514 additions and 2476 deletions

View file

@ -17,6 +17,7 @@
"@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/ban-ts-comment": "error", "@typescript-eslint/ban-ts-comment": "error",
"semi": "error", "semi": "error",
"no-console": "error" "no-console": "error",
"no-mixed-spaces-and-tabs": "off"
} }
} }

View file

@ -3,5 +3,6 @@
"trailingComma": "none", "trailingComma": "none",
"singleQuote": false, "singleQuote": false,
"tabWidth": 4, "tabWidth": 4,
"useTabs": true,
"semi": true "semi": true
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "discord-player-play-dl", "name": "discord-player-play-dl",
"version": "5.3.8", "version": "5.3.14",
"description": "Complete framework to facilitate music commands using discord.js and play-dl", "description": "Complete framework to facilitate music commands using discord.js and play-dl",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",

File diff suppressed because it is too large Load diff

View file

@ -1,73 +1,73 @@
import { ExtractorModelData } from "../types/types"; import { ExtractorModelData } from "../types/types";
class ExtractorModel { class ExtractorModel {
name: string; name: string;
private _raw: any; // eslint-disable-line @typescript-eslint/no-explicit-any private _raw: any; // eslint-disable-line @typescript-eslint/no-explicit-any
/** /**
* Model for raw Discord Player extractors * Model for raw Discord Player extractors
* @param {string} extractorName Name of the extractor * @param {string} extractorName Name of the extractor
* @param {object} data Extractor object * @param {object} data Extractor object
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(extractorName: string, data: any) { constructor(extractorName: string, data: any) {
/** /**
* The extractor name * The extractor name
* @type {string} * @type {string}
*/ */
this.name = extractorName; this.name = extractorName;
/** /**
* The raw model * The raw model
* @name ExtractorModel#_raw * @name ExtractorModel#_raw
* @type {any} * @type {any}
* @private * @private
*/ */
Object.defineProperty(this, "_raw", { value: data, configurable: false, writable: false, enumerable: false }); Object.defineProperty(this, "_raw", { value: data, configurable: false, writable: false, enumerable: false });
} }
/** /**
* Method to handle requests from `Player.play()` * Method to handle requests from `Player.play()`
* @param {string} query Query to handle * @param {string} query Query to handle
* @returns {Promise<ExtractorModelData>} * @returns {Promise<ExtractorModelData>}
*/ */
async handle(query: string): Promise<ExtractorModelData> { async handle(query: string): Promise<ExtractorModelData> {
const data = await this._raw.getInfo(query); const data = await this._raw.getInfo(query);
if (!data) return null; if (!data) return null;
return { return {
playlist: data.playlist ?? null, playlist: data.playlist ?? null,
data: data:
(data.info as Omit<ExtractorModelData, "playlist">["data"])?.map((m) => ({ (data.info as Omit<ExtractorModelData, "playlist">["data"])?.map((m) => ({
title: m.title as string, title: m.title as string,
duration: m.duration as number, duration: m.duration as number,
thumbnail: m.thumbnail as string, thumbnail: m.thumbnail as string,
engine: m.engine, engine: m.engine,
views: m.views as number, views: m.views as number,
author: m.author as string, author: m.author as string,
description: m.description as string, description: m.description as string,
url: m.url as string, url: m.url as string,
source: m.source || "arbitrary" source: m.source || "arbitrary"
})) ?? [] })) ?? []
}; };
} }
/** /**
* Method used by Discord Player to validate query with this extractor * Method used by Discord Player to validate query with this extractor
* @param {string} query The query to validate * @param {string} query The query to validate
* @returns {boolean} * @returns {boolean}
*/ */
validate(query: string): boolean { validate(query: string): boolean {
return Boolean(this._raw.validate(query)); return Boolean(this._raw.validate(query));
} }
/** /**
* The extractor version * The extractor version
* @type {string} * @type {string}
*/ */
get version(): string { get version(): string {
return this._raw.version ?? "0.0.0"; return this._raw.version ?? "0.0.0";
} }
} }
export { ExtractorModel }; export { ExtractorModel };

View file

@ -1,53 +1,53 @@
export enum ErrorStatusCode { export enum ErrorStatusCode {
STREAM_ERROR = "StreamError", STREAM_ERROR = "StreamError",
AUDIO_PLAYER_ERROR = "AudioPlayerError", AUDIO_PLAYER_ERROR = "AudioPlayerError",
PLAYER_ERROR = "PlayerError", PLAYER_ERROR = "PlayerError",
NO_AUDIO_RESOURCE = "NoAudioResource", NO_AUDIO_RESOURCE = "NoAudioResource",
UNKNOWN_GUILD = "UnknownGuild", UNKNOWN_GUILD = "UnknownGuild",
INVALID_ARG_TYPE = "InvalidArgType", INVALID_ARG_TYPE = "InvalidArgType",
UNKNOWN_EXTRACTOR = "UnknownExtractor", UNKNOWN_EXTRACTOR = "UnknownExtractor",
INVALID_EXTRACTOR = "InvalidExtractor", INVALID_EXTRACTOR = "InvalidExtractor",
INVALID_CHANNEL_TYPE = "InvalidChannelType", INVALID_CHANNEL_TYPE = "InvalidChannelType",
INVALID_TRACK = "InvalidTrack", INVALID_TRACK = "InvalidTrack",
UNKNOWN_REPEAT_MODE = "UnknownRepeatMode", UNKNOWN_REPEAT_MODE = "UnknownRepeatMode",
TRACK_NOT_FOUND = "TrackNotFound", TRACK_NOT_FOUND = "TrackNotFound",
NO_CONNECTION = "NoConnection", NO_CONNECTION = "NoConnection",
DESTROYED_QUEUE = "DestroyedQueue" DESTROYED_QUEUE = "DestroyedQueue"
} }
export class PlayerError extends Error { export class PlayerError extends Error {
message: string; message: string;
statusCode: ErrorStatusCode; statusCode: ErrorStatusCode;
createdAt = new Date(); createdAt = new Date();
constructor(message: string, code: ErrorStatusCode = ErrorStatusCode.PLAYER_ERROR) { constructor(message: string, code: ErrorStatusCode = ErrorStatusCode.PLAYER_ERROR) {
super(); super();
this.message = `[${code}] ${message}`; this.message = `[${code}] ${message}`;
this.statusCode = code; this.statusCode = code;
this.name = code; this.name = code;
Error.captureStackTrace(this); Error.captureStackTrace(this);
} }
get createdTimestamp() { get createdTimestamp() {
return this.createdAt.getTime(); return this.createdAt.getTime();
} }
valueOf() { valueOf() {
return this.statusCode; return this.statusCode;
} }
toJSON() { toJSON() {
return { return {
stack: this.stack, stack: this.stack,
code: this.statusCode, code: this.statusCode,
message: this.message, message: this.message,
created: this.createdTimestamp created: this.createdTimestamp
}; };
} }
toString() { toString() {
return this.stack; return this.stack;
} }
} }

View file

@ -3,136 +3,136 @@ import { Track } from "./Track";
import { PlaylistInitData, PlaylistJSON, TrackJSON, TrackSource } from "../types/types"; import { PlaylistInitData, PlaylistJSON, TrackJSON, TrackSource } from "../types/types";
class Playlist { class Playlist {
public readonly player: Player; public readonly player: Player;
public tracks: Track[]; public tracks: Track[];
public title: string; public title: string;
public description: string; public description: string;
public thumbnail: string; public thumbnail: string;
public type: "album" | "playlist"; public type: "album" | "playlist";
public source: TrackSource; public source: TrackSource;
public author: { public author: {
name: string; name: string;
url: string; url: string;
}; };
public id: string; public id: string;
public url: string; public url: string;
public readonly rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any public readonly rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
/** /**
* Playlist constructor * Playlist constructor
* @param {Player} player The player * @param {Player} player The player
* @param {PlaylistInitData} data The data * @param {PlaylistInitData} data The data
*/ */
constructor(player: Player, data: PlaylistInitData) { constructor(player: Player, data: PlaylistInitData) {
/** /**
* The player * The player
* @name Playlist#player * @name Playlist#player
* @type {Player} * @type {Player}
* @readonly * @readonly
*/ */
this.player = player; this.player = player;
/** /**
* The tracks in this playlist * The tracks in this playlist
* @name Playlist#tracks * @name Playlist#tracks
* @type {Track[]} * @type {Track[]}
*/ */
this.tracks = data.tracks ?? []; this.tracks = data.tracks ?? [];
/** /**
* The author of this playlist * The author of this playlist
* @name Playlist#author * @name Playlist#author
* @type {object} * @type {object}
*/ */
this.author = data.author; this.author = data.author;
/** /**
* The description * The description
* @name Playlist#description * @name Playlist#description
* @type {string} * @type {string}
*/ */
this.description = data.description; this.description = data.description;
/** /**
* The thumbnail of this playlist * The thumbnail of this playlist
* @name Playlist#thumbnail * @name Playlist#thumbnail
* @type {string} * @type {string}
*/ */
this.thumbnail = data.thumbnail; this.thumbnail = data.thumbnail;
/** /**
* The playlist type: * The playlist type:
* - `album` * - `album`
* - `playlist` * - `playlist`
* @name Playlist#type * @name Playlist#type
* @type {string} * @type {string}
*/ */
this.type = data.type; this.type = data.type;
/** /**
* The source of this playlist: * The source of this playlist:
* - `youtube` * - `youtube`
* - `soundcloud` * - `soundcloud`
* - `spotify` * - `spotify`
* - `arbitrary` * - `arbitrary`
* @name Playlist#source * @name Playlist#source
* @type {string} * @type {string}
*/ */
this.source = data.source; this.source = data.source;
/** /**
* The playlist id * The playlist id
* @name Playlist#id * @name Playlist#id
* @type {string} * @type {string}
*/ */
this.id = data.id; this.id = data.id;
/** /**
* The playlist url * The playlist url
* @name Playlist#url * @name Playlist#url
* @type {string} * @type {string}
*/ */
this.url = data.url; this.url = data.url;
/** /**
* The playlist title * The playlist title
* @type {string} * @type {string}
*/ */
this.title = data.title; this.title = data.title;
/** /**
* @name Playlist#rawPlaylist * @name Playlist#rawPlaylist
* @type {any} * @type {any}
* @readonly * @readonly
*/ */
} }
*[Symbol.iterator]() { *[Symbol.iterator]() {
yield* this.tracks; yield* this.tracks;
} }
/** /**
* JSON representation of this playlist * JSON representation of this playlist
* @param {boolean} [withTracks=true] If it should build json with tracks * @param {boolean} [withTracks=true] If it should build json with tracks
* @returns {PlaylistJSON} * @returns {PlaylistJSON}
*/ */
toJSON(withTracks = true) { toJSON(withTracks = true) {
const payload = { const payload = {
id: this.id, id: this.id,
url: this.url, url: this.url,
title: this.title, title: this.title,
description: this.description, description: this.description,
thumbnail: this.thumbnail, thumbnail: this.thumbnail,
type: this.type, type: this.type,
source: this.source, source: this.source,
author: this.author, author: this.author,
tracks: [] as TrackJSON[] tracks: [] as TrackJSON[]
}; };
if (withTracks) payload.tracks = this.tracks.map((m) => m.toJSON(true)); if (withTracks) payload.tracks = this.tracks.map((m) => m.toJSON(true));
return payload as PlaylistJSON; return payload as PlaylistJSON;
} }
} }
export { Playlist }; export { Playlist };

File diff suppressed because it is too large Load diff

View file

@ -5,185 +5,185 @@ import { Playlist } from "./Playlist";
import { Queue } from "./Queue"; import { Queue } from "./Queue";
class Track { class Track {
public player!: Player; public player!: Player;
public title!: string; public title!: string;
public description!: string; public description!: string;
public author!: string; public author!: string;
public url!: string; public url!: string;
public thumbnail!: string; public thumbnail!: string;
public duration!: string; public duration!: string;
public views!: number; public views!: number;
public requestedBy!: User; public requestedBy!: User;
public playlist?: Playlist; public playlist?: Playlist;
public readonly raw: RawTrackData = {} as RawTrackData; public readonly raw: RawTrackData = {} as RawTrackData;
public readonly id = SnowflakeUtil.generate().toString(); public readonly id = SnowflakeUtil.generate().toString();
/** /**
* Track constructor * Track constructor
* @param {Player} player The player that instantiated this Track * @param {Player} player The player that instantiated this Track
* @param {RawTrackData} data Track data * @param {RawTrackData} data Track data
*/ */
constructor(player: Player, data: RawTrackData) { constructor(player: Player, data: RawTrackData) {
/** /**
* The player that instantiated this Track * The player that instantiated this Track
* @name Track#player * @name Track#player
* @type {Player} * @type {Player}
* @readonly * @readonly
*/ */
Object.defineProperty(this, "player", { value: player, enumerable: false }); Object.defineProperty(this, "player", { value: player, enumerable: false });
/** /**
* Title of this track * Title of this track
* @name Track#title * @name Track#title
* @type {string} * @type {string}
*/ */
/** /**
* Description of this track * Description of this track
* @name Track#description * @name Track#description
* @type {string} * @type {string}
*/ */
/** /**
* Author of this track * Author of this track
* @name Track#author * @name Track#author
* @type {string} * @type {string}
*/ */
/** /**
* URL of this track * URL of this track
* @name Track#url * @name Track#url
* @type {string} * @type {string}
*/ */
/** /**
* Thumbnail of this track * Thumbnail of this track
* @name Track#thumbnail * @name Track#thumbnail
* @type {string} * @type {string}
*/ */
/** /**
* Duration of this track * Duration of this track
* @name Track#duration * @name Track#duration
* @type {string} * @type {string}
*/ */
/** /**
* Views count of this track * Views count of this track
* @name Track#views * @name Track#views
* @type {number} * @type {number}
*/ */
/** /**
* Person who requested this track * Person who requested this track
* @name Track#requestedBy * @name Track#requestedBy
* @type {User} * @type {User}
*/ */
/** /**
* If this track belongs to playlist * If this track belongs to playlist
* @name Track#fromPlaylist * @name Track#fromPlaylist
* @type {boolean} * @type {boolean}
*/ */
/** /**
* Raw track data * Raw track data
* @name Track#raw * @name Track#raw
* @type {RawTrackData} * @type {RawTrackData}
*/ */
/** /**
* The track id * The track id
* @name Track#id * @name Track#id
* @type {Snowflake} * @type {Snowflake}
* @readonly * @readonly
*/ */
/** /**
* The playlist which track belongs * The playlist which track belongs
* @name Track#playlist * @name Track#playlist
* @type {Playlist} * @type {Playlist}
*/ */
void this._patch(data); void this._patch(data);
} }
private _patch(data: RawTrackData) { private _patch(data: RawTrackData) {
this.title = escapeMarkdown(data.title ?? ""); this.title = escapeMarkdown(data.title ?? "");
this.description = data.description ?? ""; this.description = data.description ?? "";
this.author = data.author ?? ""; this.author = data.author ?? "";
this.url = data.url ?? ""; this.url = data.url ?? "";
this.thumbnail = data.thumbnail ?? ""; this.thumbnail = data.thumbnail ?? "";
this.duration = data.duration ?? ""; this.duration = data.duration ?? "";
this.views = data.views ?? 0; this.views = data.views ?? 0;
this.requestedBy = data.requestedBy; this.requestedBy = data.requestedBy;
this.playlist = data.playlist; this.playlist = data.playlist;
// raw // raw
Object.defineProperty(this, "raw", { value: Object.assign({}, { source: data.raw?.source ?? data.source }, data.raw ?? data), enumerable: false }); Object.defineProperty(this, "raw", { value: Object.assign({}, { source: data.raw?.source ?? data.source }, data.raw ?? data), enumerable: false });
} }
/** /**
* The queue in which this track is located * The queue in which this track is located
* @type {Queue} * @type {Queue}
*/ */
get queue(): Queue { get queue(): Queue {
return this.player.queues.find((q) => q.tracks.some((ab) => ab.id === this.id)); return this.player.queues.find((q) => q.tracks.some((ab) => ab.id === this.id));
} }
/** /**
* The track duration in millisecond * The track duration in millisecond
* @type {number} * @type {number}
*/ */
get durationMS(): number { get durationMS(): number {
const times = (n: number, t: number) => { const times = (n: number, t: number) => {
let tn = 1; let tn = 1;
for (let i = 0; i < t; i++) tn *= n; for (let i = 0; i < t; i++) tn *= n;
return t <= 0 ? 1000 : tn * 1000; return t <= 0 ? 1000 : tn * 1000;
}; };
return this.duration return this.duration
.split(":") .split(":")
.reverse() .reverse()
.map((m, i) => parseInt(m) * times(60, i)) .map((m, i) => parseInt(m) * times(60, i))
.reduce((a, c) => a + c, 0); .reduce((a, c) => a + c, 0);
} }
/** /**
* Returns source of this track * Returns source of this track
* @type {TrackSource} * @type {TrackSource}
*/ */
get source() { get source() {
return this.raw.source ?? "arbitrary"; return this.raw.source ?? "arbitrary";
} }
/** /**
* String representation of this track * String representation of this track
* @returns {string} * @returns {string}
*/ */
toString(): string { toString(): string {
return `${this.title} by ${this.author}`; return `${this.title} by ${this.author}`;
} }
/** /**
* Raw JSON representation of this track * Raw JSON representation of this track
* @returns {TrackJSON} * @returns {TrackJSON}
*/ */
toJSON(hidePlaylist?: boolean) { toJSON(hidePlaylist?: boolean) {
return { return {
id: this.id, id: this.id,
title: this.title, title: this.title,
description: this.description, description: this.description,
author: this.author, author: this.author,
url: this.url, url: this.url,
thumbnail: this.thumbnail, thumbnail: this.thumbnail,
duration: this.duration, duration: this.duration,
durationMS: this.durationMS, durationMS: this.durationMS,
views: this.views, views: this.views,
requestedBy: this.requestedBy?.id, requestedBy: this.requestedBy?.id,
playlist: hidePlaylist ? null : this.playlist?.toJSON() ?? null playlist: hidePlaylist ? null : this.playlist?.toJSON() ?? null
} as TrackJSON; } as TrackJSON;
} }
} }
export default Track; export default Track;

View file

@ -1,15 +1,16 @@
import { import {
AudioPlayer, AudioPlayer,
AudioPlayerError, AudioPlayerError,
AudioPlayerStatus, AudioPlayerStatus,
AudioResource, AudioResource,
createAudioPlayer, createAudioPlayer,
createAudioResource, createAudioResource,
entersState, entersState,
StreamType, StreamType,
VoiceConnection, VoiceConnection,
VoiceConnectionStatus, VoiceConnectionStatus,
VoiceConnectionDisconnectReason VoiceConnectionDisconnectReason,
VoiceConnectionState
} from "@discordjs/voice"; } from "@discordjs/voice";
import { StageChannel, VoiceChannel } from "discord.js"; import { StageChannel, VoiceChannel } from "discord.js";
import { Duplex, Readable } from "stream"; import { Duplex, Readable } from "stream";
@ -19,235 +20,248 @@ import { Util } from "../utils/Util";
import { PlayerError, ErrorStatusCode } from "../Structures/PlayerError"; import { PlayerError, ErrorStatusCode } from "../Structures/PlayerError";
export interface VoiceEvents { export interface VoiceEvents {
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
error: (error: AudioPlayerError) => any; error: (error: AudioPlayerError) => any;
debug: (message: string) => any; debug: (message: string) => any;
start: (resource: AudioResource<Track>) => any; start: (resource: AudioResource<Track>) => any;
finish: (resource: AudioResource<Track>) => any; finish: (resource: AudioResource<Track>) => any;
/* eslint-enable @typescript-eslint/no-explicit-any */ /* eslint-enable @typescript-eslint/no-explicit-any */
} }
class StreamDispatcher extends EventEmitter<VoiceEvents> { class StreamDispatcher extends EventEmitter<VoiceEvents> {
public readonly voiceConnection: VoiceConnection; public readonly voiceConnection: VoiceConnection;
public readonly audioPlayer: AudioPlayer; public readonly audioPlayer: AudioPlayer;
public channel: VoiceChannel | StageChannel; public channel: VoiceChannel | StageChannel;
public audioResource?: AudioResource<Track>; public audioResource?: AudioResource<Track>;
private readyLock = false; private readyLock = false;
public paused: boolean; public paused: boolean;
/** /**
* Creates new connection object * Creates new connection object
* @param {VoiceConnection} connection The connection * @param {VoiceConnection} connection The connection
* @param {VoiceChannel|StageChannel} channel The connected channel * @param {VoiceChannel|StageChannel} channel The connected channel
* @private * @private
*/ */
constructor(connection: VoiceConnection, channel: VoiceChannel | StageChannel, public readonly connectionTimeout: number = 20000) { constructor(connection: VoiceConnection, channel: VoiceChannel | StageChannel, public readonly connectionTimeout: number = 20000) {
super(); super();
/** /**
* The voice connection * The voice connection
* @type {VoiceConnection} * @type {VoiceConnection}
*/ */
this.voiceConnection = connection; this.voiceConnection = connection;
/** /**
* The audio player * The audio player
* @type {AudioPlayer} * @type {AudioPlayer}
*/ */
this.audioPlayer = createAudioPlayer(); this.audioPlayer = createAudioPlayer();
/** /**
* The voice channel * The voice channel
* @type {VoiceChannel|StageChannel} * @type {VoiceChannel|StageChannel}
*/ */
this.channel = channel; this.channel = channel;
/** /**
* The paused state * The paused state
* @type {boolean} * @type {boolean}
*/ */
this.paused = false; this.paused = false;
this.voiceConnection.on("stateChange", async (_, newState) => { this.voiceConnection.on("stateChange", async (oldState, newState) => {
if (newState.status === VoiceConnectionStatus.Disconnected) { // oh no, fix no work
if (newState.reason === VoiceConnectionDisconnectReason.WebSocketClose && newState.closeCode === 4014) { const oldNetworking = Reflect.get(oldState, "networking");
try { const newNetworking = Reflect.get(newState, "networking");
await entersState(this.voiceConnection, VoiceConnectionStatus.Connecting, this.connectionTimeout);
} catch {
try {
this.voiceConnection.destroy();
} catch (err) {
this.emit("error", err as AudioPlayerError);
}
}
} else if (this.voiceConnection.rejoinAttempts < 5) {
await Util.wait((this.voiceConnection.rejoinAttempts + 1) * 5000);
this.voiceConnection.rejoin();
} else {
try {
this.voiceConnection.destroy();
} catch (err) {
this.emit("error", err as AudioPlayerError);
}
}
} else if (newState.status === VoiceConnectionStatus.Destroyed) {
this.end();
} else if (!this.readyLock && (newState.status === VoiceConnectionStatus.Connecting || newState.status === VoiceConnectionStatus.Signalling)) {
this.readyLock = true;
try {
await entersState(this.voiceConnection, VoiceConnectionStatus.Ready, this.connectionTimeout);
} catch {
if (this.voiceConnection.state.status !== VoiceConnectionStatus.Destroyed) {
try {
this.voiceConnection.destroy();
} catch (err) {
this.emit("error", err as AudioPlayerError);
}
}
} finally {
this.readyLock = false;
}
}
});
this.audioPlayer.on("stateChange", (oldState, newState) => { const networkStateChangeHandler = (_: VoiceConnectionState, newNetworkState: VoiceConnectionState) => {
if (newState.status === AudioPlayerStatus.Playing) { const newUdp = Reflect.get(newNetworkState, "udp");
if (!this.paused) return void this.emit("start", this.audioResource); clearInterval(newUdp?.keepAliveInterval);
} else if (newState.status === AudioPlayerStatus.Idle && oldState.status !== AudioPlayerStatus.Idle) { };
if (!this.paused) {
void this.emit("finish", this.audioResource);
this.audioResource = null;
}
}
});
this.audioPlayer.on("debug", (m) => void this.emit("debug", m)); oldNetworking?.off("stateChange", networkStateChangeHandler);
this.audioPlayer.on("error", (error) => void this.emit("error", error)); newNetworking?.on("stateChange", networkStateChangeHandler);
this.voiceConnection.subscribe(this.audioPlayer); // temp fix end
}
/** if (newState.status === VoiceConnectionStatus.Disconnected) {
* Creates stream if (newState.reason === VoiceConnectionDisconnectReason.WebSocketClose && newState.closeCode === 4014) {
* @param {Readable|Duplex|string} src The stream source try {
* @param {object} [ops] Options await entersState(this.voiceConnection, VoiceConnectionStatus.Connecting, this.connectionTimeout);
* @returns {AudioResource} } catch {
*/ try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any this.voiceConnection.destroy();
createStream(src: Readable | Duplex | string, ops?: { type?: StreamType; data?: any; disableVolume?: boolean }) { } catch (err) {
this.audioResource = createAudioResource(src, { this.emit("error", err as AudioPlayerError);
inputType: ops?.type ?? StreamType.Arbitrary, }
metadata: ops?.data, }
// eslint-disable-next-line no-extra-boolean-cast } else if (this.voiceConnection.rejoinAttempts < 5) {
inlineVolume: !Boolean(ops?.disableVolume) await Util.wait((this.voiceConnection.rejoinAttempts + 1) * 5000);
}); this.voiceConnection.rejoin();
} else {
try {
this.voiceConnection.destroy();
} catch (err) {
this.emit("error", err as AudioPlayerError);
}
}
} else if (newState.status === VoiceConnectionStatus.Destroyed) {
this.end();
} else if (!this.readyLock && (newState.status === VoiceConnectionStatus.Connecting || newState.status === VoiceConnectionStatus.Signalling)) {
this.readyLock = true;
try {
await entersState(this.voiceConnection, VoiceConnectionStatus.Ready, this.connectionTimeout);
} catch {
if (this.voiceConnection.state.status !== VoiceConnectionStatus.Destroyed) {
try {
this.voiceConnection.destroy();
} catch (err) {
this.emit("error", err as AudioPlayerError);
}
}
} finally {
this.readyLock = false;
}
}
});
return this.audioResource; this.audioPlayer.on("stateChange", (oldState, newState) => {
} if (newState.status === AudioPlayerStatus.Playing) {
if (!this.paused) return void this.emit("start", this.audioResource);
} else if (newState.status === AudioPlayerStatus.Idle && oldState.status !== AudioPlayerStatus.Idle) {
if (!this.paused) {
void this.emit("finish", this.audioResource);
this.audioResource = null;
}
}
});
/** this.audioPlayer.on("debug", (m) => void this.emit("debug", m));
* The player status this.audioPlayer.on("error", (error) => void this.emit("error", error));
* @type {AudioPlayerStatus} this.voiceConnection.subscribe(this.audioPlayer);
*/ }
get status() {
return this.audioPlayer.state.status;
}
/** /**
* Disconnects from voice * Creates stream
* @returns {void} * @param {Readable|Duplex|string} src The stream source
*/ * @param {object} [ops] Options
disconnect() { * @returns {AudioResource}
try { */
this.audioPlayer.stop(true); // eslint-disable-next-line @typescript-eslint/no-explicit-any
this.voiceConnection.destroy(); createStream(src: Readable | Duplex | string, ops?: { type?: StreamType; data?: any; disableVolume?: boolean }) {
} catch {} // eslint-disable-line no-empty this.audioResource = createAudioResource(src, {
} inputType: ops?.type ?? StreamType.Arbitrary,
metadata: ops?.data,
// eslint-disable-next-line no-extra-boolean-cast
inlineVolume: !Boolean(ops?.disableVolume)
});
/** return this.audioResource;
* Stops the player }
* @returns {void}
*/
end() {
this.audioPlayer.stop();
}
/** /**
* Pauses the stream playback * The player status
* @param {boolean} [interpolateSilence=false] If true, the player will play 5 packets of silence after pausing to prevent audio glitches. * @type {AudioPlayerStatus}
* @returns {boolean} */
*/ get status() {
pause(interpolateSilence?: boolean) { return this.audioPlayer.state.status;
const success = this.audioPlayer.pause(interpolateSilence); }
this.paused = success;
return success;
}
/** /**
* Resumes the stream playback * Disconnects from voice
* @returns {boolean} * @returns {void}
*/ */
resume() { disconnect() {
const success = this.audioPlayer.unpause(); try {
this.paused = !success; this.audioPlayer.stop(true);
return success; this.voiceConnection.destroy();
} } catch {} // eslint-disable-line no-empty
}
/** /**
* Play stream * Stops the player
* @param {AudioResource<Track>} [resource=this.audioResource] The audio resource to play * @returns {void}
* @returns {Promise<StreamDispatcher>} */
*/ end() {
async playStream(resource: AudioResource<Track> = this.audioResource) { this.audioPlayer.stop();
if (!resource) throw new PlayerError("Audio resource is not available!", ErrorStatusCode.NO_AUDIO_RESOURCE); }
if (resource.ended) return void this.emit("error", new PlayerError("Cannot play a resource that has already ended.") as unknown as AudioPlayerError);
if (!this.audioResource) this.audioResource = resource;
if (this.voiceConnection.state.status !== VoiceConnectionStatus.Ready) {
try {
await entersState(this.voiceConnection, VoiceConnectionStatus.Ready, this.connectionTimeout);
} catch (err) {
return void this.emit("error", err as AudioPlayerError);
}
}
try { /**
this.audioPlayer.play(resource); * Pauses the stream playback
} catch (e) { * @param {boolean} [interpolateSilence=false] If true, the player will play 5 packets of silence after pausing to prevent audio glitches.
this.emit("error", e as AudioPlayerError); * @returns {boolean}
} */
pause(interpolateSilence?: boolean) {
const success = this.audioPlayer.pause(interpolateSilence);
this.paused = success;
return success;
}
return this; /**
} * Resumes the stream playback
* @returns {boolean}
*/
resume() {
const success = this.audioPlayer.unpause();
this.paused = !success;
return success;
}
/** /**
* Sets playback volume * Play stream
* @param {number} value The volume amount * @param {AudioResource<Track>} [resource=this.audioResource] The audio resource to play
* @returns {boolean} * @returns {Promise<StreamDispatcher>}
*/ */
setVolume(value: number) { async playStream(resource: AudioResource<Track> = this.audioResource) {
if (!this.audioResource?.volume || isNaN(value) || value < 0 || value > Infinity) return false; if (!resource) throw new PlayerError("Audio resource is not available!", ErrorStatusCode.NO_AUDIO_RESOURCE);
if (resource.ended) return void this.emit("error", new PlayerError("Cannot play a resource that has already ended.") as unknown as AudioPlayerError);
if (!this.audioResource) this.audioResource = resource;
if (this.voiceConnection.state.status !== VoiceConnectionStatus.Ready) {
try {
await entersState(this.voiceConnection, VoiceConnectionStatus.Ready, this.connectionTimeout);
} catch (err) {
return void this.emit("error", err as AudioPlayerError);
}
}
this.audioResource.volume.setVolumeLogarithmic(value / 100); try {
return true; this.audioPlayer.play(resource);
} } catch (e) {
this.emit("error", e as AudioPlayerError);
}
/** return this;
* The current volume }
* @type {number}
*/
get volume() {
if (!this.audioResource?.volume) return 100;
const currentVol = this.audioResource.volume.volume;
return Math.round(Math.pow(currentVol, 1 / 1.660964) * 100);
}
/** /**
* The playback time * Sets playback volume
* @type {number} * @param {number} value The volume amount
*/ * @returns {boolean}
get streamTime() { */
if (!this.audioResource) return 0; setVolume(value: number) {
return this.audioResource.playbackDuration; if (!this.audioResource?.volume || isNaN(value) || value < 0 || value > Infinity) return false;
}
this.audioResource.volume.setVolumeLogarithmic(value / 100);
return true;
}
/**
* The current volume
* @type {number}
*/
get volume() {
if (!this.audioResource?.volume) return 100;
const currentVol = this.audioResource.volume.volume;
return Math.round(Math.pow(currentVol, 1 / 1.660964) * 100);
}
/**
* The playback time
* @type {number}
*/
get streamTime() {
if (!this.audioResource) return 0;
return this.audioResource.playbackDuration;
}
} }
export { StreamDispatcher as StreamDispatcher }; export { StreamDispatcher as StreamDispatcher };

View file

@ -3,80 +3,80 @@ import { DiscordGatewayAdapterCreator, joinVoiceChannel, VoiceConnection } from
import { StreamDispatcher } from "./StreamDispatcher"; import { StreamDispatcher } from "./StreamDispatcher";
class VoiceUtils { class VoiceUtils {
public cache: Collection<Snowflake, StreamDispatcher>; public cache: Collection<Snowflake, StreamDispatcher>;
/** /**
* The voice utils * The voice utils
* @private * @private
*/ */
constructor() { constructor() {
/** /**
* The cache where voice utils stores stream managers * The cache where voice utils stores stream managers
* @type {Collection<Snowflake, StreamDispatcher>} * @type {Collection<Snowflake, StreamDispatcher>}
*/ */
this.cache = new Collection<Snowflake, StreamDispatcher>(); this.cache = new Collection<Snowflake, StreamDispatcher>();
} }
/** /**
* Joins a voice channel, creating basic stream dispatch manager * Joins a voice channel, creating basic stream dispatch manager
* @param {StageChannel|VoiceChannel} channel The voice channel * @param {StageChannel|VoiceChannel} channel The voice channel
* @param {object} [options] Join options * @param {object} [options] Join options
* @returns {Promise<StreamDispatcher>} * @returns {Promise<StreamDispatcher>}
*/ */
public async connect( public async connect(
channel: VoiceChannel | StageChannel, channel: VoiceChannel | StageChannel,
options?: { options?: {
deaf?: boolean; deaf?: boolean;
maxTime?: number; maxTime?: number;
} }
): Promise<StreamDispatcher> { ): Promise<StreamDispatcher> {
const conn = await this.join(channel, options); const conn = await this.join(channel, options);
const sub = new StreamDispatcher(conn, channel, options.maxTime); const sub = new StreamDispatcher(conn, channel, options.maxTime);
this.cache.set(channel.guild.id, sub); this.cache.set(channel.guild.id, sub);
return sub; return sub;
} }
/** /**
* Joins a voice channel * Joins a voice channel
* @param {StageChannel|VoiceChannel} [channel] The voice/stage channel to join * @param {StageChannel|VoiceChannel} [channel] The voice/stage channel to join
* @param {object} [options] Join options * @param {object} [options] Join options
* @returns {VoiceConnection} * @returns {VoiceConnection}
*/ */
public async join( public async join(
channel: VoiceChannel | StageChannel, channel: VoiceChannel | StageChannel,
options?: { options?: {
deaf?: boolean; deaf?: boolean;
maxTime?: number; maxTime?: number;
} }
) { ) {
const conn = joinVoiceChannel({ const conn = joinVoiceChannel({
guildId: channel.guild.id, guildId: channel.guild.id,
channelId: channel.id, channelId: channel.id,
adapterCreator: channel.guild.voiceAdapterCreator as unknown as DiscordGatewayAdapterCreator, adapterCreator: channel.guild.voiceAdapterCreator as unknown as DiscordGatewayAdapterCreator,
selfDeaf: Boolean(options.deaf) selfDeaf: Boolean(options.deaf)
}); });
return conn; return conn;
} }
/** /**
* Disconnects voice connection * Disconnects voice connection
* @param {VoiceConnection} connection The voice connection * @param {VoiceConnection} connection The voice connection
* @returns {void} * @returns {void}
*/ */
public disconnect(connection: VoiceConnection | StreamDispatcher) { public disconnect(connection: VoiceConnection | StreamDispatcher) {
if (connection instanceof StreamDispatcher) return connection.voiceConnection.destroy(); if (connection instanceof StreamDispatcher) return connection.voiceConnection.destroy();
return connection.destroy(); return connection.destroy();
} }
/** /**
* Returns Discord Player voice connection * Returns Discord Player voice connection
* @param {Snowflake} guild The guild id * @param {Snowflake} guild The guild id
* @returns {StreamDispatcher} * @returns {StreamDispatcher}
*/ */
public getConnection(guild: Snowflake) { public getConnection(guild: Snowflake) {
return this.cache.get(guild); return this.cache.get(guild);
} }
} }
export { VoiceUtils }; export { VoiceUtils };

View file

@ -3,142 +3,142 @@
import { Transform, TransformOptions } from "stream"; import { Transform, TransformOptions } from "stream";
export interface VolumeTransformerOptions extends TransformOptions { export interface VolumeTransformerOptions extends TransformOptions {
type?: "s16le" | "s16be" | "s32le" | "s32be"; type?: "s16le" | "s16be" | "s32le" | "s32be";
smoothness?: number; smoothness?: number;
volume?: number; volume?: number;
} }
export class VolumeTransformer extends Transform { export class VolumeTransformer extends Transform {
private _bits: number; private _bits: number;
private _smoothing: number; private _smoothing: number;
private _bytes: number; private _bytes: number;
private _extremum: number; private _extremum: number;
private _chunk: Buffer; private _chunk: Buffer;
public volume: number; public volume: number;
private _targetVolume: number; private _targetVolume: number;
public type: "s16le" | "s32le" | "s16be" | "s32be"; public type: "s16le" | "s32le" | "s16be" | "s32be";
constructor(options: VolumeTransformerOptions = {}) { constructor(options: VolumeTransformerOptions = {}) {
super(options); super(options);
switch (options.type) { switch (options.type) {
case "s16le": case "s16le":
this._readInt = (buffer, index) => buffer.readInt16LE(index); this._readInt = (buffer, index) => buffer.readInt16LE(index);
this._writeInt = (buffer, int, index) => buffer.writeInt16LE(int, index); this._writeInt = (buffer, int, index) => buffer.writeInt16LE(int, index);
this._bits = 16; this._bits = 16;
break; break;
case "s16be": case "s16be":
this._readInt = (buffer, index) => buffer.readInt16BE(index); this._readInt = (buffer, index) => buffer.readInt16BE(index);
this._writeInt = (buffer, int, index) => buffer.writeInt16BE(int, index); this._writeInt = (buffer, int, index) => buffer.writeInt16BE(int, index);
this._bits = 16; this._bits = 16;
break; break;
case "s32le": case "s32le":
this._readInt = (buffer, index) => buffer.readInt32LE(index); this._readInt = (buffer, index) => buffer.readInt32LE(index);
this._writeInt = (buffer, int, index) => buffer.writeInt32LE(int, index); this._writeInt = (buffer, int, index) => buffer.writeInt32LE(int, index);
this._bits = 32; this._bits = 32;
break; break;
case "s32be": case "s32be":
this._readInt = (buffer, index) => buffer.readInt32BE(index); this._readInt = (buffer, index) => buffer.readInt32BE(index);
this._writeInt = (buffer, int, index) => buffer.writeInt32BE(int, index); this._writeInt = (buffer, int, index) => buffer.writeInt32BE(int, index);
this._bits = 32; this._bits = 32;
break; break;
default: default:
throw new Error("VolumeTransformer type should be one of s16le, s16be, s32le, s32be"); throw new Error("VolumeTransformer type should be one of s16le, s16be, s32le, s32be");
} }
this.type = options.type; this.type = options.type;
this._bytes = this._bits / 8; this._bytes = this._bits / 8;
this._extremum = Math.pow(2, this._bits - 1); this._extremum = Math.pow(2, this._bits - 1);
this.volume = Number.isNaN(options.volume) ? 1 : Number(options.volume); this.volume = Number.isNaN(options.volume) ? 1 : Number(options.volume);
if (!Number.isFinite(this.volume)) this.volume = 1; if (!Number.isFinite(this.volume)) this.volume = 1;
this._targetVolume = this.volume; this._targetVolume = this.volume;
this._chunk = Buffer.alloc(0); this._chunk = Buffer.alloc(0);
this._smoothing = options.smoothness || 0; this._smoothing = options.smoothness || 0;
} }
_readInt(buffer: Buffer, index: number) { _readInt(buffer: Buffer, index: number) {
return index; return index;
} }
_writeInt(buffer: Buffer, int: number, index: number) { _writeInt(buffer: Buffer, int: number, index: number) {
return index; return index;
} }
_applySmoothness() { _applySmoothness() {
if (this.volume < this._targetVolume) { if (this.volume < this._targetVolume) {
this.volume = this.volume + this._smoothing >= this._targetVolume ? this._targetVolume : this.volume + this._smoothing; this.volume = this.volume + this._smoothing >= this._targetVolume ? this._targetVolume : this.volume + this._smoothing;
} else if (this.volume > this._targetVolume) { } else if (this.volume > this._targetVolume) {
this.volume = this.volume - this._smoothing <= this._targetVolume ? this._targetVolume : this.volume - this._smoothing; this.volume = this.volume - this._smoothing <= this._targetVolume ? this._targetVolume : this.volume - this._smoothing;
} }
} }
_transform(chunk: Buffer, encoding: BufferEncoding, done: () => unknown) { _transform(chunk: Buffer, encoding: BufferEncoding, done: () => unknown) {
if (this.smoothingEnabled() && this.volume !== this._targetVolume) this._applySmoothness(); if (this.smoothingEnabled() && this.volume !== this._targetVolume) this._applySmoothness();
if (this.volume === 1) { if (this.volume === 1) {
this.push(chunk); this.push(chunk);
return done(); return done();
} }
const { _bytes, _extremum } = this; const { _bytes, _extremum } = this;
chunk = this._chunk = Buffer.concat([this._chunk, chunk]); chunk = this._chunk = Buffer.concat([this._chunk, chunk]);
if (chunk.length < _bytes) return done(); if (chunk.length < _bytes) return done();
const complete = Math.floor(chunk.length / _bytes) * _bytes; const complete = Math.floor(chunk.length / _bytes) * _bytes;
for (let i = 0; i < complete; i += _bytes) { for (let i = 0; i < complete; i += _bytes) {
const int = Math.min(_extremum - 1, Math.max(-_extremum, Math.floor(this.volume * this._readInt(chunk, i)))); const int = Math.min(_extremum - 1, Math.max(-_extremum, Math.floor(this.volume * this._readInt(chunk, i))));
this._writeInt(chunk, int, i); this._writeInt(chunk, int, i);
} }
this._chunk = chunk.slice(complete); this._chunk = chunk.slice(complete);
this.push(chunk.slice(0, complete)); this.push(chunk.slice(0, complete));
return done(); return done();
} }
_destroy(err: Error, cb: (error: Error) => void) { _destroy(err: Error, cb: (error: Error) => void) {
super._destroy(err, cb); super._destroy(err, cb);
this._chunk = null; this._chunk = null;
} }
setVolume(volume: number) { setVolume(volume: number) {
if (Number.isNaN(volume)) volume = 1; if (Number.isNaN(volume)) volume = 1;
if (typeof volume !== "number") volume = Number(volume); if (typeof volume !== "number") volume = Number(volume);
if (!Number.isFinite(volume)) volume = volume < 0 ? 0 : 1; if (!Number.isFinite(volume)) volume = volume < 0 ? 0 : 1;
this._targetVolume = volume; this._targetVolume = volume;
if (this._smoothing <= 0) this.volume = volume; if (this._smoothing <= 0) this.volume = volume;
} }
setVolumeDecibels(db: number) { setVolumeDecibels(db: number) {
this.setVolume(Math.pow(10, db / 20)); this.setVolume(Math.pow(10, db / 20));
} }
setVolumeLogarithmic(value: number) { setVolumeLogarithmic(value: number) {
this.setVolume(Math.pow(value, 1.660964)); this.setVolume(Math.pow(value, 1.660964));
} }
get volumeDecibels() { get volumeDecibels() {
return Math.log10(this.volume) * 20; return Math.log10(this.volume) * 20;
} }
get volumeLogarithmic() { get volumeLogarithmic() {
return Math.pow(this.volume, 1 / 1.660964); return Math.pow(this.volume, 1 / 1.660964);
} }
get smoothness() { get smoothness() {
return this._smoothing; return this._smoothing;
} }
setSmoothness(smoothness: number) { setSmoothness(smoothness: number) {
this._smoothing = smoothness; this._smoothing = smoothness;
} }
smoothingEnabled() { smoothingEnabled() {
return Number.isFinite(this._smoothing) && this._smoothing > 0; return Number.isFinite(this._smoothing) && this._smoothing > 0;
} }
get hasSmoothness() { get hasSmoothness() {
return true; return true;
} }
static get hasSmoothing() { static get hasSmoothing() {
return true; return true;
} }
} }

View file

@ -1,12 +1,12 @@
import { VolumeTransformer as VolumeTransformerMock } from "./VoiceInterface/VolumeTransformer"; import { VolumeTransformer as VolumeTransformerMock } from "./VoiceInterface/VolumeTransformer";
try { try {
// eslint-disable-next-line // eslint-disable-next-line
const mod = require("prism-media") as typeof import("prism-media") & { VolumeTransformer: typeof VolumeTransformerMock }; const mod = require("prism-media") as typeof import("prism-media") & { VolumeTransformer: typeof VolumeTransformerMock };
if (typeof mod.VolumeTransformer.hasSmoothing !== "boolean") { if (typeof mod.VolumeTransformer.hasSmoothing !== "boolean") {
Reflect.set(mod, "VolumeTransformer", VolumeTransformerMock); Reflect.set(mod, "VolumeTransformer", VolumeTransformerMock);
} }
} catch { } catch {
/* do nothing */ /* do nothing */
} }

View file

@ -8,48 +8,48 @@ import { StreamDispatcher } from "../VoiceInterface/StreamDispatcher";
export type FiltersName = keyof QueueFilters; export type FiltersName = keyof QueueFilters;
export interface PlayerSearchResult { export interface PlayerSearchResult {
playlist: Playlist | null; playlist: Playlist | null;
tracks: Track[]; tracks: Track[];
searched?: boolean; searched?: boolean;
} }
/** /**
* @typedef {AudioFilters} QueueFilters * @typedef {AudioFilters} QueueFilters
*/ */
export interface QueueFilters { export interface QueueFilters {
bassboost_low?: boolean; bassboost_low?: boolean;
bassboost?: boolean; bassboost?: boolean;
bassboost_high?: boolean; bassboost_high?: boolean;
"8D"?: boolean; "8D"?: boolean;
vaporwave?: boolean; vaporwave?: boolean;
nightcore?: boolean; nightcore?: boolean;
phaser?: boolean; phaser?: boolean;
tremolo?: boolean; tremolo?: boolean;
vibrato?: boolean; vibrato?: boolean;
reverse?: boolean; reverse?: boolean;
treble?: boolean; treble?: boolean;
normalizer?: boolean; normalizer?: boolean;
normalizer2?: boolean; normalizer2?: boolean;
surrounding?: boolean; surrounding?: boolean;
pulsator?: boolean; pulsator?: boolean;
subboost?: boolean; subboost?: boolean;
karaoke?: boolean; karaoke?: boolean;
flanger?: boolean; flanger?: boolean;
gate?: boolean; gate?: boolean;
haas?: boolean; haas?: boolean;
mcompand?: boolean; mcompand?: boolean;
mono?: boolean; mono?: boolean;
mstlr?: boolean; mstlr?: boolean;
mstrr?: boolean; mstrr?: boolean;
compressor?: boolean; compressor?: boolean;
expander?: boolean; expander?: boolean;
softlimiter?: boolean; softlimiter?: boolean;
chorus?: boolean; chorus?: boolean;
chorus2d?: boolean; chorus2d?: boolean;
chorus3d?: boolean; chorus3d?: boolean;
fadein?: boolean; fadein?: boolean;
dim?: boolean; dim?: boolean;
earrape?: boolean; earrape?: boolean;
} }
/** /**
@ -79,19 +79,19 @@ export type TrackSource = "soundcloud" | "youtube" | "spotify" | "arbitrary";
* @property {any} [raw] The raw data * @property {any} [raw] The raw data
*/ */
export interface RawTrackData { export interface RawTrackData {
title: string; title: string;
description: string; description: string;
author: string; author: string;
url: string; url: string;
thumbnail: string; thumbnail: string;
duration: string; duration: string;
views: number; views: number;
requestedBy: User; requestedBy: User;
playlist?: Playlist; playlist?: Playlist;
source?: TrackSource; source?: TrackSource;
engine?: any; // eslint-disable-line @typescript-eslint/no-explicit-any engine?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
live?: boolean; live?: boolean;
raw?: any; // eslint-disable-line @typescript-eslint/no-explicit-any raw?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
} }
/** /**
@ -102,10 +102,10 @@ export interface RawTrackData {
* @property {number} seconds Time in seconds * @property {number} seconds Time in seconds
*/ */
export interface TimeData { export interface TimeData {
days: number; days: number;
hours: number; hours: number;
minutes: number; minutes: number;
seconds: number; seconds: number;
} }
/** /**
@ -117,11 +117,11 @@ export interface TimeData {
* @property {string} [indicator] The indicator * @property {string} [indicator] The indicator
*/ */
export interface PlayerProgressbarOptions { export interface PlayerProgressbarOptions {
timecodes?: boolean; timecodes?: boolean;
length?: number; length?: number;
line?: string; line?: string;
indicator?: string; indicator?: string;
queue?: boolean; queue?: boolean;
} }
/** /**
@ -130,7 +130,7 @@ export interface PlayerProgressbarOptions {
* @property {boolean} [leaveOnStop=true] If it should leave on stop * @property {boolean} [leaveOnStop=true] If it should leave on stop
* @property {boolean} [leaveOnEmpty=true] If it should leave on empty * @property {boolean} [leaveOnEmpty=true] If it should leave on empty
* @property {number} [leaveOnEmptyCooldown=1000] The cooldown in ms * @property {number} [leaveOnEmptyCooldown=1000] The cooldown in ms
* @property {boolean} [autoSelfDeaf=true] If it should set the bot in deaf mode * @property {boolean} [autoSelfDeaf=false] If it should set the bot in deaf mode
* @property {number} [initialVolume=100] The initial player volume * @property {number} [initialVolume=100] The initial player volume
* @property {number} [bufferingTimeout=3000] Buffering timeout for the stream * @property {number} [bufferingTimeout=3000] Buffering timeout for the stream
* @property {boolean} [disableVolume=false] If player should disable inline volume * @property {boolean} [disableVolume=false] If player should disable inline volume
@ -139,16 +139,16 @@ export interface PlayerProgressbarOptions {
* @property {Function} [onBeforeCreateStream] Runs before creating stream * @property {Function} [onBeforeCreateStream] Runs before creating stream
*/ */
export interface PlayerOptions { export interface PlayerOptions {
leaveOnEnd?: boolean; leaveOnEnd?: boolean;
leaveOnStop?: boolean; leaveOnStop?: boolean;
leaveOnEmpty?: boolean; leaveOnEmpty?: boolean;
leaveOnEmptyCooldown?: number; leaveOnEmptyCooldown?: number;
autoSelfDeaf?: boolean; autoSelfDeaf?: boolean;
initialVolume?: number; initialVolume?: number;
bufferingTimeout?: number; bufferingTimeout?: number;
disableVolume?: boolean; disableVolume?: boolean;
volumeSmoothness?: number; volumeSmoothness?: number;
onBeforeCreateStream?: (track: Track, source: TrackSource, queue: Queue) => Promise<Readable>; onBeforeCreateStream?: (track: Track, source: TrackSource, queue: Queue) => Promise<Readable>;
} }
/** /**
@ -182,32 +182,32 @@ export interface PlayerOptions {
* @property {TrackSource} [source="arbitrary"] The source * @property {TrackSource} [source="arbitrary"] The source
*/ */
export interface ExtractorModelData { export interface ExtractorModelData {
playlist?: { playlist?: {
title: string; title: string;
description: string; description: string;
thumbnail: string; thumbnail: string;
type: "album" | "playlist"; type: "album" | "playlist";
source: TrackSource; source: TrackSource;
author: { author: {
name: string; name: string;
url: string; url: string;
}; };
id: string; id: string;
url: string; url: string;
rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
}; };
data: { data: {
title: string; title: string;
duration: number; duration: number;
thumbnail: string; thumbnail: string;
engine: string | Readable | Duplex; engine: string | Readable | Duplex;
views: number; views: number;
author: string; author: string;
description: string; description: string;
url: string; url: string;
version?: string; version?: string;
source?: TrackSource; source?: TrackSource;
}[]; }[];
} }
/** /**
@ -232,22 +232,22 @@ export interface ExtractorModelData {
* @typedef {number} QueryType * @typedef {number} QueryType
*/ */
export enum QueryType { export enum QueryType {
AUTO, AUTO,
YOUTUBE, YOUTUBE,
YOUTUBE_PLAYLIST, YOUTUBE_PLAYLIST,
SOUNDCLOUD_TRACK, SOUNDCLOUD_TRACK,
SOUNDCLOUD_PLAYLIST, SOUNDCLOUD_PLAYLIST,
SOUNDCLOUD, SOUNDCLOUD,
SPOTIFY_SONG, SPOTIFY_SONG,
SPOTIFY_ALBUM, SPOTIFY_ALBUM,
SPOTIFY_PLAYLIST, SPOTIFY_PLAYLIST,
FACEBOOK, FACEBOOK,
VIMEO, VIMEO,
ARBITRARY, ARBITRARY,
REVERBNATION, REVERBNATION,
YOUTUBE_SEARCH, YOUTUBE_SEARCH,
YOUTUBE_VIDEO, YOUTUBE_VIDEO,
SOUNDCLOUD_SEARCH SOUNDCLOUD_SEARCH
} }
/** /**
@ -327,17 +327,17 @@ export enum QueryType {
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
export interface PlayerEvents { export interface PlayerEvents {
botDisconnect: (queue: Queue) => any; botDisconnect: (queue: Queue) => any;
channelEmpty: (queue: Queue) => any; channelEmpty: (queue: Queue) => any;
connectionCreate: (queue: Queue, connection: StreamDispatcher) => any; connectionCreate: (queue: Queue, connection: StreamDispatcher) => any;
debug: (queue: Queue, message: string) => any; debug: (queue: Queue, message: string) => any;
error: (queue: Queue, error: Error) => any; error: (queue: Queue, error: Error) => any;
connectionError: (queue: Queue, error: Error) => any; connectionError: (queue: Queue, error: Error) => any;
queueEnd: (queue: Queue) => any; queueEnd: (queue: Queue) => any;
trackAdd: (queue: Queue, track: Track) => any; trackAdd: (queue: Queue, track: Track) => any;
tracksAdd: (queue: Queue, track: Track[]) => any; tracksAdd: (queue: Queue, track: Track[]) => any;
trackStart: (queue: Queue, track: Track) => any; trackStart: (queue: Queue, track: Track) => any;
trackEnd: (queue: Queue, track: Track) => any; trackEnd: (queue: Queue, track: Track) => any;
} }
/* eslint-enable @typescript-eslint/no-explicit-any */ /* eslint-enable @typescript-eslint/no-explicit-any */
@ -350,10 +350,10 @@ export interface PlayerEvents {
* @property {boolean} [immediate=false] If it should start playing the provided track immediately * @property {boolean} [immediate=false] If it should start playing the provided track immediately
*/ */
export interface PlayOptions { export interface PlayOptions {
filtersUpdate?: boolean; filtersUpdate?: boolean;
encoderArgs?: string[]; encoderArgs?: string[];
seek?: number; seek?: number;
immediate?: boolean; immediate?: boolean;
} }
/** /**
@ -363,9 +363,9 @@ export interface PlayOptions {
* @property {boolean} [blockExtractor=false] If it should block custom extractors * @property {boolean} [blockExtractor=false] If it should block custom extractors
*/ */
export interface SearchOptions { export interface SearchOptions {
requestedBy: UserResolvable; requestedBy: UserResolvable;
searchEngine?: QueryType | string; searchEngine?: QueryType | string;
blockExtractor?: boolean; blockExtractor?: boolean;
} }
/** /**
@ -377,10 +377,10 @@ export interface SearchOptions {
* @typedef {number} QueueRepeatMode * @typedef {number} QueueRepeatMode
*/ */
export enum QueueRepeatMode { export enum QueueRepeatMode {
OFF = 0, OFF = 0,
TRACK = 1, TRACK = 1,
QUEUE = 2, QUEUE = 2,
AUTOPLAY = 3 AUTOPLAY = 3
} }
/** /**
@ -399,19 +399,19 @@ export enum QueueRepeatMode {
* @property {any} [rawPlaylist] The raw playlist data * @property {any} [rawPlaylist] The raw playlist data
*/ */
export interface PlaylistInitData { export interface PlaylistInitData {
tracks: Track[]; tracks: Track[];
title: string; title: string;
description: string; description: string;
thumbnail: string; thumbnail: string;
type: "album" | "playlist"; type: "album" | "playlist";
source: TrackSource; source: TrackSource;
author: { author: {
name: string; name: string;
url: string; url: string;
}; };
id: string; id: string;
url: string; url: string;
rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
} }
/** /**
@ -428,17 +428,17 @@ export interface PlaylistInitData {
* @property {PlaylistJSON} [playlist] The playlist info (if any) * @property {PlaylistJSON} [playlist] The playlist info (if any)
*/ */
export interface TrackJSON { export interface TrackJSON {
id: Snowflake; id: Snowflake;
title: string; title: string;
description: string; description: string;
author: string; author: string;
url: string; url: string;
thumbnail: string; thumbnail: string;
duration: string; duration: string;
durationMS: number; durationMS: number;
views: number; views: number;
requestedBy: Snowflake; requestedBy: Snowflake;
playlist?: PlaylistJSON; playlist?: PlaylistJSON;
} }
/** /**
@ -456,18 +456,18 @@ export interface TrackJSON {
* @property {TrackJSON[]} tracks The tracks data (if any) * @property {TrackJSON[]} tracks The tracks data (if any)
*/ */
export interface PlaylistJSON { export interface PlaylistJSON {
id: string; id: string;
url: string; url: string;
title: string; title: string;
description: string; description: string;
thumbnail: string; thumbnail: string;
type: "album" | "playlist"; type: "album" | "playlist";
source: TrackSource; source: TrackSource;
author: { author: {
name: string; name: string;
url: string; url: string;
}; };
tracks: TrackJSON[]; tracks: TrackJSON[];
} }
/** /**
@ -476,6 +476,6 @@ export interface PlaylistJSON {
* @property {number} [connectionTimeout=20000] The voice connection timeout * @property {number} [connectionTimeout=20000] The voice connection timeout
*/ */
export interface PlayerInitOptions { export interface PlayerInitOptions {
autoRegisterExtractor?: boolean; autoRegisterExtractor?: boolean;
connectionTimeout?: number; connectionTimeout?: number;
} }

View file

@ -3,104 +3,104 @@ import { FiltersName } from "../types/types";
const bass = (g: number) => `bass=g=${g}:f=110:w=0.3`; const bass = (g: number) => `bass=g=${g}:f=110:w=0.3`;
class AudioFilters { class AudioFilters {
public constructor() { public constructor() {
return AudioFilters; return AudioFilters;
} }
public static get filters(): Record<FiltersName, string> { public static get filters(): Record<FiltersName, string> {
return { return {
bassboost_low: bass(15), bassboost_low: bass(15),
bassboost: bass(20), bassboost: bass(20),
bassboost_high: bass(30), bassboost_high: bass(30),
"8D": "apulsator=hz=0.09", "8D": "apulsator=hz=0.09",
vaporwave: "aresample=48000,asetrate=48000*0.8", vaporwave: "aresample=48000,asetrate=48000*0.8",
nightcore: "aresample=48000,asetrate=48000*1.25", nightcore: "aresample=48000,asetrate=48000*1.25",
phaser: "aphaser=in_gain=0.4", phaser: "aphaser=in_gain=0.4",
tremolo: "tremolo", tremolo: "tremolo",
vibrato: "vibrato=f=6.5", vibrato: "vibrato=f=6.5",
reverse: "areverse", reverse: "areverse",
treble: "treble=g=5", treble: "treble=g=5",
normalizer2: "dynaudnorm=g=101", normalizer2: "dynaudnorm=g=101",
normalizer: "acompressor", normalizer: "acompressor",
surrounding: "surround", surrounding: "surround",
pulsator: "apulsator=hz=1", pulsator: "apulsator=hz=1",
subboost: "asubboost", subboost: "asubboost",
karaoke: "stereotools=mlev=0.03", karaoke: "stereotools=mlev=0.03",
flanger: "flanger", flanger: "flanger",
gate: "agate", gate: "agate",
haas: "haas", haas: "haas",
mcompand: "mcompand", mcompand: "mcompand",
mono: "pan=mono|c0=.5*c0+.5*c1", mono: "pan=mono|c0=.5*c0+.5*c1",
mstlr: "stereotools=mode=ms>lr", mstlr: "stereotools=mode=ms>lr",
mstrr: "stereotools=mode=ms>rr", mstrr: "stereotools=mode=ms>rr",
compressor: "compand=points=-80/-105|-62/-80|-15.4/-15.4|0/-12|20/-7.6", compressor: "compand=points=-80/-105|-62/-80|-15.4/-15.4|0/-12|20/-7.6",
expander: "compand=attacks=0:points=-80/-169|-54/-80|-49.5/-64.6|-41.1/-41.1|-25.8/-15|-10.8/-4.5|0/0|20/8.3", expander: "compand=attacks=0:points=-80/-169|-54/-80|-49.5/-64.6|-41.1/-41.1|-25.8/-15|-10.8/-4.5|0/0|20/8.3",
softlimiter: "compand=attacks=0:points=-80/-80|-12.4/-12.4|-6/-8|0/-6.8|20/-2.8", softlimiter: "compand=attacks=0:points=-80/-80|-12.4/-12.4|-6/-8|0/-6.8|20/-2.8",
chorus: "chorus=0.7:0.9:55:0.4:0.25:2", chorus: "chorus=0.7:0.9:55:0.4:0.25:2",
chorus2d: "chorus=0.6:0.9:50|60:0.4|0.32:0.25|0.4:2|1.3", chorus2d: "chorus=0.6:0.9:50|60:0.4|0.32:0.25|0.4:2|1.3",
chorus3d: "chorus=0.5:0.9:50|60|40:0.4|0.32|0.3:0.25|0.4|0.3:2|2.3|1.3", chorus3d: "chorus=0.5:0.9:50|60|40:0.4|0.32|0.3:0.25|0.4|0.3:2|2.3|1.3",
fadein: "afade=t=in:ss=0:d=10", fadein: "afade=t=in:ss=0:d=10",
dim: `afftfilt="'real=re * (1-clip((b/nb)*b,0,1))':imag='im * (1-clip((b/nb)*b,0,1))'"`, dim: `afftfilt="'real=re * (1-clip((b/nb)*b,0,1))':imag='im * (1-clip((b/nb)*b,0,1))'"`,
earrape: "channelsplit,sidechaingate=level_in=64" earrape: "channelsplit,sidechaingate=level_in=64"
}; };
} }
public static get<K extends FiltersName>(name: K) { public static get<K extends FiltersName>(name: K) {
return this.filters[name]; return this.filters[name];
} }
public static has<K extends FiltersName>(name: K) { public static has<K extends FiltersName>(name: K) {
return name in this.filters; return name in this.filters;
} }
public static *[Symbol.iterator](): IterableIterator<{ name: FiltersName; value: string }> { public static *[Symbol.iterator](): IterableIterator<{ name: FiltersName; value: string }> {
for (const [k, v] of Object.entries(this.filters)) { for (const [k, v] of Object.entries(this.filters)) {
yield { name: k as FiltersName, value: v as string }; yield { name: k as FiltersName, value: v as string };
} }
} }
public static get names() { public static get names() {
return Object.keys(this.filters) as FiltersName[]; return Object.keys(this.filters) as FiltersName[];
} }
// @ts-expect-error AudioFilters.length // @ts-expect-error AudioFilters.length
public static get length() { public static get length() {
return this.names.length; return this.names.length;
} }
public static toString() { public static toString() {
return this.names.map((m) => (this as any)[m]).join(","); // eslint-disable-line @typescript-eslint/no-explicit-any return this.names.map((m) => (this as any)[m]).join(","); // eslint-disable-line @typescript-eslint/no-explicit-any
} }
/** /**
* Create ffmpeg args from the specified filters name * Create ffmpeg args from the specified filters name
* @param filter The filter name * @param filter The filter name
* @returns * @returns
*/ */
public static create<K extends FiltersName>(filters?: K[]) { public static create<K extends FiltersName>(filters?: K[]) {
if (!filters || !Array.isArray(filters)) return this.toString(); if (!filters || !Array.isArray(filters)) return this.toString();
return filters return filters
.filter((predicate) => typeof predicate === "string") .filter((predicate) => typeof predicate === "string")
.map((m) => this.get(m)) .map((m) => this.get(m))
.join(","); .join(",");
} }
/** /**
* Defines audio filter * Defines audio filter
* @param filterName The name of the filter * @param filterName The name of the filter
* @param value The ffmpeg args * @param value The ffmpeg args
*/ */
public static define(filterName: string, value: string) { public static define(filterName: string, value: string) {
this.filters[filterName as FiltersName] = value; this.filters[filterName as FiltersName] = value;
} }
/** /**
* Defines multiple audio filters * Defines multiple audio filters
* @param filtersArray Array of filters containing the filter name and ffmpeg args * @param filtersArray Array of filters containing the filter name and ffmpeg args
*/ */
public static defineBulk(filtersArray: { name: string; value: string }[]) { public static defineBulk(filtersArray: { name: string; value: string }[]) {
filtersArray.forEach((arr) => this.define(arr.name, arr.value)); filtersArray.forEach((arr) => this.define(arr.name, arr.value));
} }
} }
export default AudioFilters; export default AudioFilters;

View file

@ -2,15 +2,15 @@ import { FFmpeg } from "prism-media";
import type { Duplex, Readable } from "stream"; import type { Duplex, Readable } from "stream";
export interface FFmpegStreamOptions { export interface FFmpegStreamOptions {
fmt?: string; fmt?: string;
encoderArgs?: string[]; encoderArgs?: string[];
seek?: number; seek?: number;
skip?: boolean; skip?: boolean;
} }
export function FFMPEG_ARGS_STRING(stream: string, fmt?: string) { export function FFMPEG_ARGS_STRING(stream: string, fmt?: string) {
// prettier-ignore // prettier-ignore
return [ return [
"-reconnect", "1", "-reconnect", "1",
"-reconnect_streamed", "1", "-reconnect_streamed", "1",
"-reconnect_delay_max", "5", "-reconnect_delay_max", "5",
@ -24,8 +24,8 @@ export function FFMPEG_ARGS_STRING(stream: string, fmt?: string) {
} }
export function FFMPEG_ARGS_PIPED(fmt?: string) { export function FFMPEG_ARGS_PIPED(fmt?: string) {
// prettier-ignore // prettier-ignore
return [ return [
"-analyzeduration", "0", "-analyzeduration", "0",
"-loglevel", "0", "-loglevel", "0",
"-f", `${typeof fmt === "string" ? fmt : "s16le"}`, "-f", `${typeof fmt === "string" ? fmt : "s16le"}`,
@ -40,20 +40,20 @@ export function FFMPEG_ARGS_PIPED(fmt?: string) {
* @param options FFmpeg stream options * @param options FFmpeg stream options
*/ */
export function createFFmpegStream(stream: Readable | Duplex | string, options?: FFmpegStreamOptions) { export function createFFmpegStream(stream: Readable | Duplex | string, options?: FFmpegStreamOptions) {
if (options.skip && typeof stream !== "string") return stream; if (options.skip && typeof stream !== "string") return stream;
options ??= {}; options ??= {};
const args = typeof stream === "string" ? FFMPEG_ARGS_STRING(stream, options.fmt) : FFMPEG_ARGS_PIPED(options.fmt); const args = typeof stream === "string" ? FFMPEG_ARGS_STRING(stream, options.fmt) : FFMPEG_ARGS_PIPED(options.fmt);
if (!Number.isNaN(options.seek)) args.unshift("-ss", String(options.seek)); if (!Number.isNaN(options.seek)) args.unshift("-ss", String(options.seek));
if (Array.isArray(options.encoderArgs)) args.push(...options.encoderArgs); if (Array.isArray(options.encoderArgs)) args.push(...options.encoderArgs);
const transcoder = new FFmpeg({ shell: false, args }); const transcoder = new FFmpeg({ shell: false, args });
transcoder.on("close", () => transcoder.destroy()); transcoder.on("close", () => transcoder.destroy());
if (typeof stream !== "string") { if (typeof stream !== "string") {
stream.on("error", () => transcoder.destroy()); stream.on("error", () => transcoder.destroy());
stream.pipe(transcoder); stream.pipe(transcoder);
} }
return transcoder; return transcoder;
} }

View file

@ -10,49 +10,49 @@ const vimeoRegex = /(http|https)?:\/\/(www\.|player\.)?vimeo\.com\/(?:channels\/
const facebookRegex = /(https?:\/\/)(www\.|m\.)?(facebook|fb).com\/.*\/videos\/.*/; const facebookRegex = /(https?:\/\/)(www\.|m\.)?(facebook|fb).com\/.*\/videos\/.*/;
const reverbnationRegex = /https:\/\/(www.)?reverbnation.com\/(.+)\/song\/(.+)/; const reverbnationRegex = /https:\/\/(www.)?reverbnation.com\/(.+)\/song\/(.+)/;
const attachmentRegex = const attachmentRegex =
/^(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/; /^(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/;
// scary things above *sigh* // scary things above *sigh*
class QueryResolver { class QueryResolver {
/** /**
* Query resolver * Query resolver
*/ */
private constructor() {} // eslint-disable-line @typescript-eslint/no-empty-function private constructor() {} // eslint-disable-line @typescript-eslint/no-empty-function
/** /**
* Resolves the given search query * Resolves the given search query
* @param {string} query The query * @param {string} query The query
* @returns {QueryType} * @returns {QueryType}
*/ */
static async resolve(query: string): Promise<QueryType> { static async resolve(query: string): Promise<QueryType> {
if ((await play.so_validate(query)) === "track") return QueryType.SOUNDCLOUD_TRACK; if ((await play.so_validate(query)) === "track") return QueryType.SOUNDCLOUD_TRACK;
if ((await play.so_validate(query)) === "playlist" || query.includes("/sets/")) return QueryType.SOUNDCLOUD_PLAYLIST; if ((await play.so_validate(query)) === "playlist" || query.includes("/sets/")) return QueryType.SOUNDCLOUD_PLAYLIST;
if (play.yt_validate(query) === "playlist") return QueryType.YOUTUBE_PLAYLIST; if (play.yt_validate(query) === "playlist") return QueryType.YOUTUBE_PLAYLIST;
if (play.yt_validate(query) === "video") return QueryType.YOUTUBE_VIDEO; if (play.yt_validate(query) === "video") return QueryType.YOUTUBE_VIDEO;
if (spotifySongRegex.test(query)) return QueryType.SPOTIFY_SONG; if (spotifySongRegex.test(query)) return QueryType.SPOTIFY_SONG;
if (spotifyPlaylistRegex.test(query)) return QueryType.SPOTIFY_PLAYLIST; if (spotifyPlaylistRegex.test(query)) return QueryType.SPOTIFY_PLAYLIST;
if (spotifyAlbumRegex.test(query)) return QueryType.SPOTIFY_ALBUM; if (spotifyAlbumRegex.test(query)) return QueryType.SPOTIFY_ALBUM;
if (vimeoRegex.test(query)) return QueryType.VIMEO; if (vimeoRegex.test(query)) return QueryType.VIMEO;
if (facebookRegex.test(query)) return QueryType.FACEBOOK; if (facebookRegex.test(query)) return QueryType.FACEBOOK;
if (reverbnationRegex.test(query)) return QueryType.REVERBNATION; if (reverbnationRegex.test(query)) return QueryType.REVERBNATION;
if (attachmentRegex.test(query)) return QueryType.ARBITRARY; if (attachmentRegex.test(query)) return QueryType.ARBITRARY;
return QueryType.YOUTUBE_SEARCH; return QueryType.YOUTUBE_SEARCH;
} }
/** /**
* Parses vimeo id from url * Parses vimeo id from url
* @param {string} query The query * @param {string} query The query
* @returns {string} * @returns {string}
*/ */
static async getVimeoID(query: string): Promise<string> { static async getVimeoID(query: string): Promise<string> {
return (await QueryResolver.resolve(query)) === QueryType.VIMEO return (await QueryResolver.resolve(query)) === QueryType.VIMEO
? query ? query
.split("/") .split("/")
.filter((x) => !!x) .filter((x) => !!x)
.pop() .pop()
: null; : null;
} }
} }
export { QueryResolver }; export { QueryResolver };

View file

@ -2,116 +2,116 @@ import { StageChannel, VoiceChannel } from "discord.js";
import { TimeData } from "../types/types"; import { TimeData } from "../types/types";
class Util { class Util {
/** /**
* Utils * Utils
*/ */
private constructor() {} // eslint-disable-line @typescript-eslint/no-empty-function private constructor() {} // eslint-disable-line @typescript-eslint/no-empty-function
/** /**
* Creates duration string * Creates duration string
* @param {object} durObj The duration object * @param {object} durObj The duration object
* @returns {string} * @returns {string}
*/ */
static durationString(durObj: Record<string, number>) { static durationString(durObj: Record<string, number>) {
return Object.values(durObj) return Object.values(durObj)
.map((m) => (isNaN(m) ? 0 : m)) .map((m) => (isNaN(m) ? 0 : m))
.join(":"); .join(":");
} }
/** /**
* Parses milliseconds to consumable time object * Parses milliseconds to consumable time object
* @param {number} milliseconds The time in ms * @param {number} milliseconds The time in ms
* @returns {TimeData} * @returns {TimeData}
*/ */
static parseMS(milliseconds: number) { static parseMS(milliseconds: number) {
const round = milliseconds > 0 ? Math.floor : Math.ceil; const round = milliseconds > 0 ? Math.floor : Math.ceil;
return { return {
days: round(milliseconds / 86400000), days: round(milliseconds / 86400000),
hours: round(milliseconds / 3600000) % 24, hours: round(milliseconds / 3600000) % 24,
minutes: round(milliseconds / 60000) % 60, minutes: round(milliseconds / 60000) % 60,
seconds: round(milliseconds / 1000) % 60 seconds: round(milliseconds / 1000) % 60
} as TimeData; } as TimeData;
} }
/** /**
* Builds time code * Builds time code
* @param {TimeData} duration The duration object * @param {TimeData} duration The duration object
* @returns {string} * @returns {string}
*/ */
static buildTimeCode(duration: TimeData) { static buildTimeCode(duration: TimeData) {
const items = Object.keys(duration); const items = Object.keys(duration);
const required = ["days", "hours", "minutes", "seconds"]; const required = ["days", "hours", "minutes", "seconds"];
const parsed = items.filter((x) => required.includes(x)).map((m) => duration[m as keyof TimeData]); const parsed = items.filter((x) => required.includes(x)).map((m) => duration[m as keyof TimeData]);
const final = parsed const final = parsed
.slice(parsed.findIndex((x) => x !== 0)) .slice(parsed.findIndex((x) => x !== 0))
.map((x) => x.toString().padStart(2, "0")) .map((x) => x.toString().padStart(2, "0"))
.join(":"); .join(":");
return final.length <= 3 ? `0:${final.padStart(2, "0") || 0}` : final; return final.length <= 3 ? `0:${final.padStart(2, "0") || 0}` : final;
} }
/** /**
* Picks last item of the given array * Picks last item of the given array
* @param {any[]} arr The array * @param {any[]} arr The array
* @returns {any} * @returns {any}
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
static last<T = any>(arr: T[]): T { static last<T = any>(arr: T[]): T {
if (!Array.isArray(arr)) return; if (!Array.isArray(arr)) return;
return arr[arr.length - 1]; return arr[arr.length - 1];
} }
/** /**
* Checks if the voice channel is empty * Checks if the voice channel is empty
* @param {VoiceChannel|StageChannel} channel The voice channel * @param {VoiceChannel|StageChannel} channel The voice channel
* @returns {boolean} * @returns {boolean}
*/ */
static isVoiceEmpty(channel: VoiceChannel | StageChannel) { static isVoiceEmpty(channel: VoiceChannel | StageChannel) {
return channel.members.filter((member) => !member.user.bot).size === 0; return channel.members.filter((member) => !member.user.bot).size === 0;
} }
/** /**
* Safer require * Safer require
* @param {string} id Node require id * @param {string} id Node require id
* @returns {any} * @returns {any}
*/ */
static require(id: string) { static require(id: string) {
try { try {
return require(id); return require(id);
} catch { } catch {
return null; return null;
} }
} }
/** /**
* Asynchronous timeout * Asynchronous timeout
* @param {number} time The time in ms to wait * @param {number} time The time in ms to wait
* @returns {Promise<unknown>} * @returns {Promise<unknown>}
*/ */
static wait(time: number) { static wait(time: number) {
return new Promise((r) => setTimeout(r, time).unref()); return new Promise((r) => setTimeout(r, time).unref());
} }
static noop() {} // eslint-disable-line @typescript-eslint/no-empty-function static noop() {} // eslint-disable-line @typescript-eslint/no-empty-function
static async getFetch() { static async getFetch() {
if ("fetch" in globalThis) return globalThis.fetch; if ("fetch" in globalThis) return globalThis.fetch;
for (const lib of ["undici", "node-fetch"]) { for (const lib of ["undici", "node-fetch"]) {
try { try {
return await import(lib).then((res) => res.fetch || res.default?.fetch || res.default); return await import(lib).then((res) => res.fetch || res.default?.fetch || res.default);
} catch { } catch {
try { try {
// eslint-disable-next-line // eslint-disable-next-line
const res = require(lib); const res = require(lib);
if (res) return res.fetch || res.default?.fetch || res.default; if (res) return res.fetch || res.default?.fetch || res.default;
} catch { } catch {
// no? // no?
} }
} }
} }
} }
} }
export { Util }; export { Util };