feat(Structures): basic setup

This commit is contained in:
Snowflake107 2021-06-11 16:17:22 +05:45
parent d5fda6cf1e
commit 1e45ebc54c
5 changed files with 371 additions and 4 deletions

View file

@ -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 };

View file

@ -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 };

View file

@ -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 };

View file

@ -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 };

View file

@ -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;
}