Compare commits
10 commits
0ab06128ac
...
6f189cc523
Author | SHA1 | Date | |
---|---|---|---|
|
6f189cc523 | ||
|
d7ce7acb22 | ||
|
5dc2774e0f | ||
|
2f9c48b927 | ||
|
83c6179e1e | ||
|
241a22b5d1 | ||
|
ca53b6da0c | ||
|
641ef8ee85 | ||
|
5d57f6589a | ||
|
e2fad021d6 |
18 changed files with 2514 additions and 2476 deletions
|
@ -17,6 +17,7 @@
|
||||||
"@typescript-eslint/no-explicit-any": "error",
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
"@typescript-eslint/ban-ts-comment": "error",
|
"@typescript-eslint/ban-ts-comment": "error",
|
||||||
"semi": "error",
|
"semi": "error",
|
||||||
"no-console": "error"
|
"no-console": "error",
|
||||||
|
"no-mixed-spaces-and-tabs": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,5 +3,6 @@
|
||||||
"trailingComma": "none",
|
"trailingComma": "none",
|
||||||
"singleQuote": false,
|
"singleQuote": false,
|
||||||
"tabWidth": 4,
|
"tabWidth": 4,
|
||||||
|
"useTabs": true,
|
||||||
"semi": true
|
"semi": true
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "discord-player-play-dl",
|
"name": "discord-player-play-dl",
|
||||||
"version": "5.3.8",
|
"version": "5.3.14",
|
||||||
"description": "Complete framework to facilitate music commands using discord.js and play-dl",
|
"description": "Complete framework to facilitate music commands using discord.js and play-dl",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|
1095
src/Player.ts
1095
src/Player.ts
File diff suppressed because it is too large
Load diff
|
@ -1,73 +1,73 @@
|
||||||
import { ExtractorModelData } from "../types/types";
|
import { ExtractorModelData } from "../types/types";
|
||||||
|
|
||||||
class ExtractorModel {
|
class ExtractorModel {
|
||||||
name: string;
|
name: string;
|
||||||
private _raw: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
private _raw: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model for raw Discord Player extractors
|
* Model for raw Discord Player extractors
|
||||||
* @param {string} extractorName Name of the extractor
|
* @param {string} extractorName Name of the extractor
|
||||||
* @param {object} data Extractor object
|
* @param {object} data Extractor object
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
constructor(extractorName: string, data: any) {
|
constructor(extractorName: string, data: any) {
|
||||||
/**
|
/**
|
||||||
* The extractor name
|
* The extractor name
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
this.name = extractorName;
|
this.name = extractorName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The raw model
|
* The raw model
|
||||||
* @name ExtractorModel#_raw
|
* @name ExtractorModel#_raw
|
||||||
* @type {any}
|
* @type {any}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
Object.defineProperty(this, "_raw", { value: data, configurable: false, writable: false, enumerable: false });
|
Object.defineProperty(this, "_raw", { value: data, configurable: false, writable: false, enumerable: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to handle requests from `Player.play()`
|
* Method to handle requests from `Player.play()`
|
||||||
* @param {string} query Query to handle
|
* @param {string} query Query to handle
|
||||||
* @returns {Promise<ExtractorModelData>}
|
* @returns {Promise<ExtractorModelData>}
|
||||||
*/
|
*/
|
||||||
async handle(query: string): Promise<ExtractorModelData> {
|
async handle(query: string): Promise<ExtractorModelData> {
|
||||||
const data = await this._raw.getInfo(query);
|
const data = await this._raw.getInfo(query);
|
||||||
if (!data) return null;
|
if (!data) return null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
playlist: data.playlist ?? null,
|
playlist: data.playlist ?? null,
|
||||||
data:
|
data:
|
||||||
(data.info as Omit<ExtractorModelData, "playlist">["data"])?.map((m) => ({
|
(data.info as Omit<ExtractorModelData, "playlist">["data"])?.map((m) => ({
|
||||||
title: m.title as string,
|
title: m.title as string,
|
||||||
duration: m.duration as number,
|
duration: m.duration as number,
|
||||||
thumbnail: m.thumbnail as string,
|
thumbnail: m.thumbnail as string,
|
||||||
engine: m.engine,
|
engine: m.engine,
|
||||||
views: m.views as number,
|
views: m.views as number,
|
||||||
author: m.author as string,
|
author: m.author as string,
|
||||||
description: m.description as string,
|
description: m.description as string,
|
||||||
url: m.url as string,
|
url: m.url as string,
|
||||||
source: m.source || "arbitrary"
|
source: m.source || "arbitrary"
|
||||||
})) ?? []
|
})) ?? []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method used by Discord Player to validate query with this extractor
|
* Method used by Discord Player to validate query with this extractor
|
||||||
* @param {string} query The query to validate
|
* @param {string} query The query to validate
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
validate(query: string): boolean {
|
validate(query: string): boolean {
|
||||||
return Boolean(this._raw.validate(query));
|
return Boolean(this._raw.validate(query));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The extractor version
|
* The extractor version
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
get version(): string {
|
get version(): string {
|
||||||
return this._raw.version ?? "0.0.0";
|
return this._raw.version ?? "0.0.0";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { ExtractorModel };
|
export { ExtractorModel };
|
||||||
|
|
|
@ -1,53 +1,53 @@
|
||||||
export enum ErrorStatusCode {
|
export enum ErrorStatusCode {
|
||||||
STREAM_ERROR = "StreamError",
|
STREAM_ERROR = "StreamError",
|
||||||
AUDIO_PLAYER_ERROR = "AudioPlayerError",
|
AUDIO_PLAYER_ERROR = "AudioPlayerError",
|
||||||
PLAYER_ERROR = "PlayerError",
|
PLAYER_ERROR = "PlayerError",
|
||||||
NO_AUDIO_RESOURCE = "NoAudioResource",
|
NO_AUDIO_RESOURCE = "NoAudioResource",
|
||||||
UNKNOWN_GUILD = "UnknownGuild",
|
UNKNOWN_GUILD = "UnknownGuild",
|
||||||
INVALID_ARG_TYPE = "InvalidArgType",
|
INVALID_ARG_TYPE = "InvalidArgType",
|
||||||
UNKNOWN_EXTRACTOR = "UnknownExtractor",
|
UNKNOWN_EXTRACTOR = "UnknownExtractor",
|
||||||
INVALID_EXTRACTOR = "InvalidExtractor",
|
INVALID_EXTRACTOR = "InvalidExtractor",
|
||||||
INVALID_CHANNEL_TYPE = "InvalidChannelType",
|
INVALID_CHANNEL_TYPE = "InvalidChannelType",
|
||||||
INVALID_TRACK = "InvalidTrack",
|
INVALID_TRACK = "InvalidTrack",
|
||||||
UNKNOWN_REPEAT_MODE = "UnknownRepeatMode",
|
UNKNOWN_REPEAT_MODE = "UnknownRepeatMode",
|
||||||
TRACK_NOT_FOUND = "TrackNotFound",
|
TRACK_NOT_FOUND = "TrackNotFound",
|
||||||
NO_CONNECTION = "NoConnection",
|
NO_CONNECTION = "NoConnection",
|
||||||
DESTROYED_QUEUE = "DestroyedQueue"
|
DESTROYED_QUEUE = "DestroyedQueue"
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PlayerError extends Error {
|
export class PlayerError extends Error {
|
||||||
message: string;
|
message: string;
|
||||||
statusCode: ErrorStatusCode;
|
statusCode: ErrorStatusCode;
|
||||||
createdAt = new Date();
|
createdAt = new Date();
|
||||||
|
|
||||||
constructor(message: string, code: ErrorStatusCode = ErrorStatusCode.PLAYER_ERROR) {
|
constructor(message: string, code: ErrorStatusCode = ErrorStatusCode.PLAYER_ERROR) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.message = `[${code}] ${message}`;
|
this.message = `[${code}] ${message}`;
|
||||||
this.statusCode = code;
|
this.statusCode = code;
|
||||||
this.name = code;
|
this.name = code;
|
||||||
|
|
||||||
Error.captureStackTrace(this);
|
Error.captureStackTrace(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
get createdTimestamp() {
|
get createdTimestamp() {
|
||||||
return this.createdAt.getTime();
|
return this.createdAt.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
valueOf() {
|
valueOf() {
|
||||||
return this.statusCode;
|
return this.statusCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return {
|
return {
|
||||||
stack: this.stack,
|
stack: this.stack,
|
||||||
code: this.statusCode,
|
code: this.statusCode,
|
||||||
message: this.message,
|
message: this.message,
|
||||||
created: this.createdTimestamp
|
created: this.createdTimestamp
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return this.stack;
|
return this.stack;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,136 +3,136 @@ import { Track } from "./Track";
|
||||||
import { PlaylistInitData, PlaylistJSON, TrackJSON, TrackSource } from "../types/types";
|
import { PlaylistInitData, PlaylistJSON, TrackJSON, TrackSource } from "../types/types";
|
||||||
|
|
||||||
class Playlist {
|
class Playlist {
|
||||||
public readonly player: Player;
|
public readonly player: Player;
|
||||||
public tracks: Track[];
|
public tracks: Track[];
|
||||||
public title: string;
|
public title: string;
|
||||||
public description: string;
|
public description: string;
|
||||||
public thumbnail: string;
|
public thumbnail: string;
|
||||||
public type: "album" | "playlist";
|
public type: "album" | "playlist";
|
||||||
public source: TrackSource;
|
public source: TrackSource;
|
||||||
public author: {
|
public author: {
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
};
|
};
|
||||||
public id: string;
|
public id: string;
|
||||||
public url: string;
|
public url: string;
|
||||||
public readonly rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
public readonly rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Playlist constructor
|
* Playlist constructor
|
||||||
* @param {Player} player The player
|
* @param {Player} player The player
|
||||||
* @param {PlaylistInitData} data The data
|
* @param {PlaylistInitData} data The data
|
||||||
*/
|
*/
|
||||||
constructor(player: Player, data: PlaylistInitData) {
|
constructor(player: Player, data: PlaylistInitData) {
|
||||||
/**
|
/**
|
||||||
* The player
|
* The player
|
||||||
* @name Playlist#player
|
* @name Playlist#player
|
||||||
* @type {Player}
|
* @type {Player}
|
||||||
* @readonly
|
* @readonly
|
||||||
*/
|
*/
|
||||||
this.player = player;
|
this.player = player;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The tracks in this playlist
|
* The tracks in this playlist
|
||||||
* @name Playlist#tracks
|
* @name Playlist#tracks
|
||||||
* @type {Track[]}
|
* @type {Track[]}
|
||||||
*/
|
*/
|
||||||
this.tracks = data.tracks ?? [];
|
this.tracks = data.tracks ?? [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The author of this playlist
|
* The author of this playlist
|
||||||
* @name Playlist#author
|
* @name Playlist#author
|
||||||
* @type {object}
|
* @type {object}
|
||||||
*/
|
*/
|
||||||
this.author = data.author;
|
this.author = data.author;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The description
|
* The description
|
||||||
* @name Playlist#description
|
* @name Playlist#description
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
this.description = data.description;
|
this.description = data.description;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The thumbnail of this playlist
|
* The thumbnail of this playlist
|
||||||
* @name Playlist#thumbnail
|
* @name Playlist#thumbnail
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
this.thumbnail = data.thumbnail;
|
this.thumbnail = data.thumbnail;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The playlist type:
|
* The playlist type:
|
||||||
* - `album`
|
* - `album`
|
||||||
* - `playlist`
|
* - `playlist`
|
||||||
* @name Playlist#type
|
* @name Playlist#type
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
this.type = data.type;
|
this.type = data.type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The source of this playlist:
|
* The source of this playlist:
|
||||||
* - `youtube`
|
* - `youtube`
|
||||||
* - `soundcloud`
|
* - `soundcloud`
|
||||||
* - `spotify`
|
* - `spotify`
|
||||||
* - `arbitrary`
|
* - `arbitrary`
|
||||||
* @name Playlist#source
|
* @name Playlist#source
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
this.source = data.source;
|
this.source = data.source;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The playlist id
|
* The playlist id
|
||||||
* @name Playlist#id
|
* @name Playlist#id
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
this.id = data.id;
|
this.id = data.id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The playlist url
|
* The playlist url
|
||||||
* @name Playlist#url
|
* @name Playlist#url
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
this.url = data.url;
|
this.url = data.url;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The playlist title
|
* The playlist title
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
this.title = data.title;
|
this.title = data.title;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name Playlist#rawPlaylist
|
* @name Playlist#rawPlaylist
|
||||||
* @type {any}
|
* @type {any}
|
||||||
* @readonly
|
* @readonly
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
*[Symbol.iterator]() {
|
*[Symbol.iterator]() {
|
||||||
yield* this.tracks;
|
yield* this.tracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSON representation of this playlist
|
* JSON representation of this playlist
|
||||||
* @param {boolean} [withTracks=true] If it should build json with tracks
|
* @param {boolean} [withTracks=true] If it should build json with tracks
|
||||||
* @returns {PlaylistJSON}
|
* @returns {PlaylistJSON}
|
||||||
*/
|
*/
|
||||||
toJSON(withTracks = true) {
|
toJSON(withTracks = true) {
|
||||||
const payload = {
|
const payload = {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
url: this.url,
|
url: this.url,
|
||||||
title: this.title,
|
title: this.title,
|
||||||
description: this.description,
|
description: this.description,
|
||||||
thumbnail: this.thumbnail,
|
thumbnail: this.thumbnail,
|
||||||
type: this.type,
|
type: this.type,
|
||||||
source: this.source,
|
source: this.source,
|
||||||
author: this.author,
|
author: this.author,
|
||||||
tracks: [] as TrackJSON[]
|
tracks: [] as TrackJSON[]
|
||||||
};
|
};
|
||||||
|
|
||||||
if (withTracks) payload.tracks = this.tracks.map((m) => m.toJSON(true));
|
if (withTracks) payload.tracks = this.tracks.map((m) => m.toJSON(true));
|
||||||
|
|
||||||
return payload as PlaylistJSON;
|
return payload as PlaylistJSON;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Playlist };
|
export { Playlist };
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -5,185 +5,185 @@ import { Playlist } from "./Playlist";
|
||||||
import { Queue } from "./Queue";
|
import { Queue } from "./Queue";
|
||||||
|
|
||||||
class Track {
|
class Track {
|
||||||
public player!: Player;
|
public player!: Player;
|
||||||
public title!: string;
|
public title!: string;
|
||||||
public description!: string;
|
public description!: string;
|
||||||
public author!: string;
|
public author!: string;
|
||||||
public url!: string;
|
public url!: string;
|
||||||
public thumbnail!: string;
|
public thumbnail!: string;
|
||||||
public duration!: string;
|
public duration!: string;
|
||||||
public views!: number;
|
public views!: number;
|
||||||
public requestedBy!: User;
|
public requestedBy!: User;
|
||||||
public playlist?: Playlist;
|
public playlist?: Playlist;
|
||||||
public readonly raw: RawTrackData = {} as RawTrackData;
|
public readonly raw: RawTrackData = {} as RawTrackData;
|
||||||
public readonly id = SnowflakeUtil.generate().toString();
|
public readonly id = SnowflakeUtil.generate().toString();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Track constructor
|
* Track constructor
|
||||||
* @param {Player} player The player that instantiated this Track
|
* @param {Player} player The player that instantiated this Track
|
||||||
* @param {RawTrackData} data Track data
|
* @param {RawTrackData} data Track data
|
||||||
*/
|
*/
|
||||||
constructor(player: Player, data: RawTrackData) {
|
constructor(player: Player, data: RawTrackData) {
|
||||||
/**
|
/**
|
||||||
* The player that instantiated this Track
|
* The player that instantiated this Track
|
||||||
* @name Track#player
|
* @name Track#player
|
||||||
* @type {Player}
|
* @type {Player}
|
||||||
* @readonly
|
* @readonly
|
||||||
*/
|
*/
|
||||||
Object.defineProperty(this, "player", { value: player, enumerable: false });
|
Object.defineProperty(this, "player", { value: player, enumerable: false });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Title of this track
|
* Title of this track
|
||||||
* @name Track#title
|
* @name Track#title
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Description of this track
|
* Description of this track
|
||||||
* @name Track#description
|
* @name Track#description
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Author of this track
|
* Author of this track
|
||||||
* @name Track#author
|
* @name Track#author
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* URL of this track
|
* URL of this track
|
||||||
* @name Track#url
|
* @name Track#url
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thumbnail of this track
|
* Thumbnail of this track
|
||||||
* @name Track#thumbnail
|
* @name Track#thumbnail
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Duration of this track
|
* Duration of this track
|
||||||
* @name Track#duration
|
* @name Track#duration
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Views count of this track
|
* Views count of this track
|
||||||
* @name Track#views
|
* @name Track#views
|
||||||
* @type {number}
|
* @type {number}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Person who requested this track
|
* Person who requested this track
|
||||||
* @name Track#requestedBy
|
* @name Track#requestedBy
|
||||||
* @type {User}
|
* @type {User}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this track belongs to playlist
|
* If this track belongs to playlist
|
||||||
* @name Track#fromPlaylist
|
* @name Track#fromPlaylist
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Raw track data
|
* Raw track data
|
||||||
* @name Track#raw
|
* @name Track#raw
|
||||||
* @type {RawTrackData}
|
* @type {RawTrackData}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The track id
|
* The track id
|
||||||
* @name Track#id
|
* @name Track#id
|
||||||
* @type {Snowflake}
|
* @type {Snowflake}
|
||||||
* @readonly
|
* @readonly
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The playlist which track belongs
|
* The playlist which track belongs
|
||||||
* @name Track#playlist
|
* @name Track#playlist
|
||||||
* @type {Playlist}
|
* @type {Playlist}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void this._patch(data);
|
void this._patch(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _patch(data: RawTrackData) {
|
private _patch(data: RawTrackData) {
|
||||||
this.title = escapeMarkdown(data.title ?? "");
|
this.title = escapeMarkdown(data.title ?? "");
|
||||||
this.description = data.description ?? "";
|
this.description = data.description ?? "";
|
||||||
this.author = data.author ?? "";
|
this.author = data.author ?? "";
|
||||||
this.url = data.url ?? "";
|
this.url = data.url ?? "";
|
||||||
this.thumbnail = data.thumbnail ?? "";
|
this.thumbnail = data.thumbnail ?? "";
|
||||||
this.duration = data.duration ?? "";
|
this.duration = data.duration ?? "";
|
||||||
this.views = data.views ?? 0;
|
this.views = data.views ?? 0;
|
||||||
this.requestedBy = data.requestedBy;
|
this.requestedBy = data.requestedBy;
|
||||||
this.playlist = data.playlist;
|
this.playlist = data.playlist;
|
||||||
|
|
||||||
// raw
|
// raw
|
||||||
Object.defineProperty(this, "raw", { value: Object.assign({}, { source: data.raw?.source ?? data.source }, data.raw ?? data), enumerable: false });
|
Object.defineProperty(this, "raw", { value: Object.assign({}, { source: data.raw?.source ?? data.source }, data.raw ?? data), enumerable: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The queue in which this track is located
|
* The queue in which this track is located
|
||||||
* @type {Queue}
|
* @type {Queue}
|
||||||
*/
|
*/
|
||||||
get queue(): Queue {
|
get queue(): Queue {
|
||||||
return this.player.queues.find((q) => q.tracks.some((ab) => ab.id === this.id));
|
return this.player.queues.find((q) => q.tracks.some((ab) => ab.id === this.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The track duration in millisecond
|
* The track duration in millisecond
|
||||||
* @type {number}
|
* @type {number}
|
||||||
*/
|
*/
|
||||||
get durationMS(): number {
|
get durationMS(): number {
|
||||||
const times = (n: number, t: number) => {
|
const times = (n: number, t: number) => {
|
||||||
let tn = 1;
|
let tn = 1;
|
||||||
for (let i = 0; i < t; i++) tn *= n;
|
for (let i = 0; i < t; i++) tn *= n;
|
||||||
return t <= 0 ? 1000 : tn * 1000;
|
return t <= 0 ? 1000 : tn * 1000;
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.duration
|
return this.duration
|
||||||
.split(":")
|
.split(":")
|
||||||
.reverse()
|
.reverse()
|
||||||
.map((m, i) => parseInt(m) * times(60, i))
|
.map((m, i) => parseInt(m) * times(60, i))
|
||||||
.reduce((a, c) => a + c, 0);
|
.reduce((a, c) => a + c, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns source of this track
|
* Returns source of this track
|
||||||
* @type {TrackSource}
|
* @type {TrackSource}
|
||||||
*/
|
*/
|
||||||
get source() {
|
get source() {
|
||||||
return this.raw.source ?? "arbitrary";
|
return this.raw.source ?? "arbitrary";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* String representation of this track
|
* String representation of this track
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
toString(): string {
|
toString(): string {
|
||||||
return `${this.title} by ${this.author}`;
|
return `${this.title} by ${this.author}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Raw JSON representation of this track
|
* Raw JSON representation of this track
|
||||||
* @returns {TrackJSON}
|
* @returns {TrackJSON}
|
||||||
*/
|
*/
|
||||||
toJSON(hidePlaylist?: boolean) {
|
toJSON(hidePlaylist?: boolean) {
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
title: this.title,
|
title: this.title,
|
||||||
description: this.description,
|
description: this.description,
|
||||||
author: this.author,
|
author: this.author,
|
||||||
url: this.url,
|
url: this.url,
|
||||||
thumbnail: this.thumbnail,
|
thumbnail: this.thumbnail,
|
||||||
duration: this.duration,
|
duration: this.duration,
|
||||||
durationMS: this.durationMS,
|
durationMS: this.durationMS,
|
||||||
views: this.views,
|
views: this.views,
|
||||||
requestedBy: this.requestedBy?.id,
|
requestedBy: this.requestedBy?.id,
|
||||||
playlist: hidePlaylist ? null : this.playlist?.toJSON() ?? null
|
playlist: hidePlaylist ? null : this.playlist?.toJSON() ?? null
|
||||||
} as TrackJSON;
|
} as TrackJSON;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Track;
|
export default Track;
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import {
|
import {
|
||||||
AudioPlayer,
|
AudioPlayer,
|
||||||
AudioPlayerError,
|
AudioPlayerError,
|
||||||
AudioPlayerStatus,
|
AudioPlayerStatus,
|
||||||
AudioResource,
|
AudioResource,
|
||||||
createAudioPlayer,
|
createAudioPlayer,
|
||||||
createAudioResource,
|
createAudioResource,
|
||||||
entersState,
|
entersState,
|
||||||
StreamType,
|
StreamType,
|
||||||
VoiceConnection,
|
VoiceConnection,
|
||||||
VoiceConnectionStatus,
|
VoiceConnectionStatus,
|
||||||
VoiceConnectionDisconnectReason
|
VoiceConnectionDisconnectReason,
|
||||||
|
VoiceConnectionState
|
||||||
} from "@discordjs/voice";
|
} from "@discordjs/voice";
|
||||||
import { StageChannel, VoiceChannel } from "discord.js";
|
import { StageChannel, VoiceChannel } from "discord.js";
|
||||||
import { Duplex, Readable } from "stream";
|
import { Duplex, Readable } from "stream";
|
||||||
|
@ -19,235 +20,248 @@ import { Util } from "../utils/Util";
|
||||||
import { PlayerError, ErrorStatusCode } from "../Structures/PlayerError";
|
import { PlayerError, ErrorStatusCode } from "../Structures/PlayerError";
|
||||||
|
|
||||||
export interface VoiceEvents {
|
export interface VoiceEvents {
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
error: (error: AudioPlayerError) => any;
|
error: (error: AudioPlayerError) => any;
|
||||||
debug: (message: string) => any;
|
debug: (message: string) => any;
|
||||||
start: (resource: AudioResource<Track>) => any;
|
start: (resource: AudioResource<Track>) => any;
|
||||||
finish: (resource: AudioResource<Track>) => any;
|
finish: (resource: AudioResource<Track>) => any;
|
||||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||||
}
|
}
|
||||||
|
|
||||||
class StreamDispatcher extends EventEmitter<VoiceEvents> {
|
class StreamDispatcher extends EventEmitter<VoiceEvents> {
|
||||||
public readonly voiceConnection: VoiceConnection;
|
public readonly voiceConnection: VoiceConnection;
|
||||||
public readonly audioPlayer: AudioPlayer;
|
public readonly audioPlayer: AudioPlayer;
|
||||||
public channel: VoiceChannel | StageChannel;
|
public channel: VoiceChannel | StageChannel;
|
||||||
public audioResource?: AudioResource<Track>;
|
public audioResource?: AudioResource<Track>;
|
||||||
private readyLock = false;
|
private readyLock = false;
|
||||||
public paused: boolean;
|
public paused: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new connection object
|
* Creates new connection object
|
||||||
* @param {VoiceConnection} connection The connection
|
* @param {VoiceConnection} connection The connection
|
||||||
* @param {VoiceChannel|StageChannel} channel The connected channel
|
* @param {VoiceChannel|StageChannel} channel The connected channel
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
constructor(connection: VoiceConnection, channel: VoiceChannel | StageChannel, public readonly connectionTimeout: number = 20000) {
|
constructor(connection: VoiceConnection, channel: VoiceChannel | StageChannel, public readonly connectionTimeout: number = 20000) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The voice connection
|
* The voice connection
|
||||||
* @type {VoiceConnection}
|
* @type {VoiceConnection}
|
||||||
*/
|
*/
|
||||||
this.voiceConnection = connection;
|
this.voiceConnection = connection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The audio player
|
* The audio player
|
||||||
* @type {AudioPlayer}
|
* @type {AudioPlayer}
|
||||||
*/
|
*/
|
||||||
this.audioPlayer = createAudioPlayer();
|
this.audioPlayer = createAudioPlayer();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The voice channel
|
* The voice channel
|
||||||
* @type {VoiceChannel|StageChannel}
|
* @type {VoiceChannel|StageChannel}
|
||||||
*/
|
*/
|
||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The paused state
|
* The paused state
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
this.paused = false;
|
this.paused = false;
|
||||||
|
|
||||||
this.voiceConnection.on("stateChange", async (_, newState) => {
|
this.voiceConnection.on("stateChange", async (oldState, newState) => {
|
||||||
if (newState.status === VoiceConnectionStatus.Disconnected) {
|
// oh no, fix no work
|
||||||
if (newState.reason === VoiceConnectionDisconnectReason.WebSocketClose && newState.closeCode === 4014) {
|
const oldNetworking = Reflect.get(oldState, "networking");
|
||||||
try {
|
const newNetworking = Reflect.get(newState, "networking");
|
||||||
await entersState(this.voiceConnection, VoiceConnectionStatus.Connecting, this.connectionTimeout);
|
|
||||||
} catch {
|
|
||||||
try {
|
|
||||||
this.voiceConnection.destroy();
|
|
||||||
} catch (err) {
|
|
||||||
this.emit("error", err as AudioPlayerError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (this.voiceConnection.rejoinAttempts < 5) {
|
|
||||||
await Util.wait((this.voiceConnection.rejoinAttempts + 1) * 5000);
|
|
||||||
this.voiceConnection.rejoin();
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
this.voiceConnection.destroy();
|
|
||||||
} catch (err) {
|
|
||||||
this.emit("error", err as AudioPlayerError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (newState.status === VoiceConnectionStatus.Destroyed) {
|
|
||||||
this.end();
|
|
||||||
} else if (!this.readyLock && (newState.status === VoiceConnectionStatus.Connecting || newState.status === VoiceConnectionStatus.Signalling)) {
|
|
||||||
this.readyLock = true;
|
|
||||||
try {
|
|
||||||
await entersState(this.voiceConnection, VoiceConnectionStatus.Ready, this.connectionTimeout);
|
|
||||||
} catch {
|
|
||||||
if (this.voiceConnection.state.status !== VoiceConnectionStatus.Destroyed) {
|
|
||||||
try {
|
|
||||||
this.voiceConnection.destroy();
|
|
||||||
} catch (err) {
|
|
||||||
this.emit("error", err as AudioPlayerError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
this.readyLock = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.audioPlayer.on("stateChange", (oldState, newState) => {
|
const networkStateChangeHandler = (_: VoiceConnectionState, newNetworkState: VoiceConnectionState) => {
|
||||||
if (newState.status === AudioPlayerStatus.Playing) {
|
const newUdp = Reflect.get(newNetworkState, "udp");
|
||||||
if (!this.paused) return void this.emit("start", this.audioResource);
|
clearInterval(newUdp?.keepAliveInterval);
|
||||||
} else if (newState.status === AudioPlayerStatus.Idle && oldState.status !== AudioPlayerStatus.Idle) {
|
};
|
||||||
if (!this.paused) {
|
|
||||||
void this.emit("finish", this.audioResource);
|
|
||||||
this.audioResource = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.audioPlayer.on("debug", (m) => void this.emit("debug", m));
|
oldNetworking?.off("stateChange", networkStateChangeHandler);
|
||||||
this.audioPlayer.on("error", (error) => void this.emit("error", error));
|
newNetworking?.on("stateChange", networkStateChangeHandler);
|
||||||
this.voiceConnection.subscribe(this.audioPlayer);
|
// temp fix end
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (newState.status === VoiceConnectionStatus.Disconnected) {
|
||||||
* Creates stream
|
if (newState.reason === VoiceConnectionDisconnectReason.WebSocketClose && newState.closeCode === 4014) {
|
||||||
* @param {Readable|Duplex|string} src The stream source
|
try {
|
||||||
* @param {object} [ops] Options
|
await entersState(this.voiceConnection, VoiceConnectionStatus.Connecting, this.connectionTimeout);
|
||||||
* @returns {AudioResource}
|
} catch {
|
||||||
*/
|
try {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
this.voiceConnection.destroy();
|
||||||
createStream(src: Readable | Duplex | string, ops?: { type?: StreamType; data?: any; disableVolume?: boolean }) {
|
} catch (err) {
|
||||||
this.audioResource = createAudioResource(src, {
|
this.emit("error", err as AudioPlayerError);
|
||||||
inputType: ops?.type ?? StreamType.Arbitrary,
|
}
|
||||||
metadata: ops?.data,
|
}
|
||||||
// eslint-disable-next-line no-extra-boolean-cast
|
} else if (this.voiceConnection.rejoinAttempts < 5) {
|
||||||
inlineVolume: !Boolean(ops?.disableVolume)
|
await Util.wait((this.voiceConnection.rejoinAttempts + 1) * 5000);
|
||||||
});
|
this.voiceConnection.rejoin();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
this.voiceConnection.destroy();
|
||||||
|
} catch (err) {
|
||||||
|
this.emit("error", err as AudioPlayerError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (newState.status === VoiceConnectionStatus.Destroyed) {
|
||||||
|
this.end();
|
||||||
|
} else if (!this.readyLock && (newState.status === VoiceConnectionStatus.Connecting || newState.status === VoiceConnectionStatus.Signalling)) {
|
||||||
|
this.readyLock = true;
|
||||||
|
try {
|
||||||
|
await entersState(this.voiceConnection, VoiceConnectionStatus.Ready, this.connectionTimeout);
|
||||||
|
} catch {
|
||||||
|
if (this.voiceConnection.state.status !== VoiceConnectionStatus.Destroyed) {
|
||||||
|
try {
|
||||||
|
this.voiceConnection.destroy();
|
||||||
|
} catch (err) {
|
||||||
|
this.emit("error", err as AudioPlayerError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.readyLock = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return this.audioResource;
|
this.audioPlayer.on("stateChange", (oldState, newState) => {
|
||||||
}
|
if (newState.status === AudioPlayerStatus.Playing) {
|
||||||
|
if (!this.paused) return void this.emit("start", this.audioResource);
|
||||||
|
} else if (newState.status === AudioPlayerStatus.Idle && oldState.status !== AudioPlayerStatus.Idle) {
|
||||||
|
if (!this.paused) {
|
||||||
|
void this.emit("finish", this.audioResource);
|
||||||
|
this.audioResource = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
this.audioPlayer.on("debug", (m) => void this.emit("debug", m));
|
||||||
* The player status
|
this.audioPlayer.on("error", (error) => void this.emit("error", error));
|
||||||
* @type {AudioPlayerStatus}
|
this.voiceConnection.subscribe(this.audioPlayer);
|
||||||
*/
|
}
|
||||||
get status() {
|
|
||||||
return this.audioPlayer.state.status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disconnects from voice
|
* Creates stream
|
||||||
* @returns {void}
|
* @param {Readable|Duplex|string} src The stream source
|
||||||
*/
|
* @param {object} [ops] Options
|
||||||
disconnect() {
|
* @returns {AudioResource}
|
||||||
try {
|
*/
|
||||||
this.audioPlayer.stop(true);
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
this.voiceConnection.destroy();
|
createStream(src: Readable | Duplex | string, ops?: { type?: StreamType; data?: any; disableVolume?: boolean }) {
|
||||||
} catch {} // eslint-disable-line no-empty
|
this.audioResource = createAudioResource(src, {
|
||||||
}
|
inputType: ops?.type ?? StreamType.Arbitrary,
|
||||||
|
metadata: ops?.data,
|
||||||
|
// eslint-disable-next-line no-extra-boolean-cast
|
||||||
|
inlineVolume: !Boolean(ops?.disableVolume)
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
return this.audioResource;
|
||||||
* Stops the player
|
}
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
end() {
|
|
||||||
this.audioPlayer.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pauses the stream playback
|
* The player status
|
||||||
* @param {boolean} [interpolateSilence=false] If true, the player will play 5 packets of silence after pausing to prevent audio glitches.
|
* @type {AudioPlayerStatus}
|
||||||
* @returns {boolean}
|
*/
|
||||||
*/
|
get status() {
|
||||||
pause(interpolateSilence?: boolean) {
|
return this.audioPlayer.state.status;
|
||||||
const success = this.audioPlayer.pause(interpolateSilence);
|
}
|
||||||
this.paused = success;
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resumes the stream playback
|
* Disconnects from voice
|
||||||
* @returns {boolean}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
resume() {
|
disconnect() {
|
||||||
const success = this.audioPlayer.unpause();
|
try {
|
||||||
this.paused = !success;
|
this.audioPlayer.stop(true);
|
||||||
return success;
|
this.voiceConnection.destroy();
|
||||||
}
|
} catch {} // eslint-disable-line no-empty
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play stream
|
* Stops the player
|
||||||
* @param {AudioResource<Track>} [resource=this.audioResource] The audio resource to play
|
* @returns {void}
|
||||||
* @returns {Promise<StreamDispatcher>}
|
*/
|
||||||
*/
|
end() {
|
||||||
async playStream(resource: AudioResource<Track> = this.audioResource) {
|
this.audioPlayer.stop();
|
||||||
if (!resource) throw new PlayerError("Audio resource is not available!", ErrorStatusCode.NO_AUDIO_RESOURCE);
|
}
|
||||||
if (resource.ended) return void this.emit("error", new PlayerError("Cannot play a resource that has already ended.") as unknown as AudioPlayerError);
|
|
||||||
if (!this.audioResource) this.audioResource = resource;
|
|
||||||
if (this.voiceConnection.state.status !== VoiceConnectionStatus.Ready) {
|
|
||||||
try {
|
|
||||||
await entersState(this.voiceConnection, VoiceConnectionStatus.Ready, this.connectionTimeout);
|
|
||||||
} catch (err) {
|
|
||||||
return void this.emit("error", err as AudioPlayerError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
/**
|
||||||
this.audioPlayer.play(resource);
|
* Pauses the stream playback
|
||||||
} catch (e) {
|
* @param {boolean} [interpolateSilence=false] If true, the player will play 5 packets of silence after pausing to prevent audio glitches.
|
||||||
this.emit("error", e as AudioPlayerError);
|
* @returns {boolean}
|
||||||
}
|
*/
|
||||||
|
pause(interpolateSilence?: boolean) {
|
||||||
|
const success = this.audioPlayer.pause(interpolateSilence);
|
||||||
|
this.paused = success;
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
/**
|
||||||
}
|
* Resumes the stream playback
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
resume() {
|
||||||
|
const success = this.audioPlayer.unpause();
|
||||||
|
this.paused = !success;
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets playback volume
|
* Play stream
|
||||||
* @param {number} value The volume amount
|
* @param {AudioResource<Track>} [resource=this.audioResource] The audio resource to play
|
||||||
* @returns {boolean}
|
* @returns {Promise<StreamDispatcher>}
|
||||||
*/
|
*/
|
||||||
setVolume(value: number) {
|
async playStream(resource: AudioResource<Track> = this.audioResource) {
|
||||||
if (!this.audioResource?.volume || isNaN(value) || value < 0 || value > Infinity) return false;
|
if (!resource) throw new PlayerError("Audio resource is not available!", ErrorStatusCode.NO_AUDIO_RESOURCE);
|
||||||
|
if (resource.ended) return void this.emit("error", new PlayerError("Cannot play a resource that has already ended.") as unknown as AudioPlayerError);
|
||||||
|
if (!this.audioResource) this.audioResource = resource;
|
||||||
|
if (this.voiceConnection.state.status !== VoiceConnectionStatus.Ready) {
|
||||||
|
try {
|
||||||
|
await entersState(this.voiceConnection, VoiceConnectionStatus.Ready, this.connectionTimeout);
|
||||||
|
} catch (err) {
|
||||||
|
return void this.emit("error", err as AudioPlayerError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.audioResource.volume.setVolumeLogarithmic(value / 100);
|
try {
|
||||||
return true;
|
this.audioPlayer.play(resource);
|
||||||
}
|
} catch (e) {
|
||||||
|
this.emit("error", e as AudioPlayerError);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
return this;
|
||||||
* The current volume
|
}
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
get volume() {
|
|
||||||
if (!this.audioResource?.volume) return 100;
|
|
||||||
const currentVol = this.audioResource.volume.volume;
|
|
||||||
return Math.round(Math.pow(currentVol, 1 / 1.660964) * 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The playback time
|
* Sets playback volume
|
||||||
* @type {number}
|
* @param {number} value The volume amount
|
||||||
*/
|
* @returns {boolean}
|
||||||
get streamTime() {
|
*/
|
||||||
if (!this.audioResource) return 0;
|
setVolume(value: number) {
|
||||||
return this.audioResource.playbackDuration;
|
if (!this.audioResource?.volume || isNaN(value) || value < 0 || value > Infinity) return false;
|
||||||
}
|
|
||||||
|
this.audioResource.volume.setVolumeLogarithmic(value / 100);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current volume
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
get volume() {
|
||||||
|
if (!this.audioResource?.volume) return 100;
|
||||||
|
const currentVol = this.audioResource.volume.volume;
|
||||||
|
return Math.round(Math.pow(currentVol, 1 / 1.660964) * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The playback time
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
get streamTime() {
|
||||||
|
if (!this.audioResource) return 0;
|
||||||
|
return this.audioResource.playbackDuration;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { StreamDispatcher as StreamDispatcher };
|
export { StreamDispatcher as StreamDispatcher };
|
||||||
|
|
|
@ -3,80 +3,80 @@ import { DiscordGatewayAdapterCreator, joinVoiceChannel, VoiceConnection } from
|
||||||
import { StreamDispatcher } from "./StreamDispatcher";
|
import { StreamDispatcher } from "./StreamDispatcher";
|
||||||
|
|
||||||
class VoiceUtils {
|
class VoiceUtils {
|
||||||
public cache: Collection<Snowflake, StreamDispatcher>;
|
public cache: Collection<Snowflake, StreamDispatcher>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The voice utils
|
* The voice utils
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
/**
|
/**
|
||||||
* The cache where voice utils stores stream managers
|
* The cache where voice utils stores stream managers
|
||||||
* @type {Collection<Snowflake, StreamDispatcher>}
|
* @type {Collection<Snowflake, StreamDispatcher>}
|
||||||
*/
|
*/
|
||||||
this.cache = new Collection<Snowflake, StreamDispatcher>();
|
this.cache = new Collection<Snowflake, StreamDispatcher>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Joins a voice channel, creating basic stream dispatch manager
|
* Joins a voice channel, creating basic stream dispatch manager
|
||||||
* @param {StageChannel|VoiceChannel} channel The voice channel
|
* @param {StageChannel|VoiceChannel} channel The voice channel
|
||||||
* @param {object} [options] Join options
|
* @param {object} [options] Join options
|
||||||
* @returns {Promise<StreamDispatcher>}
|
* @returns {Promise<StreamDispatcher>}
|
||||||
*/
|
*/
|
||||||
public async connect(
|
public async connect(
|
||||||
channel: VoiceChannel | StageChannel,
|
channel: VoiceChannel | StageChannel,
|
||||||
options?: {
|
options?: {
|
||||||
deaf?: boolean;
|
deaf?: boolean;
|
||||||
maxTime?: number;
|
maxTime?: number;
|
||||||
}
|
}
|
||||||
): Promise<StreamDispatcher> {
|
): Promise<StreamDispatcher> {
|
||||||
const conn = await this.join(channel, options);
|
const conn = await this.join(channel, options);
|
||||||
const sub = new StreamDispatcher(conn, channel, options.maxTime);
|
const sub = new StreamDispatcher(conn, channel, options.maxTime);
|
||||||
this.cache.set(channel.guild.id, sub);
|
this.cache.set(channel.guild.id, sub);
|
||||||
return sub;
|
return sub;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Joins a voice channel
|
* Joins a voice channel
|
||||||
* @param {StageChannel|VoiceChannel} [channel] The voice/stage channel to join
|
* @param {StageChannel|VoiceChannel} [channel] The voice/stage channel to join
|
||||||
* @param {object} [options] Join options
|
* @param {object} [options] Join options
|
||||||
* @returns {VoiceConnection}
|
* @returns {VoiceConnection}
|
||||||
*/
|
*/
|
||||||
public async join(
|
public async join(
|
||||||
channel: VoiceChannel | StageChannel,
|
channel: VoiceChannel | StageChannel,
|
||||||
options?: {
|
options?: {
|
||||||
deaf?: boolean;
|
deaf?: boolean;
|
||||||
maxTime?: number;
|
maxTime?: number;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const conn = joinVoiceChannel({
|
const conn = joinVoiceChannel({
|
||||||
guildId: channel.guild.id,
|
guildId: channel.guild.id,
|
||||||
channelId: channel.id,
|
channelId: channel.id,
|
||||||
adapterCreator: channel.guild.voiceAdapterCreator as unknown as DiscordGatewayAdapterCreator,
|
adapterCreator: channel.guild.voiceAdapterCreator as unknown as DiscordGatewayAdapterCreator,
|
||||||
selfDeaf: Boolean(options.deaf)
|
selfDeaf: Boolean(options.deaf)
|
||||||
});
|
});
|
||||||
|
|
||||||
return conn;
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disconnects voice connection
|
* Disconnects voice connection
|
||||||
* @param {VoiceConnection} connection The voice connection
|
* @param {VoiceConnection} connection The voice connection
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
public disconnect(connection: VoiceConnection | StreamDispatcher) {
|
public disconnect(connection: VoiceConnection | StreamDispatcher) {
|
||||||
if (connection instanceof StreamDispatcher) return connection.voiceConnection.destroy();
|
if (connection instanceof StreamDispatcher) return connection.voiceConnection.destroy();
|
||||||
return connection.destroy();
|
return connection.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns Discord Player voice connection
|
* Returns Discord Player voice connection
|
||||||
* @param {Snowflake} guild The guild id
|
* @param {Snowflake} guild The guild id
|
||||||
* @returns {StreamDispatcher}
|
* @returns {StreamDispatcher}
|
||||||
*/
|
*/
|
||||||
public getConnection(guild: Snowflake) {
|
public getConnection(guild: Snowflake) {
|
||||||
return this.cache.get(guild);
|
return this.cache.get(guild);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { VoiceUtils };
|
export { VoiceUtils };
|
||||||
|
|
|
@ -3,142 +3,142 @@
|
||||||
import { Transform, TransformOptions } from "stream";
|
import { Transform, TransformOptions } from "stream";
|
||||||
|
|
||||||
export interface VolumeTransformerOptions extends TransformOptions {
|
export interface VolumeTransformerOptions extends TransformOptions {
|
||||||
type?: "s16le" | "s16be" | "s32le" | "s32be";
|
type?: "s16le" | "s16be" | "s32le" | "s32be";
|
||||||
smoothness?: number;
|
smoothness?: number;
|
||||||
volume?: number;
|
volume?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VolumeTransformer extends Transform {
|
export class VolumeTransformer extends Transform {
|
||||||
private _bits: number;
|
private _bits: number;
|
||||||
private _smoothing: number;
|
private _smoothing: number;
|
||||||
private _bytes: number;
|
private _bytes: number;
|
||||||
private _extremum: number;
|
private _extremum: number;
|
||||||
private _chunk: Buffer;
|
private _chunk: Buffer;
|
||||||
public volume: number;
|
public volume: number;
|
||||||
private _targetVolume: number;
|
private _targetVolume: number;
|
||||||
public type: "s16le" | "s32le" | "s16be" | "s32be";
|
public type: "s16le" | "s32le" | "s16be" | "s32be";
|
||||||
constructor(options: VolumeTransformerOptions = {}) {
|
constructor(options: VolumeTransformerOptions = {}) {
|
||||||
super(options);
|
super(options);
|
||||||
switch (options.type) {
|
switch (options.type) {
|
||||||
case "s16le":
|
case "s16le":
|
||||||
this._readInt = (buffer, index) => buffer.readInt16LE(index);
|
this._readInt = (buffer, index) => buffer.readInt16LE(index);
|
||||||
this._writeInt = (buffer, int, index) => buffer.writeInt16LE(int, index);
|
this._writeInt = (buffer, int, index) => buffer.writeInt16LE(int, index);
|
||||||
this._bits = 16;
|
this._bits = 16;
|
||||||
break;
|
break;
|
||||||
case "s16be":
|
case "s16be":
|
||||||
this._readInt = (buffer, index) => buffer.readInt16BE(index);
|
this._readInt = (buffer, index) => buffer.readInt16BE(index);
|
||||||
this._writeInt = (buffer, int, index) => buffer.writeInt16BE(int, index);
|
this._writeInt = (buffer, int, index) => buffer.writeInt16BE(int, index);
|
||||||
this._bits = 16;
|
this._bits = 16;
|
||||||
break;
|
break;
|
||||||
case "s32le":
|
case "s32le":
|
||||||
this._readInt = (buffer, index) => buffer.readInt32LE(index);
|
this._readInt = (buffer, index) => buffer.readInt32LE(index);
|
||||||
this._writeInt = (buffer, int, index) => buffer.writeInt32LE(int, index);
|
this._writeInt = (buffer, int, index) => buffer.writeInt32LE(int, index);
|
||||||
this._bits = 32;
|
this._bits = 32;
|
||||||
break;
|
break;
|
||||||
case "s32be":
|
case "s32be":
|
||||||
this._readInt = (buffer, index) => buffer.readInt32BE(index);
|
this._readInt = (buffer, index) => buffer.readInt32BE(index);
|
||||||
this._writeInt = (buffer, int, index) => buffer.writeInt32BE(int, index);
|
this._writeInt = (buffer, int, index) => buffer.writeInt32BE(int, index);
|
||||||
this._bits = 32;
|
this._bits = 32;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error("VolumeTransformer type should be one of s16le, s16be, s32le, s32be");
|
throw new Error("VolumeTransformer type should be one of s16le, s16be, s32le, s32be");
|
||||||
}
|
}
|
||||||
this.type = options.type;
|
this.type = options.type;
|
||||||
this._bytes = this._bits / 8;
|
this._bytes = this._bits / 8;
|
||||||
this._extremum = Math.pow(2, this._bits - 1);
|
this._extremum = Math.pow(2, this._bits - 1);
|
||||||
this.volume = Number.isNaN(options.volume) ? 1 : Number(options.volume);
|
this.volume = Number.isNaN(options.volume) ? 1 : Number(options.volume);
|
||||||
if (!Number.isFinite(this.volume)) this.volume = 1;
|
if (!Number.isFinite(this.volume)) this.volume = 1;
|
||||||
this._targetVolume = this.volume;
|
this._targetVolume = this.volume;
|
||||||
this._chunk = Buffer.alloc(0);
|
this._chunk = Buffer.alloc(0);
|
||||||
this._smoothing = options.smoothness || 0;
|
this._smoothing = options.smoothness || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
_readInt(buffer: Buffer, index: number) {
|
_readInt(buffer: Buffer, index: number) {
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
_writeInt(buffer: Buffer, int: number, index: number) {
|
_writeInt(buffer: Buffer, int: number, index: number) {
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
_applySmoothness() {
|
_applySmoothness() {
|
||||||
if (this.volume < this._targetVolume) {
|
if (this.volume < this._targetVolume) {
|
||||||
this.volume = this.volume + this._smoothing >= this._targetVolume ? this._targetVolume : this.volume + this._smoothing;
|
this.volume = this.volume + this._smoothing >= this._targetVolume ? this._targetVolume : this.volume + this._smoothing;
|
||||||
} else if (this.volume > this._targetVolume) {
|
} else if (this.volume > this._targetVolume) {
|
||||||
this.volume = this.volume - this._smoothing <= this._targetVolume ? this._targetVolume : this.volume - this._smoothing;
|
this.volume = this.volume - this._smoothing <= this._targetVolume ? this._targetVolume : this.volume - this._smoothing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_transform(chunk: Buffer, encoding: BufferEncoding, done: () => unknown) {
|
_transform(chunk: Buffer, encoding: BufferEncoding, done: () => unknown) {
|
||||||
if (this.smoothingEnabled() && this.volume !== this._targetVolume) this._applySmoothness();
|
if (this.smoothingEnabled() && this.volume !== this._targetVolume) this._applySmoothness();
|
||||||
|
|
||||||
if (this.volume === 1) {
|
if (this.volume === 1) {
|
||||||
this.push(chunk);
|
this.push(chunk);
|
||||||
return done();
|
return done();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { _bytes, _extremum } = this;
|
const { _bytes, _extremum } = this;
|
||||||
|
|
||||||
chunk = this._chunk = Buffer.concat([this._chunk, chunk]);
|
chunk = this._chunk = Buffer.concat([this._chunk, chunk]);
|
||||||
if (chunk.length < _bytes) return done();
|
if (chunk.length < _bytes) return done();
|
||||||
|
|
||||||
const complete = Math.floor(chunk.length / _bytes) * _bytes;
|
const complete = Math.floor(chunk.length / _bytes) * _bytes;
|
||||||
|
|
||||||
for (let i = 0; i < complete; i += _bytes) {
|
for (let i = 0; i < complete; i += _bytes) {
|
||||||
const int = Math.min(_extremum - 1, Math.max(-_extremum, Math.floor(this.volume * this._readInt(chunk, i))));
|
const int = Math.min(_extremum - 1, Math.max(-_extremum, Math.floor(this.volume * this._readInt(chunk, i))));
|
||||||
this._writeInt(chunk, int, i);
|
this._writeInt(chunk, int, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._chunk = chunk.slice(complete);
|
this._chunk = chunk.slice(complete);
|
||||||
this.push(chunk.slice(0, complete));
|
this.push(chunk.slice(0, complete));
|
||||||
return done();
|
return done();
|
||||||
}
|
}
|
||||||
|
|
||||||
_destroy(err: Error, cb: (error: Error) => void) {
|
_destroy(err: Error, cb: (error: Error) => void) {
|
||||||
super._destroy(err, cb);
|
super._destroy(err, cb);
|
||||||
this._chunk = null;
|
this._chunk = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
setVolume(volume: number) {
|
setVolume(volume: number) {
|
||||||
if (Number.isNaN(volume)) volume = 1;
|
if (Number.isNaN(volume)) volume = 1;
|
||||||
if (typeof volume !== "number") volume = Number(volume);
|
if (typeof volume !== "number") volume = Number(volume);
|
||||||
if (!Number.isFinite(volume)) volume = volume < 0 ? 0 : 1;
|
if (!Number.isFinite(volume)) volume = volume < 0 ? 0 : 1;
|
||||||
this._targetVolume = volume;
|
this._targetVolume = volume;
|
||||||
if (this._smoothing <= 0) this.volume = volume;
|
if (this._smoothing <= 0) this.volume = volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
setVolumeDecibels(db: number) {
|
setVolumeDecibels(db: number) {
|
||||||
this.setVolume(Math.pow(10, db / 20));
|
this.setVolume(Math.pow(10, db / 20));
|
||||||
}
|
}
|
||||||
|
|
||||||
setVolumeLogarithmic(value: number) {
|
setVolumeLogarithmic(value: number) {
|
||||||
this.setVolume(Math.pow(value, 1.660964));
|
this.setVolume(Math.pow(value, 1.660964));
|
||||||
}
|
}
|
||||||
|
|
||||||
get volumeDecibels() {
|
get volumeDecibels() {
|
||||||
return Math.log10(this.volume) * 20;
|
return Math.log10(this.volume) * 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
get volumeLogarithmic() {
|
get volumeLogarithmic() {
|
||||||
return Math.pow(this.volume, 1 / 1.660964);
|
return Math.pow(this.volume, 1 / 1.660964);
|
||||||
}
|
}
|
||||||
|
|
||||||
get smoothness() {
|
get smoothness() {
|
||||||
return this._smoothing;
|
return this._smoothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSmoothness(smoothness: number) {
|
setSmoothness(smoothness: number) {
|
||||||
this._smoothing = smoothness;
|
this._smoothing = smoothness;
|
||||||
}
|
}
|
||||||
|
|
||||||
smoothingEnabled() {
|
smoothingEnabled() {
|
||||||
return Number.isFinite(this._smoothing) && this._smoothing > 0;
|
return Number.isFinite(this._smoothing) && this._smoothing > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasSmoothness() {
|
get hasSmoothness() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get hasSmoothing() {
|
static get hasSmoothing() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { VolumeTransformer as VolumeTransformerMock } from "./VoiceInterface/VolumeTransformer";
|
import { VolumeTransformer as VolumeTransformerMock } from "./VoiceInterface/VolumeTransformer";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const mod = require("prism-media") as typeof import("prism-media") & { VolumeTransformer: typeof VolumeTransformerMock };
|
const mod = require("prism-media") as typeof import("prism-media") & { VolumeTransformer: typeof VolumeTransformerMock };
|
||||||
|
|
||||||
if (typeof mod.VolumeTransformer.hasSmoothing !== "boolean") {
|
if (typeof mod.VolumeTransformer.hasSmoothing !== "boolean") {
|
||||||
Reflect.set(mod, "VolumeTransformer", VolumeTransformerMock);
|
Reflect.set(mod, "VolumeTransformer", VolumeTransformerMock);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
/* do nothing */
|
/* do nothing */
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,48 +8,48 @@ import { StreamDispatcher } from "../VoiceInterface/StreamDispatcher";
|
||||||
export type FiltersName = keyof QueueFilters;
|
export type FiltersName = keyof QueueFilters;
|
||||||
|
|
||||||
export interface PlayerSearchResult {
|
export interface PlayerSearchResult {
|
||||||
playlist: Playlist | null;
|
playlist: Playlist | null;
|
||||||
tracks: Track[];
|
tracks: Track[];
|
||||||
searched?: boolean;
|
searched?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {AudioFilters} QueueFilters
|
* @typedef {AudioFilters} QueueFilters
|
||||||
*/
|
*/
|
||||||
export interface QueueFilters {
|
export interface QueueFilters {
|
||||||
bassboost_low?: boolean;
|
bassboost_low?: boolean;
|
||||||
bassboost?: boolean;
|
bassboost?: boolean;
|
||||||
bassboost_high?: boolean;
|
bassboost_high?: boolean;
|
||||||
"8D"?: boolean;
|
"8D"?: boolean;
|
||||||
vaporwave?: boolean;
|
vaporwave?: boolean;
|
||||||
nightcore?: boolean;
|
nightcore?: boolean;
|
||||||
phaser?: boolean;
|
phaser?: boolean;
|
||||||
tremolo?: boolean;
|
tremolo?: boolean;
|
||||||
vibrato?: boolean;
|
vibrato?: boolean;
|
||||||
reverse?: boolean;
|
reverse?: boolean;
|
||||||
treble?: boolean;
|
treble?: boolean;
|
||||||
normalizer?: boolean;
|
normalizer?: boolean;
|
||||||
normalizer2?: boolean;
|
normalizer2?: boolean;
|
||||||
surrounding?: boolean;
|
surrounding?: boolean;
|
||||||
pulsator?: boolean;
|
pulsator?: boolean;
|
||||||
subboost?: boolean;
|
subboost?: boolean;
|
||||||
karaoke?: boolean;
|
karaoke?: boolean;
|
||||||
flanger?: boolean;
|
flanger?: boolean;
|
||||||
gate?: boolean;
|
gate?: boolean;
|
||||||
haas?: boolean;
|
haas?: boolean;
|
||||||
mcompand?: boolean;
|
mcompand?: boolean;
|
||||||
mono?: boolean;
|
mono?: boolean;
|
||||||
mstlr?: boolean;
|
mstlr?: boolean;
|
||||||
mstrr?: boolean;
|
mstrr?: boolean;
|
||||||
compressor?: boolean;
|
compressor?: boolean;
|
||||||
expander?: boolean;
|
expander?: boolean;
|
||||||
softlimiter?: boolean;
|
softlimiter?: boolean;
|
||||||
chorus?: boolean;
|
chorus?: boolean;
|
||||||
chorus2d?: boolean;
|
chorus2d?: boolean;
|
||||||
chorus3d?: boolean;
|
chorus3d?: boolean;
|
||||||
fadein?: boolean;
|
fadein?: boolean;
|
||||||
dim?: boolean;
|
dim?: boolean;
|
||||||
earrape?: boolean;
|
earrape?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,19 +79,19 @@ export type TrackSource = "soundcloud" | "youtube" | "spotify" | "arbitrary";
|
||||||
* @property {any} [raw] The raw data
|
* @property {any} [raw] The raw data
|
||||||
*/
|
*/
|
||||||
export interface RawTrackData {
|
export interface RawTrackData {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
author: string;
|
author: string;
|
||||||
url: string;
|
url: string;
|
||||||
thumbnail: string;
|
thumbnail: string;
|
||||||
duration: string;
|
duration: string;
|
||||||
views: number;
|
views: number;
|
||||||
requestedBy: User;
|
requestedBy: User;
|
||||||
playlist?: Playlist;
|
playlist?: Playlist;
|
||||||
source?: TrackSource;
|
source?: TrackSource;
|
||||||
engine?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
engine?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
live?: boolean;
|
live?: boolean;
|
||||||
raw?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
raw?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -102,10 +102,10 @@ export interface RawTrackData {
|
||||||
* @property {number} seconds Time in seconds
|
* @property {number} seconds Time in seconds
|
||||||
*/
|
*/
|
||||||
export interface TimeData {
|
export interface TimeData {
|
||||||
days: number;
|
days: number;
|
||||||
hours: number;
|
hours: number;
|
||||||
minutes: number;
|
minutes: number;
|
||||||
seconds: number;
|
seconds: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -117,11 +117,11 @@ export interface TimeData {
|
||||||
* @property {string} [indicator] The indicator
|
* @property {string} [indicator] The indicator
|
||||||
*/
|
*/
|
||||||
export interface PlayerProgressbarOptions {
|
export interface PlayerProgressbarOptions {
|
||||||
timecodes?: boolean;
|
timecodes?: boolean;
|
||||||
length?: number;
|
length?: number;
|
||||||
line?: string;
|
line?: string;
|
||||||
indicator?: string;
|
indicator?: string;
|
||||||
queue?: boolean;
|
queue?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -130,7 +130,7 @@ export interface PlayerProgressbarOptions {
|
||||||
* @property {boolean} [leaveOnStop=true] If it should leave on stop
|
* @property {boolean} [leaveOnStop=true] If it should leave on stop
|
||||||
* @property {boolean} [leaveOnEmpty=true] If it should leave on empty
|
* @property {boolean} [leaveOnEmpty=true] If it should leave on empty
|
||||||
* @property {number} [leaveOnEmptyCooldown=1000] The cooldown in ms
|
* @property {number} [leaveOnEmptyCooldown=1000] The cooldown in ms
|
||||||
* @property {boolean} [autoSelfDeaf=true] If it should set the bot in deaf mode
|
* @property {boolean} [autoSelfDeaf=false] If it should set the bot in deaf mode
|
||||||
* @property {number} [initialVolume=100] The initial player volume
|
* @property {number} [initialVolume=100] The initial player volume
|
||||||
* @property {number} [bufferingTimeout=3000] Buffering timeout for the stream
|
* @property {number} [bufferingTimeout=3000] Buffering timeout for the stream
|
||||||
* @property {boolean} [disableVolume=false] If player should disable inline volume
|
* @property {boolean} [disableVolume=false] If player should disable inline volume
|
||||||
|
@ -139,16 +139,16 @@ export interface PlayerProgressbarOptions {
|
||||||
* @property {Function} [onBeforeCreateStream] Runs before creating stream
|
* @property {Function} [onBeforeCreateStream] Runs before creating stream
|
||||||
*/
|
*/
|
||||||
export interface PlayerOptions {
|
export interface PlayerOptions {
|
||||||
leaveOnEnd?: boolean;
|
leaveOnEnd?: boolean;
|
||||||
leaveOnStop?: boolean;
|
leaveOnStop?: boolean;
|
||||||
leaveOnEmpty?: boolean;
|
leaveOnEmpty?: boolean;
|
||||||
leaveOnEmptyCooldown?: number;
|
leaveOnEmptyCooldown?: number;
|
||||||
autoSelfDeaf?: boolean;
|
autoSelfDeaf?: boolean;
|
||||||
initialVolume?: number;
|
initialVolume?: number;
|
||||||
bufferingTimeout?: number;
|
bufferingTimeout?: number;
|
||||||
disableVolume?: boolean;
|
disableVolume?: boolean;
|
||||||
volumeSmoothness?: number;
|
volumeSmoothness?: number;
|
||||||
onBeforeCreateStream?: (track: Track, source: TrackSource, queue: Queue) => Promise<Readable>;
|
onBeforeCreateStream?: (track: Track, source: TrackSource, queue: Queue) => Promise<Readable>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -182,32 +182,32 @@ export interface PlayerOptions {
|
||||||
* @property {TrackSource} [source="arbitrary"] The source
|
* @property {TrackSource} [source="arbitrary"] The source
|
||||||
*/
|
*/
|
||||||
export interface ExtractorModelData {
|
export interface ExtractorModelData {
|
||||||
playlist?: {
|
playlist?: {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
thumbnail: string;
|
thumbnail: string;
|
||||||
type: "album" | "playlist";
|
type: "album" | "playlist";
|
||||||
source: TrackSource;
|
source: TrackSource;
|
||||||
author: {
|
author: {
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
};
|
};
|
||||||
id: string;
|
id: string;
|
||||||
url: string;
|
url: string;
|
||||||
rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
};
|
};
|
||||||
data: {
|
data: {
|
||||||
title: string;
|
title: string;
|
||||||
duration: number;
|
duration: number;
|
||||||
thumbnail: string;
|
thumbnail: string;
|
||||||
engine: string | Readable | Duplex;
|
engine: string | Readable | Duplex;
|
||||||
views: number;
|
views: number;
|
||||||
author: string;
|
author: string;
|
||||||
description: string;
|
description: string;
|
||||||
url: string;
|
url: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
source?: TrackSource;
|
source?: TrackSource;
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -232,22 +232,22 @@ export interface ExtractorModelData {
|
||||||
* @typedef {number} QueryType
|
* @typedef {number} QueryType
|
||||||
*/
|
*/
|
||||||
export enum QueryType {
|
export enum QueryType {
|
||||||
AUTO,
|
AUTO,
|
||||||
YOUTUBE,
|
YOUTUBE,
|
||||||
YOUTUBE_PLAYLIST,
|
YOUTUBE_PLAYLIST,
|
||||||
SOUNDCLOUD_TRACK,
|
SOUNDCLOUD_TRACK,
|
||||||
SOUNDCLOUD_PLAYLIST,
|
SOUNDCLOUD_PLAYLIST,
|
||||||
SOUNDCLOUD,
|
SOUNDCLOUD,
|
||||||
SPOTIFY_SONG,
|
SPOTIFY_SONG,
|
||||||
SPOTIFY_ALBUM,
|
SPOTIFY_ALBUM,
|
||||||
SPOTIFY_PLAYLIST,
|
SPOTIFY_PLAYLIST,
|
||||||
FACEBOOK,
|
FACEBOOK,
|
||||||
VIMEO,
|
VIMEO,
|
||||||
ARBITRARY,
|
ARBITRARY,
|
||||||
REVERBNATION,
|
REVERBNATION,
|
||||||
YOUTUBE_SEARCH,
|
YOUTUBE_SEARCH,
|
||||||
YOUTUBE_VIDEO,
|
YOUTUBE_VIDEO,
|
||||||
SOUNDCLOUD_SEARCH
|
SOUNDCLOUD_SEARCH
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -327,17 +327,17 @@ export enum QueryType {
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
export interface PlayerEvents {
|
export interface PlayerEvents {
|
||||||
botDisconnect: (queue: Queue) => any;
|
botDisconnect: (queue: Queue) => any;
|
||||||
channelEmpty: (queue: Queue) => any;
|
channelEmpty: (queue: Queue) => any;
|
||||||
connectionCreate: (queue: Queue, connection: StreamDispatcher) => any;
|
connectionCreate: (queue: Queue, connection: StreamDispatcher) => any;
|
||||||
debug: (queue: Queue, message: string) => any;
|
debug: (queue: Queue, message: string) => any;
|
||||||
error: (queue: Queue, error: Error) => any;
|
error: (queue: Queue, error: Error) => any;
|
||||||
connectionError: (queue: Queue, error: Error) => any;
|
connectionError: (queue: Queue, error: Error) => any;
|
||||||
queueEnd: (queue: Queue) => any;
|
queueEnd: (queue: Queue) => any;
|
||||||
trackAdd: (queue: Queue, track: Track) => any;
|
trackAdd: (queue: Queue, track: Track) => any;
|
||||||
tracksAdd: (queue: Queue, track: Track[]) => any;
|
tracksAdd: (queue: Queue, track: Track[]) => any;
|
||||||
trackStart: (queue: Queue, track: Track) => any;
|
trackStart: (queue: Queue, track: Track) => any;
|
||||||
trackEnd: (queue: Queue, track: Track) => any;
|
trackEnd: (queue: Queue, track: Track) => any;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||||
|
@ -350,10 +350,10 @@ export interface PlayerEvents {
|
||||||
* @property {boolean} [immediate=false] If it should start playing the provided track immediately
|
* @property {boolean} [immediate=false] If it should start playing the provided track immediately
|
||||||
*/
|
*/
|
||||||
export interface PlayOptions {
|
export interface PlayOptions {
|
||||||
filtersUpdate?: boolean;
|
filtersUpdate?: boolean;
|
||||||
encoderArgs?: string[];
|
encoderArgs?: string[];
|
||||||
seek?: number;
|
seek?: number;
|
||||||
immediate?: boolean;
|
immediate?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -363,9 +363,9 @@ export interface PlayOptions {
|
||||||
* @property {boolean} [blockExtractor=false] If it should block custom extractors
|
* @property {boolean} [blockExtractor=false] If it should block custom extractors
|
||||||
*/
|
*/
|
||||||
export interface SearchOptions {
|
export interface SearchOptions {
|
||||||
requestedBy: UserResolvable;
|
requestedBy: UserResolvable;
|
||||||
searchEngine?: QueryType | string;
|
searchEngine?: QueryType | string;
|
||||||
blockExtractor?: boolean;
|
blockExtractor?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -377,10 +377,10 @@ export interface SearchOptions {
|
||||||
* @typedef {number} QueueRepeatMode
|
* @typedef {number} QueueRepeatMode
|
||||||
*/
|
*/
|
||||||
export enum QueueRepeatMode {
|
export enum QueueRepeatMode {
|
||||||
OFF = 0,
|
OFF = 0,
|
||||||
TRACK = 1,
|
TRACK = 1,
|
||||||
QUEUE = 2,
|
QUEUE = 2,
|
||||||
AUTOPLAY = 3
|
AUTOPLAY = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -399,19 +399,19 @@ export enum QueueRepeatMode {
|
||||||
* @property {any} [rawPlaylist] The raw playlist data
|
* @property {any} [rawPlaylist] The raw playlist data
|
||||||
*/
|
*/
|
||||||
export interface PlaylistInitData {
|
export interface PlaylistInitData {
|
||||||
tracks: Track[];
|
tracks: Track[];
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
thumbnail: string;
|
thumbnail: string;
|
||||||
type: "album" | "playlist";
|
type: "album" | "playlist";
|
||||||
source: TrackSource;
|
source: TrackSource;
|
||||||
author: {
|
author: {
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
};
|
};
|
||||||
id: string;
|
id: string;
|
||||||
url: string;
|
url: string;
|
||||||
rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -428,17 +428,17 @@ export interface PlaylistInitData {
|
||||||
* @property {PlaylistJSON} [playlist] The playlist info (if any)
|
* @property {PlaylistJSON} [playlist] The playlist info (if any)
|
||||||
*/
|
*/
|
||||||
export interface TrackJSON {
|
export interface TrackJSON {
|
||||||
id: Snowflake;
|
id: Snowflake;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
author: string;
|
author: string;
|
||||||
url: string;
|
url: string;
|
||||||
thumbnail: string;
|
thumbnail: string;
|
||||||
duration: string;
|
duration: string;
|
||||||
durationMS: number;
|
durationMS: number;
|
||||||
views: number;
|
views: number;
|
||||||
requestedBy: Snowflake;
|
requestedBy: Snowflake;
|
||||||
playlist?: PlaylistJSON;
|
playlist?: PlaylistJSON;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -456,18 +456,18 @@ export interface TrackJSON {
|
||||||
* @property {TrackJSON[]} tracks The tracks data (if any)
|
* @property {TrackJSON[]} tracks The tracks data (if any)
|
||||||
*/
|
*/
|
||||||
export interface PlaylistJSON {
|
export interface PlaylistJSON {
|
||||||
id: string;
|
id: string;
|
||||||
url: string;
|
url: string;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
thumbnail: string;
|
thumbnail: string;
|
||||||
type: "album" | "playlist";
|
type: "album" | "playlist";
|
||||||
source: TrackSource;
|
source: TrackSource;
|
||||||
author: {
|
author: {
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
};
|
};
|
||||||
tracks: TrackJSON[];
|
tracks: TrackJSON[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -476,6 +476,6 @@ export interface PlaylistJSON {
|
||||||
* @property {number} [connectionTimeout=20000] The voice connection timeout
|
* @property {number} [connectionTimeout=20000] The voice connection timeout
|
||||||
*/
|
*/
|
||||||
export interface PlayerInitOptions {
|
export interface PlayerInitOptions {
|
||||||
autoRegisterExtractor?: boolean;
|
autoRegisterExtractor?: boolean;
|
||||||
connectionTimeout?: number;
|
connectionTimeout?: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,104 +3,104 @@ import { FiltersName } from "../types/types";
|
||||||
const bass = (g: number) => `bass=g=${g}:f=110:w=0.3`;
|
const bass = (g: number) => `bass=g=${g}:f=110:w=0.3`;
|
||||||
|
|
||||||
class AudioFilters {
|
class AudioFilters {
|
||||||
public constructor() {
|
public constructor() {
|
||||||
return AudioFilters;
|
return AudioFilters;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static get filters(): Record<FiltersName, string> {
|
public static get filters(): Record<FiltersName, string> {
|
||||||
return {
|
return {
|
||||||
bassboost_low: bass(15),
|
bassboost_low: bass(15),
|
||||||
bassboost: bass(20),
|
bassboost: bass(20),
|
||||||
bassboost_high: bass(30),
|
bassboost_high: bass(30),
|
||||||
"8D": "apulsator=hz=0.09",
|
"8D": "apulsator=hz=0.09",
|
||||||
vaporwave: "aresample=48000,asetrate=48000*0.8",
|
vaporwave: "aresample=48000,asetrate=48000*0.8",
|
||||||
nightcore: "aresample=48000,asetrate=48000*1.25",
|
nightcore: "aresample=48000,asetrate=48000*1.25",
|
||||||
phaser: "aphaser=in_gain=0.4",
|
phaser: "aphaser=in_gain=0.4",
|
||||||
tremolo: "tremolo",
|
tremolo: "tremolo",
|
||||||
vibrato: "vibrato=f=6.5",
|
vibrato: "vibrato=f=6.5",
|
||||||
reverse: "areverse",
|
reverse: "areverse",
|
||||||
treble: "treble=g=5",
|
treble: "treble=g=5",
|
||||||
normalizer2: "dynaudnorm=g=101",
|
normalizer2: "dynaudnorm=g=101",
|
||||||
normalizer: "acompressor",
|
normalizer: "acompressor",
|
||||||
surrounding: "surround",
|
surrounding: "surround",
|
||||||
pulsator: "apulsator=hz=1",
|
pulsator: "apulsator=hz=1",
|
||||||
subboost: "asubboost",
|
subboost: "asubboost",
|
||||||
karaoke: "stereotools=mlev=0.03",
|
karaoke: "stereotools=mlev=0.03",
|
||||||
flanger: "flanger",
|
flanger: "flanger",
|
||||||
gate: "agate",
|
gate: "agate",
|
||||||
haas: "haas",
|
haas: "haas",
|
||||||
mcompand: "mcompand",
|
mcompand: "mcompand",
|
||||||
mono: "pan=mono|c0=.5*c0+.5*c1",
|
mono: "pan=mono|c0=.5*c0+.5*c1",
|
||||||
mstlr: "stereotools=mode=ms>lr",
|
mstlr: "stereotools=mode=ms>lr",
|
||||||
mstrr: "stereotools=mode=ms>rr",
|
mstrr: "stereotools=mode=ms>rr",
|
||||||
compressor: "compand=points=-80/-105|-62/-80|-15.4/-15.4|0/-12|20/-7.6",
|
compressor: "compand=points=-80/-105|-62/-80|-15.4/-15.4|0/-12|20/-7.6",
|
||||||
expander: "compand=attacks=0:points=-80/-169|-54/-80|-49.5/-64.6|-41.1/-41.1|-25.8/-15|-10.8/-4.5|0/0|20/8.3",
|
expander: "compand=attacks=0:points=-80/-169|-54/-80|-49.5/-64.6|-41.1/-41.1|-25.8/-15|-10.8/-4.5|0/0|20/8.3",
|
||||||
softlimiter: "compand=attacks=0:points=-80/-80|-12.4/-12.4|-6/-8|0/-6.8|20/-2.8",
|
softlimiter: "compand=attacks=0:points=-80/-80|-12.4/-12.4|-6/-8|0/-6.8|20/-2.8",
|
||||||
chorus: "chorus=0.7:0.9:55:0.4:0.25:2",
|
chorus: "chorus=0.7:0.9:55:0.4:0.25:2",
|
||||||
chorus2d: "chorus=0.6:0.9:50|60:0.4|0.32:0.25|0.4:2|1.3",
|
chorus2d: "chorus=0.6:0.9:50|60:0.4|0.32:0.25|0.4:2|1.3",
|
||||||
chorus3d: "chorus=0.5:0.9:50|60|40:0.4|0.32|0.3:0.25|0.4|0.3:2|2.3|1.3",
|
chorus3d: "chorus=0.5:0.9:50|60|40:0.4|0.32|0.3:0.25|0.4|0.3:2|2.3|1.3",
|
||||||
fadein: "afade=t=in:ss=0:d=10",
|
fadein: "afade=t=in:ss=0:d=10",
|
||||||
dim: `afftfilt="'real=re * (1-clip((b/nb)*b,0,1))':imag='im * (1-clip((b/nb)*b,0,1))'"`,
|
dim: `afftfilt="'real=re * (1-clip((b/nb)*b,0,1))':imag='im * (1-clip((b/nb)*b,0,1))'"`,
|
||||||
earrape: "channelsplit,sidechaingate=level_in=64"
|
earrape: "channelsplit,sidechaingate=level_in=64"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static get<K extends FiltersName>(name: K) {
|
public static get<K extends FiltersName>(name: K) {
|
||||||
return this.filters[name];
|
return this.filters[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static has<K extends FiltersName>(name: K) {
|
public static has<K extends FiltersName>(name: K) {
|
||||||
return name in this.filters;
|
return name in this.filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static *[Symbol.iterator](): IterableIterator<{ name: FiltersName; value: string }> {
|
public static *[Symbol.iterator](): IterableIterator<{ name: FiltersName; value: string }> {
|
||||||
for (const [k, v] of Object.entries(this.filters)) {
|
for (const [k, v] of Object.entries(this.filters)) {
|
||||||
yield { name: k as FiltersName, value: v as string };
|
yield { name: k as FiltersName, value: v as string };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static get names() {
|
public static get names() {
|
||||||
return Object.keys(this.filters) as FiltersName[];
|
return Object.keys(this.filters) as FiltersName[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-expect-error AudioFilters.length
|
// @ts-expect-error AudioFilters.length
|
||||||
public static get length() {
|
public static get length() {
|
||||||
return this.names.length;
|
return this.names.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static toString() {
|
public static toString() {
|
||||||
return this.names.map((m) => (this as any)[m]).join(","); // eslint-disable-line @typescript-eslint/no-explicit-any
|
return this.names.map((m) => (this as any)[m]).join(","); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create ffmpeg args from the specified filters name
|
* Create ffmpeg args from the specified filters name
|
||||||
* @param filter The filter name
|
* @param filter The filter name
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
public static create<K extends FiltersName>(filters?: K[]) {
|
public static create<K extends FiltersName>(filters?: K[]) {
|
||||||
if (!filters || !Array.isArray(filters)) return this.toString();
|
if (!filters || !Array.isArray(filters)) return this.toString();
|
||||||
return filters
|
return filters
|
||||||
.filter((predicate) => typeof predicate === "string")
|
.filter((predicate) => typeof predicate === "string")
|
||||||
.map((m) => this.get(m))
|
.map((m) => this.get(m))
|
||||||
.join(",");
|
.join(",");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines audio filter
|
* Defines audio filter
|
||||||
* @param filterName The name of the filter
|
* @param filterName The name of the filter
|
||||||
* @param value The ffmpeg args
|
* @param value The ffmpeg args
|
||||||
*/
|
*/
|
||||||
public static define(filterName: string, value: string) {
|
public static define(filterName: string, value: string) {
|
||||||
this.filters[filterName as FiltersName] = value;
|
this.filters[filterName as FiltersName] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines multiple audio filters
|
* Defines multiple audio filters
|
||||||
* @param filtersArray Array of filters containing the filter name and ffmpeg args
|
* @param filtersArray Array of filters containing the filter name and ffmpeg args
|
||||||
*/
|
*/
|
||||||
public static defineBulk(filtersArray: { name: string; value: string }[]) {
|
public static defineBulk(filtersArray: { name: string; value: string }[]) {
|
||||||
filtersArray.forEach((arr) => this.define(arr.name, arr.value));
|
filtersArray.forEach((arr) => this.define(arr.name, arr.value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AudioFilters;
|
export default AudioFilters;
|
||||||
|
|
|
@ -2,15 +2,15 @@ import { FFmpeg } from "prism-media";
|
||||||
import type { Duplex, Readable } from "stream";
|
import type { Duplex, Readable } from "stream";
|
||||||
|
|
||||||
export interface FFmpegStreamOptions {
|
export interface FFmpegStreamOptions {
|
||||||
fmt?: string;
|
fmt?: string;
|
||||||
encoderArgs?: string[];
|
encoderArgs?: string[];
|
||||||
seek?: number;
|
seek?: number;
|
||||||
skip?: boolean;
|
skip?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FFMPEG_ARGS_STRING(stream: string, fmt?: string) {
|
export function FFMPEG_ARGS_STRING(stream: string, fmt?: string) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return [
|
return [
|
||||||
"-reconnect", "1",
|
"-reconnect", "1",
|
||||||
"-reconnect_streamed", "1",
|
"-reconnect_streamed", "1",
|
||||||
"-reconnect_delay_max", "5",
|
"-reconnect_delay_max", "5",
|
||||||
|
@ -24,8 +24,8 @@ export function FFMPEG_ARGS_STRING(stream: string, fmt?: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FFMPEG_ARGS_PIPED(fmt?: string) {
|
export function FFMPEG_ARGS_PIPED(fmt?: string) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return [
|
return [
|
||||||
"-analyzeduration", "0",
|
"-analyzeduration", "0",
|
||||||
"-loglevel", "0",
|
"-loglevel", "0",
|
||||||
"-f", `${typeof fmt === "string" ? fmt : "s16le"}`,
|
"-f", `${typeof fmt === "string" ? fmt : "s16le"}`,
|
||||||
|
@ -40,20 +40,20 @@ export function FFMPEG_ARGS_PIPED(fmt?: string) {
|
||||||
* @param options FFmpeg stream options
|
* @param options FFmpeg stream options
|
||||||
*/
|
*/
|
||||||
export function createFFmpegStream(stream: Readable | Duplex | string, options?: FFmpegStreamOptions) {
|
export function createFFmpegStream(stream: Readable | Duplex | string, options?: FFmpegStreamOptions) {
|
||||||
if (options.skip && typeof stream !== "string") return stream;
|
if (options.skip && typeof stream !== "string") return stream;
|
||||||
options ??= {};
|
options ??= {};
|
||||||
const args = typeof stream === "string" ? FFMPEG_ARGS_STRING(stream, options.fmt) : FFMPEG_ARGS_PIPED(options.fmt);
|
const args = typeof stream === "string" ? FFMPEG_ARGS_STRING(stream, options.fmt) : FFMPEG_ARGS_PIPED(options.fmt);
|
||||||
|
|
||||||
if (!Number.isNaN(options.seek)) args.unshift("-ss", String(options.seek));
|
if (!Number.isNaN(options.seek)) args.unshift("-ss", String(options.seek));
|
||||||
if (Array.isArray(options.encoderArgs)) args.push(...options.encoderArgs);
|
if (Array.isArray(options.encoderArgs)) args.push(...options.encoderArgs);
|
||||||
|
|
||||||
const transcoder = new FFmpeg({ shell: false, args });
|
const transcoder = new FFmpeg({ shell: false, args });
|
||||||
transcoder.on("close", () => transcoder.destroy());
|
transcoder.on("close", () => transcoder.destroy());
|
||||||
|
|
||||||
if (typeof stream !== "string") {
|
if (typeof stream !== "string") {
|
||||||
stream.on("error", () => transcoder.destroy());
|
stream.on("error", () => transcoder.destroy());
|
||||||
stream.pipe(transcoder);
|
stream.pipe(transcoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
return transcoder;
|
return transcoder;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,49 +10,49 @@ const vimeoRegex = /(http|https)?:\/\/(www\.|player\.)?vimeo\.com\/(?:channels\/
|
||||||
const facebookRegex = /(https?:\/\/)(www\.|m\.)?(facebook|fb).com\/.*\/videos\/.*/;
|
const facebookRegex = /(https?:\/\/)(www\.|m\.)?(facebook|fb).com\/.*\/videos\/.*/;
|
||||||
const reverbnationRegex = /https:\/\/(www.)?reverbnation.com\/(.+)\/song\/(.+)/;
|
const reverbnationRegex = /https:\/\/(www.)?reverbnation.com\/(.+)\/song\/(.+)/;
|
||||||
const attachmentRegex =
|
const attachmentRegex =
|
||||||
/^(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/;
|
/^(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/;
|
||||||
// scary things above *sigh*
|
// scary things above *sigh*
|
||||||
|
|
||||||
class QueryResolver {
|
class QueryResolver {
|
||||||
/**
|
/**
|
||||||
* Query resolver
|
* Query resolver
|
||||||
*/
|
*/
|
||||||
private constructor() {} // eslint-disable-line @typescript-eslint/no-empty-function
|
private constructor() {} // eslint-disable-line @typescript-eslint/no-empty-function
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the given search query
|
* Resolves the given search query
|
||||||
* @param {string} query The query
|
* @param {string} query The query
|
||||||
* @returns {QueryType}
|
* @returns {QueryType}
|
||||||
*/
|
*/
|
||||||
static async resolve(query: string): Promise<QueryType> {
|
static async resolve(query: string): Promise<QueryType> {
|
||||||
if ((await play.so_validate(query)) === "track") return QueryType.SOUNDCLOUD_TRACK;
|
if ((await play.so_validate(query)) === "track") return QueryType.SOUNDCLOUD_TRACK;
|
||||||
if ((await play.so_validate(query)) === "playlist" || query.includes("/sets/")) return QueryType.SOUNDCLOUD_PLAYLIST;
|
if ((await play.so_validate(query)) === "playlist" || query.includes("/sets/")) return QueryType.SOUNDCLOUD_PLAYLIST;
|
||||||
if (play.yt_validate(query) === "playlist") return QueryType.YOUTUBE_PLAYLIST;
|
if (play.yt_validate(query) === "playlist") return QueryType.YOUTUBE_PLAYLIST;
|
||||||
if (play.yt_validate(query) === "video") return QueryType.YOUTUBE_VIDEO;
|
if (play.yt_validate(query) === "video") return QueryType.YOUTUBE_VIDEO;
|
||||||
if (spotifySongRegex.test(query)) return QueryType.SPOTIFY_SONG;
|
if (spotifySongRegex.test(query)) return QueryType.SPOTIFY_SONG;
|
||||||
if (spotifyPlaylistRegex.test(query)) return QueryType.SPOTIFY_PLAYLIST;
|
if (spotifyPlaylistRegex.test(query)) return QueryType.SPOTIFY_PLAYLIST;
|
||||||
if (spotifyAlbumRegex.test(query)) return QueryType.SPOTIFY_ALBUM;
|
if (spotifyAlbumRegex.test(query)) return QueryType.SPOTIFY_ALBUM;
|
||||||
if (vimeoRegex.test(query)) return QueryType.VIMEO;
|
if (vimeoRegex.test(query)) return QueryType.VIMEO;
|
||||||
if (facebookRegex.test(query)) return QueryType.FACEBOOK;
|
if (facebookRegex.test(query)) return QueryType.FACEBOOK;
|
||||||
if (reverbnationRegex.test(query)) return QueryType.REVERBNATION;
|
if (reverbnationRegex.test(query)) return QueryType.REVERBNATION;
|
||||||
if (attachmentRegex.test(query)) return QueryType.ARBITRARY;
|
if (attachmentRegex.test(query)) return QueryType.ARBITRARY;
|
||||||
|
|
||||||
return QueryType.YOUTUBE_SEARCH;
|
return QueryType.YOUTUBE_SEARCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses vimeo id from url
|
* Parses vimeo id from url
|
||||||
* @param {string} query The query
|
* @param {string} query The query
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
static async getVimeoID(query: string): Promise<string> {
|
static async getVimeoID(query: string): Promise<string> {
|
||||||
return (await QueryResolver.resolve(query)) === QueryType.VIMEO
|
return (await QueryResolver.resolve(query)) === QueryType.VIMEO
|
||||||
? query
|
? query
|
||||||
.split("/")
|
.split("/")
|
||||||
.filter((x) => !!x)
|
.filter((x) => !!x)
|
||||||
.pop()
|
.pop()
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { QueryResolver };
|
export { QueryResolver };
|
||||||
|
|
|
@ -2,116 +2,116 @@ import { StageChannel, VoiceChannel } from "discord.js";
|
||||||
import { TimeData } from "../types/types";
|
import { TimeData } from "../types/types";
|
||||||
|
|
||||||
class Util {
|
class Util {
|
||||||
/**
|
/**
|
||||||
* Utils
|
* Utils
|
||||||
*/
|
*/
|
||||||
private constructor() {} // eslint-disable-line @typescript-eslint/no-empty-function
|
private constructor() {} // eslint-disable-line @typescript-eslint/no-empty-function
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates duration string
|
* Creates duration string
|
||||||
* @param {object} durObj The duration object
|
* @param {object} durObj The duration object
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
static durationString(durObj: Record<string, number>) {
|
static durationString(durObj: Record<string, number>) {
|
||||||
return Object.values(durObj)
|
return Object.values(durObj)
|
||||||
.map((m) => (isNaN(m) ? 0 : m))
|
.map((m) => (isNaN(m) ? 0 : m))
|
||||||
.join(":");
|
.join(":");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses milliseconds to consumable time object
|
* Parses milliseconds to consumable time object
|
||||||
* @param {number} milliseconds The time in ms
|
* @param {number} milliseconds The time in ms
|
||||||
* @returns {TimeData}
|
* @returns {TimeData}
|
||||||
*/
|
*/
|
||||||
static parseMS(milliseconds: number) {
|
static parseMS(milliseconds: number) {
|
||||||
const round = milliseconds > 0 ? Math.floor : Math.ceil;
|
const round = milliseconds > 0 ? Math.floor : Math.ceil;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
days: round(milliseconds / 86400000),
|
days: round(milliseconds / 86400000),
|
||||||
hours: round(milliseconds / 3600000) % 24,
|
hours: round(milliseconds / 3600000) % 24,
|
||||||
minutes: round(milliseconds / 60000) % 60,
|
minutes: round(milliseconds / 60000) % 60,
|
||||||
seconds: round(milliseconds / 1000) % 60
|
seconds: round(milliseconds / 1000) % 60
|
||||||
} as TimeData;
|
} as TimeData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds time code
|
* Builds time code
|
||||||
* @param {TimeData} duration The duration object
|
* @param {TimeData} duration The duration object
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
static buildTimeCode(duration: TimeData) {
|
static buildTimeCode(duration: TimeData) {
|
||||||
const items = Object.keys(duration);
|
const items = Object.keys(duration);
|
||||||
const required = ["days", "hours", "minutes", "seconds"];
|
const required = ["days", "hours", "minutes", "seconds"];
|
||||||
|
|
||||||
const parsed = items.filter((x) => required.includes(x)).map((m) => duration[m as keyof TimeData]);
|
const parsed = items.filter((x) => required.includes(x)).map((m) => duration[m as keyof TimeData]);
|
||||||
const final = parsed
|
const final = parsed
|
||||||
.slice(parsed.findIndex((x) => x !== 0))
|
.slice(parsed.findIndex((x) => x !== 0))
|
||||||
.map((x) => x.toString().padStart(2, "0"))
|
.map((x) => x.toString().padStart(2, "0"))
|
||||||
.join(":");
|
.join(":");
|
||||||
|
|
||||||
return final.length <= 3 ? `0:${final.padStart(2, "0") || 0}` : final;
|
return final.length <= 3 ? `0:${final.padStart(2, "0") || 0}` : final;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Picks last item of the given array
|
* Picks last item of the given array
|
||||||
* @param {any[]} arr The array
|
* @param {any[]} arr The array
|
||||||
* @returns {any}
|
* @returns {any}
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
static last<T = any>(arr: T[]): T {
|
static last<T = any>(arr: T[]): T {
|
||||||
if (!Array.isArray(arr)) return;
|
if (!Array.isArray(arr)) return;
|
||||||
return arr[arr.length - 1];
|
return arr[arr.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the voice channel is empty
|
* Checks if the voice channel is empty
|
||||||
* @param {VoiceChannel|StageChannel} channel The voice channel
|
* @param {VoiceChannel|StageChannel} channel The voice channel
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
static isVoiceEmpty(channel: VoiceChannel | StageChannel) {
|
static isVoiceEmpty(channel: VoiceChannel | StageChannel) {
|
||||||
return channel.members.filter((member) => !member.user.bot).size === 0;
|
return channel.members.filter((member) => !member.user.bot).size === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Safer require
|
* Safer require
|
||||||
* @param {string} id Node require id
|
* @param {string} id Node require id
|
||||||
* @returns {any}
|
* @returns {any}
|
||||||
*/
|
*/
|
||||||
static require(id: string) {
|
static require(id: string) {
|
||||||
try {
|
try {
|
||||||
return require(id);
|
return require(id);
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asynchronous timeout
|
* Asynchronous timeout
|
||||||
* @param {number} time The time in ms to wait
|
* @param {number} time The time in ms to wait
|
||||||
* @returns {Promise<unknown>}
|
* @returns {Promise<unknown>}
|
||||||
*/
|
*/
|
||||||
static wait(time: number) {
|
static wait(time: number) {
|
||||||
return new Promise((r) => setTimeout(r, time).unref());
|
return new Promise((r) => setTimeout(r, time).unref());
|
||||||
}
|
}
|
||||||
|
|
||||||
static noop() {} // eslint-disable-line @typescript-eslint/no-empty-function
|
static noop() {} // eslint-disable-line @typescript-eslint/no-empty-function
|
||||||
|
|
||||||
static async getFetch() {
|
static async getFetch() {
|
||||||
if ("fetch" in globalThis) return globalThis.fetch;
|
if ("fetch" in globalThis) return globalThis.fetch;
|
||||||
for (const lib of ["undici", "node-fetch"]) {
|
for (const lib of ["undici", "node-fetch"]) {
|
||||||
try {
|
try {
|
||||||
return await import(lib).then((res) => res.fetch || res.default?.fetch || res.default);
|
return await import(lib).then((res) => res.fetch || res.default?.fetch || res.default);
|
||||||
} catch {
|
} catch {
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const res = require(lib);
|
const res = require(lib);
|
||||||
if (res) return res.fetch || res.default?.fetch || res.default;
|
if (res) return res.fetch || res.default?.fetch || res.default;
|
||||||
} catch {
|
} catch {
|
||||||
// no?
|
// no?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Util };
|
export { Util };
|
||||||
|
|
Loading…
Reference in a new issue