format with tabs
This commit is contained in:
parent
241a22b5d1
commit
83c6179e1e
16 changed files with 2496 additions and 2495 deletions
|
@ -3,5 +3,6 @@
|
|||
"trailingComma": "none",
|
||||
"singleQuote": false,
|
||||
"tabWidth": 4,
|
||||
"useTabs": true,
|
||||
"semi": true
|
||||
}
|
1114
src/Player.ts
1114
src/Player.ts
File diff suppressed because it is too large
Load diff
|
@ -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 };
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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;
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import {
|
||||
AudioPlayer,
|
||||
AudioPlayerError,
|
||||
AudioPlayerStatus,
|
||||
AudioResource,
|
||||
createAudioPlayer,
|
||||
createAudioResource,
|
||||
entersState,
|
||||
StreamType,
|
||||
VoiceConnection,
|
||||
VoiceConnectionStatus,
|
||||
VoiceConnectionDisconnectReason
|
||||
AudioPlayer,
|
||||
AudioPlayerError,
|
||||
AudioPlayerStatus,
|
||||
AudioResource,
|
||||
createAudioPlayer,
|
||||
createAudioResource,
|
||||
entersState,
|
||||
StreamType,
|
||||
VoiceConnection,
|
||||
VoiceConnectionStatus,
|
||||
VoiceConnectionDisconnectReason
|
||||
} from "@discordjs/voice";
|
||||
import { StageChannel, VoiceChannel } from "discord.js";
|
||||
import { Duplex, Readable } from "stream";
|
||||
|
@ -19,235 +19,235 @@ 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 (_, 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.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("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));
|
||||
this.audioPlayer.on("error", (error) => void this.emit("error", error));
|
||||
this.voiceConnection.subscribe(this.audioPlayer);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
});
|
||||
/**
|
||||
* 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)
|
||||
});
|
||||
|
||||
return this.audioResource;
|
||||
}
|
||||
return this.audioResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* The player status
|
||||
* @type {AudioPlayerStatus}
|
||||
*/
|
||||
get status() {
|
||||
return this.audioPlayer.state.status;
|
||||
}
|
||||
/**
|
||||
* The player status
|
||||
* @type {AudioPlayerStatus}
|
||||
*/
|
||||
get status() {
|
||||
return this.audioPlayer.state.status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects from voice
|
||||
* @returns {void}
|
||||
*/
|
||||
disconnect() {
|
||||
try {
|
||||
this.audioPlayer.stop(true);
|
||||
this.voiceConnection.destroy();
|
||||
} catch {} // eslint-disable-line no-empty
|
||||
}
|
||||
/**
|
||||
* Disconnects from voice
|
||||
* @returns {void}
|
||||
*/
|
||||
disconnect() {
|
||||
try {
|
||||
this.audioPlayer.stop(true);
|
||||
this.voiceConnection.destroy();
|
||||
} catch {} // eslint-disable-line no-empty
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the player
|
||||
* @returns {void}
|
||||
*/
|
||||
end() {
|
||||
this.audioPlayer.stop();
|
||||
}
|
||||
/**
|
||||
* Stops the player
|
||||
* @returns {void}
|
||||
*/
|
||||
end() {
|
||||
this.audioPlayer.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes the stream playback
|
||||
* @returns {boolean}
|
||||
*/
|
||||
resume() {
|
||||
const success = this.audioPlayer.unpause();
|
||||
this.paused = !success;
|
||||
return success;
|
||||
}
|
||||
/**
|
||||
* Resumes the stream playback
|
||||
* @returns {boolean}
|
||||
*/
|
||||
resume() {
|
||||
const success = this.audioPlayer.unpause();
|
||||
this.paused = !success;
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
this.audioPlayer.play(resource);
|
||||
} catch (e) {
|
||||
this.emit("error", e as AudioPlayerError);
|
||||
}
|
||||
try {
|
||||
this.audioPlayer.play(resource);
|
||||
} catch (e) {
|
||||
this.emit("error", e as AudioPlayerError);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
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 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;
|
||||
}
|
||||
/**
|
||||
* The playback time
|
||||
* @type {number}
|
||||
*/
|
||||
get streamTime() {
|
||||
if (!this.audioResource) return 0;
|
||||
return this.audioResource.playbackDuration;
|
||||
}
|
||||
}
|
||||
|
||||
export { StreamDispatcher as StreamDispatcher };
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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 };
|
||||
|
|
Loading…
Reference in a new issue