feat(Structures): basic setup
This commit is contained in:
parent
d5fda6cf1e
commit
1e45ebc54c
5 changed files with 371 additions and 4 deletions
|
@ -1 +1,28 @@
|
||||||
export {};
|
import { Client, Collection, Guild, Snowflake } from "discord.js";
|
||||||
|
import { TypedEmitter as EventEmitter } from "tiny-typed-emitter";
|
||||||
|
import { Queue } from "./Structures/Queue";
|
||||||
|
import { PlayerOptions } from "./types/types";
|
||||||
|
|
||||||
|
class DiscordPlayer extends EventEmitter {
|
||||||
|
public readonly client: Client;
|
||||||
|
public readonly queues = new Collection<Snowflake, Queue>();
|
||||||
|
|
||||||
|
constructor(client: Client) {
|
||||||
|
super();
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
createQueue(guild: Guild, queueInitOptions?: PlayerOptions) {
|
||||||
|
if (this.queues.has(guild.id)) return this.queues.get(guild.id);
|
||||||
|
const queue = new Queue(this, guild, queueInitOptions);
|
||||||
|
this.queues.set(guild.id, queue);
|
||||||
|
|
||||||
|
return queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
getQueue(guild: Snowflake) {
|
||||||
|
return this.queues.get(guild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { DiscordPlayer as Player };
|
||||||
|
|
|
@ -1 +1,69 @@
|
||||||
export {};
|
import { ExtractorModelData } from "../types/types";
|
||||||
|
|
||||||
|
class ExtractorModel {
|
||||||
|
name: string;
|
||||||
|
private _raw: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model for raw Discord Player extractors
|
||||||
|
* @param {String} extractorName Name of the extractor
|
||||||
|
* @param {Object} data Extractor object
|
||||||
|
*/
|
||||||
|
constructor(extractorName: string, data: any) {
|
||||||
|
/**
|
||||||
|
* The extractor name
|
||||||
|
* @type {String}
|
||||||
|
*/
|
||||||
|
this.name = extractorName;
|
||||||
|
|
||||||
|
Object.defineProperty(this, "_raw", { value: data, configurable: false, writable: false, enumerable: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to handle requests from `Player.play()`
|
||||||
|
* @param {String} query Query to handle
|
||||||
|
* @returns {Promise<ExtractorModelData>}
|
||||||
|
*/
|
||||||
|
async handle(query: string): Promise<ExtractorModelData> {
|
||||||
|
const data = await this._raw.getInfo(query);
|
||||||
|
if (!data) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: data.title,
|
||||||
|
duration: data.duration,
|
||||||
|
thumbnail: data.thumbnail,
|
||||||
|
engine: data.engine,
|
||||||
|
views: data.views,
|
||||||
|
author: data.author,
|
||||||
|
description: data.description,
|
||||||
|
url: data.url
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method used by Discord Player to validate query with this extractor
|
||||||
|
* @param {String} query The query to validate
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
validate(query: string): boolean {
|
||||||
|
return Boolean(this._raw.validate(query));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The extractor version
|
||||||
|
* @type {String}
|
||||||
|
*/
|
||||||
|
get version(): string {
|
||||||
|
return this._raw.version ?? "0.0.0";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If player should mark this extractor as important
|
||||||
|
* @type {Boolean}
|
||||||
|
*/
|
||||||
|
get important(): boolean {
|
||||||
|
return Boolean(this._raw.important);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ExtractorModel };
|
||||||
|
|
|
@ -1 +1,55 @@
|
||||||
export {};
|
import { Guild, StageChannel, VoiceChannel } from "discord.js";
|
||||||
|
import { Player } from "../Player";
|
||||||
|
import { VoiceUtils } from "../VoiceInterface/VoiceUtils";
|
||||||
|
import { VoiceSubscription } from "../VoiceInterface/VoiceSubscription";
|
||||||
|
import Track from "./Track";
|
||||||
|
import { PlayerOptions } from "../types/types";
|
||||||
|
|
||||||
|
class Queue {
|
||||||
|
public readonly guild: Guild;
|
||||||
|
public readonly player: Player;
|
||||||
|
public voiceConnection: VoiceSubscription;
|
||||||
|
public tracks: Track[] = [];
|
||||||
|
public options: PlayerOptions;
|
||||||
|
|
||||||
|
constructor(player: Player, guild: Guild, options: PlayerOptions = {}) {
|
||||||
|
this.player = player;
|
||||||
|
this.guild = guild;
|
||||||
|
this.options = {};
|
||||||
|
|
||||||
|
Object.assign(
|
||||||
|
this.options,
|
||||||
|
{
|
||||||
|
leaveOnEnd: true,
|
||||||
|
leaveOnEndCooldown: 1000,
|
||||||
|
leaveOnStop: true,
|
||||||
|
leaveOnEmpty: true,
|
||||||
|
leaveOnEmptyCooldown: 1000,
|
||||||
|
autoSelfDeaf: true,
|
||||||
|
enableLive: false,
|
||||||
|
ytdlDownloadOptions: {},
|
||||||
|
useSafeSearch: false,
|
||||||
|
disableAutoRegister: false,
|
||||||
|
fetchBeforeQueued: false
|
||||||
|
} as PlayerOptions,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async joinVoiceChannel(channel: StageChannel | VoiceChannel) {
|
||||||
|
if (!["stage", "voice"].includes(channel.type))
|
||||||
|
throw new TypeError(`Channel type must be voice or stage, got ${channel.type}!`);
|
||||||
|
const connection = await VoiceUtils.connect(channel);
|
||||||
|
this.voiceConnection = connection;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.voiceConnection.stop();
|
||||||
|
this.voiceConnection.disconnect();
|
||||||
|
this.player.queues.delete(this.guild.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Queue };
|
||||||
|
|
|
@ -1 +1,155 @@
|
||||||
export {};
|
import { User } from "discord.js";
|
||||||
|
import { Player } from "../Player";
|
||||||
|
import { RawTrackData } from "../types/types";
|
||||||
|
import { Queue } from "./Queue";
|
||||||
|
|
||||||
|
class Track {
|
||||||
|
public player!: Player;
|
||||||
|
public title!: string;
|
||||||
|
public description!: string;
|
||||||
|
public author!: string;
|
||||||
|
public url!: string;
|
||||||
|
public thumbnail!: string;
|
||||||
|
public duration!: string;
|
||||||
|
public views!: number;
|
||||||
|
public requestedBy!: User;
|
||||||
|
public fromPlaylist!: boolean;
|
||||||
|
public raw!: RawTrackData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track constructor
|
||||||
|
* @param {Player} player The player that instantiated this Track
|
||||||
|
* @param {RawTrackData} data Track data
|
||||||
|
*/
|
||||||
|
constructor(player: Player, data: RawTrackData) {
|
||||||
|
/**
|
||||||
|
* The player that instantiated this Track
|
||||||
|
* @name Track#player
|
||||||
|
* @type {Player}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
Object.defineProperty(this, "player", { value: player, enumerable: false });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Title of this track
|
||||||
|
* @name Track#title
|
||||||
|
* @type {String}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description of this track
|
||||||
|
* @name Track#description
|
||||||
|
* @type {String}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Author of this track
|
||||||
|
* @name Track#author
|
||||||
|
* @type {String}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL of this track
|
||||||
|
* @name Track#url
|
||||||
|
* @type {String}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thumbnail of this track
|
||||||
|
* @name Track#thumbnail
|
||||||
|
* @type {String}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duration of this track
|
||||||
|
* @name Track#duration
|
||||||
|
* @type {String}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Views count of this track
|
||||||
|
* @name Track#views
|
||||||
|
* @type {Number}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Person who requested this track
|
||||||
|
* @name Track#requestedBy
|
||||||
|
* @type {DiscordUser}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this track belongs to playlist
|
||||||
|
* @name Track#fromPlaylist
|
||||||
|
* @type {Boolean}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raw track data
|
||||||
|
* @name Track#raw
|
||||||
|
* @type {RawTrackData}
|
||||||
|
*/
|
||||||
|
|
||||||
|
void this._patch(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _patch(data: RawTrackData) {
|
||||||
|
this.title = data.title ?? "";
|
||||||
|
this.author = data.author ?? "";
|
||||||
|
this.url = data.url ?? "";
|
||||||
|
this.thumbnail = data.thumbnail ?? "";
|
||||||
|
this.duration = data.duration ?? "";
|
||||||
|
this.views = data.views ?? 0;
|
||||||
|
this.requestedBy = data.requestedBy;
|
||||||
|
this.fromPlaylist = Boolean(data.fromPlaylist);
|
||||||
|
|
||||||
|
// raw
|
||||||
|
Object.defineProperty(this, "raw", { get: () => data, enumerable: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The queue in which this track is located
|
||||||
|
* @type {Queue}
|
||||||
|
*/
|
||||||
|
get queue(): Queue {
|
||||||
|
return this.player.queues.find((q) => q.tracks.includes(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The track duration in millisecond
|
||||||
|
* @type {Number}
|
||||||
|
*/
|
||||||
|
get durationMS(): number {
|
||||||
|
const times = (n: number, t: number) => {
|
||||||
|
let tn = 1;
|
||||||
|
for (let i = 0; i < t; i++) tn *= n;
|
||||||
|
return t <= 0 ? 1000 : tn * 1000;
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.duration
|
||||||
|
.split(":")
|
||||||
|
.reverse()
|
||||||
|
.map((m, i) => parseInt(m) * times(60, i))
|
||||||
|
.reduce((a, c) => a + c, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns source of this track
|
||||||
|
* @type {TrackSource}
|
||||||
|
*/
|
||||||
|
get source() {
|
||||||
|
return this.raw.source ?? "arbitrary";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String representation of this track
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
toString(): string {
|
||||||
|
return `${this.title} by ${this.author}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Track;
|
||||||
|
|
||||||
|
export { Track };
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
import { User } from "discord.js";
|
||||||
|
import { downloadOptions } from "ytdl-core";
|
||||||
|
import { Readable, Duplex } from "stream";
|
||||||
|
|
||||||
export type FiltersName = keyof QueueFilters;
|
export type FiltersName = keyof QueueFilters;
|
||||||
|
|
||||||
export type QueueFilters = {
|
export type QueueFilters = {
|
||||||
|
@ -30,3 +34,63 @@ export type QueueFilters = {
|
||||||
chorus3d?: boolean;
|
chorus3d?: boolean;
|
||||||
fadein?: boolean;
|
fadein?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TrackSource = "soundcloud" | "youtube" | "spotify" | "arbitrary";
|
||||||
|
|
||||||
|
export interface RawTrackData {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
author: string;
|
||||||
|
url: string;
|
||||||
|
thumbnail: string;
|
||||||
|
duration: string;
|
||||||
|
views: number;
|
||||||
|
requestedBy: User;
|
||||||
|
fromPlaylist: boolean;
|
||||||
|
source?: TrackSource;
|
||||||
|
engine?: any;
|
||||||
|
live?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TimeData {
|
||||||
|
days: number;
|
||||||
|
hours: number;
|
||||||
|
minutes: number;
|
||||||
|
seconds: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlayerProgressbarOptions {
|
||||||
|
timecodes?: boolean;
|
||||||
|
queue?: boolean;
|
||||||
|
length?: number;
|
||||||
|
line?: string;
|
||||||
|
indicator?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlayerOptions {
|
||||||
|
leaveOnEnd?: boolean;
|
||||||
|
leaveOnEndCooldown?: number;
|
||||||
|
leaveOnStop?: boolean;
|
||||||
|
leaveOnEmpty?: boolean;
|
||||||
|
leaveOnEmptyCooldown?: number;
|
||||||
|
autoSelfDeaf?: boolean;
|
||||||
|
enableLive?: boolean;
|
||||||
|
ytdlDownloadOptions?: downloadOptions;
|
||||||
|
useSafeSearch?: boolean;
|
||||||
|
disableAutoRegister?: boolean;
|
||||||
|
fetchBeforeQueued?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtractorModelData {
|
||||||
|
title: string;
|
||||||
|
duration: number;
|
||||||
|
thumbnail: string;
|
||||||
|
engine: string | Readable | Duplex;
|
||||||
|
views: number;
|
||||||
|
author: string;
|
||||||
|
description: string;
|
||||||
|
url: string;
|
||||||
|
version?: string;
|
||||||
|
important?: boolean;
|
||||||
|
source?: TrackSource;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue