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/ban-ts-comment": "error",
"semi": "error",
"no-console": "error"
"no-console": "error",
"no-mixed-spaces-and-tabs": "off"
}
}

View file

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

View file

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

View file

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

View file

@ -3,136 +3,136 @@ import { Track } from "./Track";
import { PlaylistInitData, PlaylistJSON, TrackJSON, TrackSource } from "../types/types";
class Playlist {
public readonly player: Player;
public tracks: Track[];
public title: string;
public description: string;
public thumbnail: string;
public type: "album" | "playlist";
public source: TrackSource;
public author: {
name: string;
url: string;
};
public id: string;
public url: string;
public readonly rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
public readonly player: Player;
public tracks: Track[];
public title: string;
public description: string;
public thumbnail: string;
public type: "album" | "playlist";
public source: TrackSource;
public author: {
name: string;
url: string;
};
public id: string;
public url: string;
public readonly rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
/**
* Playlist constructor
* @param {Player} player The player
* @param {PlaylistInitData} data The data
*/
constructor(player: Player, data: PlaylistInitData) {
/**
* The player
* @name Playlist#player
* @type {Player}
* @readonly
*/
this.player = player;
/**
* Playlist constructor
* @param {Player} player The player
* @param {PlaylistInitData} data The data
*/
constructor(player: Player, data: PlaylistInitData) {
/**
* The player
* @name Playlist#player
* @type {Player}
* @readonly
*/
this.player = player;
/**
* The tracks in this playlist
* @name Playlist#tracks
* @type {Track[]}
*/
this.tracks = data.tracks ?? [];
/**
* The tracks in this playlist
* @name Playlist#tracks
* @type {Track[]}
*/
this.tracks = data.tracks ?? [];
/**
* The author of this playlist
* @name Playlist#author
* @type {object}
*/
this.author = data.author;
/**
* The author of this playlist
* @name Playlist#author
* @type {object}
*/
this.author = data.author;
/**
* The description
* @name Playlist#description
* @type {string}
*/
this.description = data.description;
/**
* The description
* @name Playlist#description
* @type {string}
*/
this.description = data.description;
/**
* The thumbnail of this playlist
* @name Playlist#thumbnail
* @type {string}
*/
this.thumbnail = data.thumbnail;
/**
* The thumbnail of this playlist
* @name Playlist#thumbnail
* @type {string}
*/
this.thumbnail = data.thumbnail;
/**
* The playlist type:
* - `album`
* - `playlist`
* @name Playlist#type
* @type {string}
*/
this.type = data.type;
/**
* The playlist type:
* - `album`
* - `playlist`
* @name Playlist#type
* @type {string}
*/
this.type = data.type;
/**
* The source of this playlist:
* - `youtube`
* - `soundcloud`
* - `spotify`
* - `arbitrary`
* @name Playlist#source
* @type {string}
*/
this.source = data.source;
/**
* The source of this playlist:
* - `youtube`
* - `soundcloud`
* - `spotify`
* - `arbitrary`
* @name Playlist#source
* @type {string}
*/
this.source = data.source;
/**
* The playlist id
* @name Playlist#id
* @type {string}
*/
this.id = data.id;
/**
* The playlist id
* @name Playlist#id
* @type {string}
*/
this.id = data.id;
/**
* The playlist url
* @name Playlist#url
* @type {string}
*/
this.url = data.url;
/**
* The playlist url
* @name Playlist#url
* @type {string}
*/
this.url = data.url;
/**
* The playlist title
* @type {string}
*/
this.title = data.title;
/**
* The playlist title
* @type {string}
*/
this.title = data.title;
/**
* @name Playlist#rawPlaylist
* @type {any}
* @readonly
*/
}
/**
* @name Playlist#rawPlaylist
* @type {any}
* @readonly
*/
}
*[Symbol.iterator]() {
yield* this.tracks;
}
*[Symbol.iterator]() {
yield* this.tracks;
}
/**
* JSON representation of this playlist
* @param {boolean} [withTracks=true] If it should build json with tracks
* @returns {PlaylistJSON}
*/
toJSON(withTracks = true) {
const payload = {
id: this.id,
url: this.url,
title: this.title,
description: this.description,
thumbnail: this.thumbnail,
type: this.type,
source: this.source,
author: this.author,
tracks: [] as TrackJSON[]
};
/**
* JSON representation of this playlist
* @param {boolean} [withTracks=true] If it should build json with tracks
* @returns {PlaylistJSON}
*/
toJSON(withTracks = true) {
const payload = {
id: this.id,
url: this.url,
title: this.title,
description: this.description,
thumbnail: this.thumbnail,
type: this.type,
source: this.source,
author: this.author,
tracks: [] as TrackJSON[]
};
if (withTracks) payload.tracks = this.tracks.map((m) => m.toJSON(true));
if (withTracks) payload.tracks = this.tracks.map((m) => m.toJSON(true));
return payload as PlaylistJSON;
}
return payload as PlaylistJSON;
}
}
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";
class Track {
public player!: Player;
public title!: string;
public description!: string;
public author!: string;
public url!: string;
public thumbnail!: string;
public duration!: string;
public views!: number;
public requestedBy!: User;
public playlist?: Playlist;
public readonly raw: RawTrackData = {} as RawTrackData;
public readonly id = SnowflakeUtil.generate().toString();
public player!: Player;
public title!: string;
public description!: string;
public author!: string;
public url!: string;
public thumbnail!: string;
public duration!: string;
public views!: number;
public requestedBy!: User;
public playlist?: Playlist;
public readonly raw: RawTrackData = {} as RawTrackData;
public readonly id = SnowflakeUtil.generate().toString();
/**
* Track constructor
* @param {Player} player The player that instantiated this Track
* @param {RawTrackData} data Track data
*/
constructor(player: Player, data: RawTrackData) {
/**
* The player that instantiated this Track
* @name Track#player
* @type {Player}
* @readonly
*/
Object.defineProperty(this, "player", { value: player, enumerable: false });
/**
* Track constructor
* @param {Player} player The player that instantiated this Track
* @param {RawTrackData} data Track data
*/
constructor(player: Player, data: RawTrackData) {
/**
* The player that instantiated this Track
* @name Track#player
* @type {Player}
* @readonly
*/
Object.defineProperty(this, "player", { value: player, enumerable: false });
/**
* Title of this track
* @name Track#title
* @type {string}
*/
/**
* Title of this track
* @name Track#title
* @type {string}
*/
/**
* Description of this track
* @name Track#description
* @type {string}
*/
/**
* Description of this track
* @name Track#description
* @type {string}
*/
/**
* Author of this track
* @name Track#author
* @type {string}
*/
/**
* Author of this track
* @name Track#author
* @type {string}
*/
/**
* URL of this track
* @name Track#url
* @type {string}
*/
/**
* URL of this track
* @name Track#url
* @type {string}
*/
/**
* Thumbnail of this track
* @name Track#thumbnail
* @type {string}
*/
/**
* Thumbnail of this track
* @name Track#thumbnail
* @type {string}
*/
/**
* Duration of this track
* @name Track#duration
* @type {string}
*/
/**
* Duration of this track
* @name Track#duration
* @type {string}
*/
/**
* Views count of this track
* @name Track#views
* @type {number}
*/
/**
* Views count of this track
* @name Track#views
* @type {number}
*/
/**
* Person who requested this track
* @name Track#requestedBy
* @type {User}
*/
/**
* Person who requested this track
* @name Track#requestedBy
* @type {User}
*/
/**
* If this track belongs to playlist
* @name Track#fromPlaylist
* @type {boolean}
*/
/**
* If this track belongs to playlist
* @name Track#fromPlaylist
* @type {boolean}
*/
/**
* Raw track data
* @name Track#raw
* @type {RawTrackData}
*/
/**
* Raw track data
* @name Track#raw
* @type {RawTrackData}
*/
/**
* The track id
* @name Track#id
* @type {Snowflake}
* @readonly
*/
/**
* The track id
* @name Track#id
* @type {Snowflake}
* @readonly
*/
/**
* The playlist which track belongs
* @name Track#playlist
* @type {Playlist}
*/
/**
* The playlist which track belongs
* @name Track#playlist
* @type {Playlist}
*/
void this._patch(data);
}
void this._patch(data);
}
private _patch(data: RawTrackData) {
this.title = escapeMarkdown(data.title ?? "");
this.description = data.description ?? "";
this.author = data.author ?? "";
this.url = data.url ?? "";
this.thumbnail = data.thumbnail ?? "";
this.duration = data.duration ?? "";
this.views = data.views ?? 0;
this.requestedBy = data.requestedBy;
this.playlist = data.playlist;
private _patch(data: RawTrackData) {
this.title = escapeMarkdown(data.title ?? "");
this.description = data.description ?? "";
this.author = data.author ?? "";
this.url = data.url ?? "";
this.thumbnail = data.thumbnail ?? "";
this.duration = data.duration ?? "";
this.views = data.views ?? 0;
this.requestedBy = data.requestedBy;
this.playlist = data.playlist;
// raw
Object.defineProperty(this, "raw", { value: Object.assign({}, { source: data.raw?.source ?? data.source }, data.raw ?? data), enumerable: false });
}
// raw
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
* @type {Queue}
*/
get queue(): Queue {
return this.player.queues.find((q) => q.tracks.some((ab) => ab.id === this.id));
}
/**
* The queue in which this track is located
* @type {Queue}
*/
get queue(): Queue {
return this.player.queues.find((q) => q.tracks.some((ab) => ab.id === this.id));
}
/**
* The track duration in millisecond
* @type {number}
*/
get durationMS(): number {
const times = (n: number, t: number) => {
let tn = 1;
for (let i = 0; i < t; i++) tn *= n;
return t <= 0 ? 1000 : tn * 1000;
};
/**
* The track duration in millisecond
* @type {number}
*/
get durationMS(): number {
const times = (n: number, t: number) => {
let tn = 1;
for (let i = 0; i < t; i++) tn *= n;
return t <= 0 ? 1000 : tn * 1000;
};
return this.duration
.split(":")
.reverse()
.map((m, i) => parseInt(m) * times(60, i))
.reduce((a, c) => a + c, 0);
}
return this.duration
.split(":")
.reverse()
.map((m, i) => parseInt(m) * times(60, i))
.reduce((a, c) => a + c, 0);
}
/**
* Returns source of this track
* @type {TrackSource}
*/
get source() {
return this.raw.source ?? "arbitrary";
}
/**
* Returns source of this track
* @type {TrackSource}
*/
get source() {
return this.raw.source ?? "arbitrary";
}
/**
* String representation of this track
* @returns {string}
*/
toString(): string {
return `${this.title} by ${this.author}`;
}
/**
* String representation of this track
* @returns {string}
*/
toString(): string {
return `${this.title} by ${this.author}`;
}
/**
* Raw JSON representation of this track
* @returns {TrackJSON}
*/
toJSON(hidePlaylist?: boolean) {
return {
id: this.id,
title: this.title,
description: this.description,
author: this.author,
url: this.url,
thumbnail: this.thumbnail,
duration: this.duration,
durationMS: this.durationMS,
views: this.views,
requestedBy: this.requestedBy?.id,
playlist: hidePlaylist ? null : this.playlist?.toJSON() ?? null
} as TrackJSON;
}
/**
* Raw JSON representation of this track
* @returns {TrackJSON}
*/
toJSON(hidePlaylist?: boolean) {
return {
id: this.id,
title: this.title,
description: this.description,
author: this.author,
url: this.url,
thumbnail: this.thumbnail,
duration: this.duration,
durationMS: this.durationMS,
views: this.views,
requestedBy: this.requestedBy?.id,
playlist: hidePlaylist ? null : this.playlist?.toJSON() ?? null
} as TrackJSON;
}
}
export default Track;

View file

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

View file

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

View file

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

View file

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

View file

@ -8,48 +8,48 @@ import { StreamDispatcher } from "../VoiceInterface/StreamDispatcher";
export type FiltersName = keyof QueueFilters;
export interface PlayerSearchResult {
playlist: Playlist | null;
tracks: Track[];
searched?: boolean;
playlist: Playlist | null;
tracks: Track[];
searched?: boolean;
}
/**
* @typedef {AudioFilters} QueueFilters
*/
export interface QueueFilters {
bassboost_low?: boolean;
bassboost?: boolean;
bassboost_high?: boolean;
"8D"?: boolean;
vaporwave?: boolean;
nightcore?: boolean;
phaser?: boolean;
tremolo?: boolean;
vibrato?: boolean;
reverse?: boolean;
treble?: boolean;
normalizer?: boolean;
normalizer2?: boolean;
surrounding?: boolean;
pulsator?: boolean;
subboost?: boolean;
karaoke?: boolean;
flanger?: boolean;
gate?: boolean;
haas?: boolean;
mcompand?: boolean;
mono?: boolean;
mstlr?: boolean;
mstrr?: boolean;
compressor?: boolean;
expander?: boolean;
softlimiter?: boolean;
chorus?: boolean;
chorus2d?: boolean;
chorus3d?: boolean;
fadein?: boolean;
dim?: boolean;
earrape?: boolean;
bassboost_low?: boolean;
bassboost?: boolean;
bassboost_high?: boolean;
"8D"?: boolean;
vaporwave?: boolean;
nightcore?: boolean;
phaser?: boolean;
tremolo?: boolean;
vibrato?: boolean;
reverse?: boolean;
treble?: boolean;
normalizer?: boolean;
normalizer2?: boolean;
surrounding?: boolean;
pulsator?: boolean;
subboost?: boolean;
karaoke?: boolean;
flanger?: boolean;
gate?: boolean;
haas?: boolean;
mcompand?: boolean;
mono?: boolean;
mstlr?: boolean;
mstrr?: boolean;
compressor?: boolean;
expander?: boolean;
softlimiter?: boolean;
chorus?: boolean;
chorus2d?: boolean;
chorus3d?: boolean;
fadein?: boolean;
dim?: boolean;
earrape?: boolean;
}
/**
@ -79,19 +79,19 @@ export type TrackSource = "soundcloud" | "youtube" | "spotify" | "arbitrary";
* @property {any} [raw] The raw data
*/
export interface RawTrackData {
title: string;
description: string;
author: string;
url: string;
thumbnail: string;
duration: string;
views: number;
requestedBy: User;
playlist?: Playlist;
source?: TrackSource;
engine?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
live?: boolean;
raw?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
title: string;
description: string;
author: string;
url: string;
thumbnail: string;
duration: string;
views: number;
requestedBy: User;
playlist?: Playlist;
source?: TrackSource;
engine?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
live?: boolean;
raw?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
}
/**
@ -102,10 +102,10 @@ export interface RawTrackData {
* @property {number} seconds Time in seconds
*/
export interface TimeData {
days: number;
hours: number;
minutes: number;
seconds: number;
days: number;
hours: number;
minutes: number;
seconds: number;
}
/**
@ -117,11 +117,11 @@ export interface TimeData {
* @property {string} [indicator] The indicator
*/
export interface PlayerProgressbarOptions {
timecodes?: boolean;
length?: number;
line?: string;
indicator?: string;
queue?: boolean;
timecodes?: boolean;
length?: number;
line?: string;
indicator?: string;
queue?: boolean;
}
/**
@ -130,7 +130,7 @@ export interface PlayerProgressbarOptions {
* @property {boolean} [leaveOnStop=true] If it should leave on stop
* @property {boolean} [leaveOnEmpty=true] If it should leave on empty
* @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} [bufferingTimeout=3000] Buffering timeout for the stream
* @property {boolean} [disableVolume=false] If player should disable inline volume
@ -139,16 +139,16 @@ export interface PlayerProgressbarOptions {
* @property {Function} [onBeforeCreateStream] Runs before creating stream
*/
export interface PlayerOptions {
leaveOnEnd?: boolean;
leaveOnStop?: boolean;
leaveOnEmpty?: boolean;
leaveOnEmptyCooldown?: number;
autoSelfDeaf?: boolean;
initialVolume?: number;
bufferingTimeout?: number;
disableVolume?: boolean;
volumeSmoothness?: number;
onBeforeCreateStream?: (track: Track, source: TrackSource, queue: Queue) => Promise<Readable>;
leaveOnEnd?: boolean;
leaveOnStop?: boolean;
leaveOnEmpty?: boolean;
leaveOnEmptyCooldown?: number;
autoSelfDeaf?: boolean;
initialVolume?: number;
bufferingTimeout?: number;
disableVolume?: boolean;
volumeSmoothness?: number;
onBeforeCreateStream?: (track: Track, source: TrackSource, queue: Queue) => Promise<Readable>;
}
/**
@ -182,32 +182,32 @@ export interface PlayerOptions {
* @property {TrackSource} [source="arbitrary"] The source
*/
export interface ExtractorModelData {
playlist?: {
title: string;
description: string;
thumbnail: string;
type: "album" | "playlist";
source: TrackSource;
author: {
name: string;
url: string;
};
id: string;
url: string;
rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
};
data: {
title: string;
duration: number;
thumbnail: string;
engine: string | Readable | Duplex;
views: number;
author: string;
description: string;
url: string;
version?: string;
source?: TrackSource;
}[];
playlist?: {
title: string;
description: string;
thumbnail: string;
type: "album" | "playlist";
source: TrackSource;
author: {
name: string;
url: string;
};
id: string;
url: string;
rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
};
data: {
title: string;
duration: number;
thumbnail: string;
engine: string | Readable | Duplex;
views: number;
author: string;
description: string;
url: string;
version?: string;
source?: TrackSource;
}[];
}
/**
@ -232,22 +232,22 @@ export interface ExtractorModelData {
* @typedef {number} QueryType
*/
export enum QueryType {
AUTO,
YOUTUBE,
YOUTUBE_PLAYLIST,
SOUNDCLOUD_TRACK,
SOUNDCLOUD_PLAYLIST,
SOUNDCLOUD,
SPOTIFY_SONG,
SPOTIFY_ALBUM,
SPOTIFY_PLAYLIST,
FACEBOOK,
VIMEO,
ARBITRARY,
REVERBNATION,
YOUTUBE_SEARCH,
YOUTUBE_VIDEO,
SOUNDCLOUD_SEARCH
AUTO,
YOUTUBE,
YOUTUBE_PLAYLIST,
SOUNDCLOUD_TRACK,
SOUNDCLOUD_PLAYLIST,
SOUNDCLOUD,
SPOTIFY_SONG,
SPOTIFY_ALBUM,
SPOTIFY_PLAYLIST,
FACEBOOK,
VIMEO,
ARBITRARY,
REVERBNATION,
YOUTUBE_SEARCH,
YOUTUBE_VIDEO,
SOUNDCLOUD_SEARCH
}
/**
@ -327,17 +327,17 @@ export enum QueryType {
/* eslint-disable @typescript-eslint/no-explicit-any */
export interface PlayerEvents {
botDisconnect: (queue: Queue) => any;
channelEmpty: (queue: Queue) => any;
connectionCreate: (queue: Queue, connection: StreamDispatcher) => any;
debug: (queue: Queue, message: string) => any;
error: (queue: Queue, error: Error) => any;
connectionError: (queue: Queue, error: Error) => any;
queueEnd: (queue: Queue) => any;
trackAdd: (queue: Queue, track: Track) => any;
tracksAdd: (queue: Queue, track: Track[]) => any;
trackStart: (queue: Queue, track: Track) => any;
trackEnd: (queue: Queue, track: Track) => any;
botDisconnect: (queue: Queue) => any;
channelEmpty: (queue: Queue) => any;
connectionCreate: (queue: Queue, connection: StreamDispatcher) => any;
debug: (queue: Queue, message: string) => any;
error: (queue: Queue, error: Error) => any;
connectionError: (queue: Queue, error: Error) => any;
queueEnd: (queue: Queue) => any;
trackAdd: (queue: Queue, track: Track) => any;
tracksAdd: (queue: Queue, track: Track[]) => any;
trackStart: (queue: Queue, track: Track) => any;
trackEnd: (queue: Queue, track: Track) => 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
*/
export interface PlayOptions {
filtersUpdate?: boolean;
encoderArgs?: string[];
seek?: number;
immediate?: boolean;
filtersUpdate?: boolean;
encoderArgs?: string[];
seek?: number;
immediate?: boolean;
}
/**
@ -363,9 +363,9 @@ export interface PlayOptions {
* @property {boolean} [blockExtractor=false] If it should block custom extractors
*/
export interface SearchOptions {
requestedBy: UserResolvable;
searchEngine?: QueryType | string;
blockExtractor?: boolean;
requestedBy: UserResolvable;
searchEngine?: QueryType | string;
blockExtractor?: boolean;
}
/**
@ -377,10 +377,10 @@ export interface SearchOptions {
* @typedef {number} QueueRepeatMode
*/
export enum QueueRepeatMode {
OFF = 0,
TRACK = 1,
QUEUE = 2,
AUTOPLAY = 3
OFF = 0,
TRACK = 1,
QUEUE = 2,
AUTOPLAY = 3
}
/**
@ -399,19 +399,19 @@ export enum QueueRepeatMode {
* @property {any} [rawPlaylist] The raw playlist data
*/
export interface PlaylistInitData {
tracks: Track[];
title: string;
description: string;
thumbnail: string;
type: "album" | "playlist";
source: TrackSource;
author: {
name: string;
url: string;
};
id: string;
url: string;
rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
tracks: Track[];
title: string;
description: string;
thumbnail: string;
type: "album" | "playlist";
source: TrackSource;
author: {
name: string;
url: string;
};
id: string;
url: string;
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)
*/
export interface TrackJSON {
id: Snowflake;
title: string;
description: string;
author: string;
url: string;
thumbnail: string;
duration: string;
durationMS: number;
views: number;
requestedBy: Snowflake;
playlist?: PlaylistJSON;
id: Snowflake;
title: string;
description: string;
author: string;
url: string;
thumbnail: string;
duration: string;
durationMS: number;
views: number;
requestedBy: Snowflake;
playlist?: PlaylistJSON;
}
/**
@ -456,18 +456,18 @@ export interface TrackJSON {
* @property {TrackJSON[]} tracks The tracks data (if any)
*/
export interface PlaylistJSON {
id: string;
url: string;
title: string;
description: string;
thumbnail: string;
type: "album" | "playlist";
source: TrackSource;
author: {
name: string;
url: string;
};
tracks: TrackJSON[];
id: string;
url: string;
title: string;
description: string;
thumbnail: string;
type: "album" | "playlist";
source: TrackSource;
author: {
name: string;
url: string;
};
tracks: TrackJSON[];
}
/**
@ -476,6 +476,6 @@ export interface PlaylistJSON {
* @property {number} [connectionTimeout=20000] The voice connection timeout
*/
export interface PlayerInitOptions {
autoRegisterExtractor?: boolean;
connectionTimeout?: number;
autoRegisterExtractor?: boolean;
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`;
class AudioFilters {
public constructor() {
return AudioFilters;
}
public constructor() {
return AudioFilters;
}
public static get filters(): Record<FiltersName, string> {
return {
bassboost_low: bass(15),
bassboost: bass(20),
bassboost_high: bass(30),
"8D": "apulsator=hz=0.09",
vaporwave: "aresample=48000,asetrate=48000*0.8",
nightcore: "aresample=48000,asetrate=48000*1.25",
phaser: "aphaser=in_gain=0.4",
tremolo: "tremolo",
vibrato: "vibrato=f=6.5",
reverse: "areverse",
treble: "treble=g=5",
normalizer2: "dynaudnorm=g=101",
normalizer: "acompressor",
surrounding: "surround",
pulsator: "apulsator=hz=1",
subboost: "asubboost",
karaoke: "stereotools=mlev=0.03",
flanger: "flanger",
gate: "agate",
haas: "haas",
mcompand: "mcompand",
mono: "pan=mono|c0=.5*c0+.5*c1",
mstlr: "stereotools=mode=ms>lr",
mstrr: "stereotools=mode=ms>rr",
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",
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",
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",
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))'"`,
earrape: "channelsplit,sidechaingate=level_in=64"
};
}
public static get filters(): Record<FiltersName, string> {
return {
bassboost_low: bass(15),
bassboost: bass(20),
bassboost_high: bass(30),
"8D": "apulsator=hz=0.09",
vaporwave: "aresample=48000,asetrate=48000*0.8",
nightcore: "aresample=48000,asetrate=48000*1.25",
phaser: "aphaser=in_gain=0.4",
tremolo: "tremolo",
vibrato: "vibrato=f=6.5",
reverse: "areverse",
treble: "treble=g=5",
normalizer2: "dynaudnorm=g=101",
normalizer: "acompressor",
surrounding: "surround",
pulsator: "apulsator=hz=1",
subboost: "asubboost",
karaoke: "stereotools=mlev=0.03",
flanger: "flanger",
gate: "agate",
haas: "haas",
mcompand: "mcompand",
mono: "pan=mono|c0=.5*c0+.5*c1",
mstlr: "stereotools=mode=ms>lr",
mstrr: "stereotools=mode=ms>rr",
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",
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",
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",
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))'"`,
earrape: "channelsplit,sidechaingate=level_in=64"
};
}
public static get<K extends FiltersName>(name: K) {
return this.filters[name];
}
public static get<K extends FiltersName>(name: K) {
return this.filters[name];
}
public static has<K extends FiltersName>(name: K) {
return name in this.filters;
}
public static has<K extends FiltersName>(name: K) {
return name in this.filters;
}
public static *[Symbol.iterator](): IterableIterator<{ name: FiltersName; value: string }> {
for (const [k, v] of Object.entries(this.filters)) {
yield { name: k as FiltersName, value: v as string };
}
}
public static *[Symbol.iterator](): IterableIterator<{ name: FiltersName; value: string }> {
for (const [k, v] of Object.entries(this.filters)) {
yield { name: k as FiltersName, value: v as string };
}
}
public static get names() {
return Object.keys(this.filters) as FiltersName[];
}
public static get names() {
return Object.keys(this.filters) as FiltersName[];
}
// @ts-expect-error AudioFilters.length
public static get length() {
return this.names.length;
}
// @ts-expect-error AudioFilters.length
public static get length() {
return this.names.length;
}
public static toString() {
return this.names.map((m) => (this as any)[m]).join(","); // eslint-disable-line @typescript-eslint/no-explicit-any
}
public static toString() {
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
* @param filter The filter name
* @returns
*/
public static create<K extends FiltersName>(filters?: K[]) {
if (!filters || !Array.isArray(filters)) return this.toString();
return filters
.filter((predicate) => typeof predicate === "string")
.map((m) => this.get(m))
.join(",");
}
/**
* Create ffmpeg args from the specified filters name
* @param filter The filter name
* @returns
*/
public static create<K extends FiltersName>(filters?: K[]) {
if (!filters || !Array.isArray(filters)) return this.toString();
return filters
.filter((predicate) => typeof predicate === "string")
.map((m) => this.get(m))
.join(",");
}
/**
* Defines audio filter
* @param filterName The name of the filter
* @param value The ffmpeg args
*/
public static define(filterName: string, value: string) {
this.filters[filterName as FiltersName] = value;
}
/**
* Defines audio filter
* @param filterName The name of the filter
* @param value The ffmpeg args
*/
public static define(filterName: string, value: string) {
this.filters[filterName as FiltersName] = value;
}
/**
* Defines multiple audio filters
* @param filtersArray Array of filters containing the filter name and ffmpeg args
*/
public static defineBulk(filtersArray: { name: string; value: string }[]) {
filtersArray.forEach((arr) => this.define(arr.name, arr.value));
}
/**
* Defines multiple audio filters
* @param filtersArray Array of filters containing the filter name and ffmpeg args
*/
public static defineBulk(filtersArray: { name: string; value: string }[]) {
filtersArray.forEach((arr) => this.define(arr.name, arr.value));
}
}
export default AudioFilters;

View file

@ -2,15 +2,15 @@ import { FFmpeg } from "prism-media";
import type { Duplex, Readable } from "stream";
export interface FFmpegStreamOptions {
fmt?: string;
encoderArgs?: string[];
seek?: number;
skip?: boolean;
fmt?: string;
encoderArgs?: string[];
seek?: number;
skip?: boolean;
}
export function FFMPEG_ARGS_STRING(stream: string, fmt?: string) {
// prettier-ignore
return [
// prettier-ignore
return [
"-reconnect", "1",
"-reconnect_streamed", "1",
"-reconnect_delay_max", "5",
@ -24,8 +24,8 @@ export function FFMPEG_ARGS_STRING(stream: string, fmt?: string) {
}
export function FFMPEG_ARGS_PIPED(fmt?: string) {
// prettier-ignore
return [
// prettier-ignore
return [
"-analyzeduration", "0",
"-loglevel", "0",
"-f", `${typeof fmt === "string" ? fmt : "s16le"}`,
@ -40,20 +40,20 @@ export function FFMPEG_ARGS_PIPED(fmt?: string) {
* @param options FFmpeg stream options
*/
export function createFFmpegStream(stream: Readable | Duplex | string, options?: FFmpegStreamOptions) {
if (options.skip && typeof stream !== "string") return stream;
options ??= {};
const args = typeof stream === "string" ? FFMPEG_ARGS_STRING(stream, options.fmt) : FFMPEG_ARGS_PIPED(options.fmt);
if (options.skip && typeof stream !== "string") return stream;
options ??= {};
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 (Array.isArray(options.encoderArgs)) args.push(...options.encoderArgs);
if (!Number.isNaN(options.seek)) args.unshift("-ss", String(options.seek));
if (Array.isArray(options.encoderArgs)) args.push(...options.encoderArgs);
const transcoder = new FFmpeg({ shell: false, args });
transcoder.on("close", () => transcoder.destroy());
const transcoder = new FFmpeg({ shell: false, args });
transcoder.on("close", () => transcoder.destroy());
if (typeof stream !== "string") {
stream.on("error", () => transcoder.destroy());
stream.pipe(transcoder);
}
if (typeof stream !== "string") {
stream.on("error", () => transcoder.destroy());
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 reverbnationRegex = /https:\/\/(www.)?reverbnation.com\/(.+)\/song\/(.+)/;
const attachmentRegex =
/^(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/;
/^(?:(?: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*
class QueryResolver {
/**
* Query resolver
*/
private constructor() {} // eslint-disable-line @typescript-eslint/no-empty-function
/**
* Query resolver
*/
private constructor() {} // eslint-disable-line @typescript-eslint/no-empty-function
/**
* Resolves the given search query
* @param {string} query The query
* @returns {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)) === "playlist" || query.includes("/sets/")) return QueryType.SOUNDCLOUD_PLAYLIST;
if (play.yt_validate(query) === "playlist") return QueryType.YOUTUBE_PLAYLIST;
if (play.yt_validate(query) === "video") return QueryType.YOUTUBE_VIDEO;
if (spotifySongRegex.test(query)) return QueryType.SPOTIFY_SONG;
if (spotifyPlaylistRegex.test(query)) return QueryType.SPOTIFY_PLAYLIST;
if (spotifyAlbumRegex.test(query)) return QueryType.SPOTIFY_ALBUM;
if (vimeoRegex.test(query)) return QueryType.VIMEO;
if (facebookRegex.test(query)) return QueryType.FACEBOOK;
if (reverbnationRegex.test(query)) return QueryType.REVERBNATION;
if (attachmentRegex.test(query)) return QueryType.ARBITRARY;
/**
* Resolves the given search query
* @param {string} query The query
* @returns {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)) === "playlist" || query.includes("/sets/")) return QueryType.SOUNDCLOUD_PLAYLIST;
if (play.yt_validate(query) === "playlist") return QueryType.YOUTUBE_PLAYLIST;
if (play.yt_validate(query) === "video") return QueryType.YOUTUBE_VIDEO;
if (spotifySongRegex.test(query)) return QueryType.SPOTIFY_SONG;
if (spotifyPlaylistRegex.test(query)) return QueryType.SPOTIFY_PLAYLIST;
if (spotifyAlbumRegex.test(query)) return QueryType.SPOTIFY_ALBUM;
if (vimeoRegex.test(query)) return QueryType.VIMEO;
if (facebookRegex.test(query)) return QueryType.FACEBOOK;
if (reverbnationRegex.test(query)) return QueryType.REVERBNATION;
if (attachmentRegex.test(query)) return QueryType.ARBITRARY;
return QueryType.YOUTUBE_SEARCH;
}
return QueryType.YOUTUBE_SEARCH;
}
/**
* Parses vimeo id from url
* @param {string} query The query
* @returns {string}
*/
static async getVimeoID(query: string): Promise<string> {
return (await QueryResolver.resolve(query)) === QueryType.VIMEO
? query
.split("/")
.filter((x) => !!x)
.pop()
: null;
}
/**
* Parses vimeo id from url
* @param {string} query The query
* @returns {string}
*/
static async getVimeoID(query: string): Promise<string> {
return (await QueryResolver.resolve(query)) === QueryType.VIMEO
? query
.split("/")
.filter((x) => !!x)
.pop()
: null;
}
}
export { QueryResolver };

View file

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