Modular concept

This commit is contained in:
Vladimir (unknowableshade) Eremin committed 2023-02-28 16:41:00 +03:00
1 parent d068a6fb01
commit b2bc8b928a
44 files changed
+715 -581

No files matched your search

+3
View File
@@ -16,6 +16,9 @@ pids
*.seed
*.pid.lock
# Binaries
bin/
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
-22
View File
@@ -1,22 +0,0 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/client.js";
import I18n from "../lib/i18n.js";
export async function run(client: Client, ctx: CommandInteraction) {
if (!ctx.guild || !ctx.channel) return;
const player = client.modules.music.get(ctx.guild.id, ctx.channel as TextChannel);
if (!player) return;
const amount = player.Queue.Tracks.length;
player.clear();
await ctx.reply({ embeds: [I18n.en.cleared(amount)] });
}
const data = new SlashCommandBuilder().setName("clear").setDescription("Clear the player's queue.");
export { data };
-38
View File
@@ -1,38 +0,0 @@
import { CommandInteraction, GuildMember, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/client.js";
import I18n from "../lib/i18n.js";
export async function run(client: Client, ctx: CommandInteraction) {
if (!ctx.guild || !ctx.channel || !ctx.member) return;
await ctx.deferReply();
const player =
client.modules.music.get(ctx.guild.id, ctx.channel as TextChannel) ||
client.modules.music.set(ctx.guild, ctx.channel as TextChannel);
if (!player.Connection) {
const channel = (ctx.member as GuildMember).voice.channel;
if (channel) {
player.connect(channel);
await ctx.editReply({ embeds: [I18n.en.okConnected(channel.name)] });
} else {
client.modules.music.del(ctx.guild.id);
await ctx.editReply({ embeds: [I18n.en.notConnected()] });
return undefined;
}
} else {
await ctx.editReply({ embeds: [I18n.en.wasConnected()] });
}
return player;
}
const data = new SlashCommandBuilder()
.setName("connect")
.setDescription("Connect me to a voice channel.");
export { data };
-20
View File
@@ -1,20 +0,0 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/client.js";
import I18n from "../lib/i18n.js";
export async function run(client: Client, ctx: CommandInteraction) {
if (!ctx.guild || !ctx.channel || !ctx.member) return;
await ctx.deferReply();
const player = client.modules.music.get(ctx.guild.id, ctx.channel as TextChannel);
await ctx.editReply({ embeds: [I18n.en.playerInfo(player)] });
}
const data = new SlashCommandBuilder()
.setName("info")
.setDescription("Information about the playback.");
export { data };
-33
View File
@@ -1,33 +0,0 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/client.js";
import I18n from "../lib/i18n.js";
export async function run(client: Client, ctx: CommandInteraction) {
const pos = ctx.options.get("position")?.value as number;
const query = ctx.options.get("query")?.value as string;
if (!ctx.guild || !query || !pos || !ctx.channel) return;
const player = client.modules.music.get(ctx.guild.id, ctx.channel as TextChannel);
if (!player) return;
await ctx.deferReply();
const track = await player.add(query, ctx.user, pos - 1);
await ctx.editReply({ embeds: [I18n.en.appended(track)] });
}
const data = new SlashCommandBuilder()
.setName("insert")
.setDescription("Insert a new track to specific position in the queue.")
.addStringOption((option) =>
option.setName("query").setDescription("Title or URL of the song.").setRequired(true)
)
.addIntegerOption((option) =>
option.setName("position").setDescription("Position to insert.").setRequired(true)
);
export { data };
-21
View File
@@ -1,21 +0,0 @@
import { CommandInteraction, SlashCommandBuilder } from "discord.js";
import Client from "../lib/client.js";
import I18n from "../lib/i18n.js";
export async function run(client: Client, ctx: CommandInteraction) {
const start = Date.now();
if (!client.application) {
await ctx.reply({ embeds: [I18n.en.ping(undefined)] });
return;
}
await client.application.fetch();
await ctx.reply({ embeds: [I18n.en.ping(Date.now() - start)] });
}
const data = new SlashCommandBuilder().setName("ping").setDescription("Try to ping me.");
export { data };
+4 -5
View File
@@ -4,12 +4,11 @@ import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const config = {
token:
discordToken:
process.env.DISCORD_TOKEN ??
"NzgzNDQwMjU3NjE3MzYyOTQ1.GQ0lLK.VftrGanpOtqfoKUQRkoXDCcbQqwMqBsybmwjGE",
commands: path.join(__dirname, "/commands/"),
events: path.join(__dirname, "/events/"),
binaries: path.join(__dirname, "/../bin/")
"NzgzNDQwMjU3NjE3MzYyOTQ1.Ghtbbs.kCCj2thmuKu5MsThHGSV5O0UUAFO3bwPlnQ1PM",
modulesDir: path.join(__dirname, "/modules/"),
binariesDir: path.join(__dirname, "/../bin/")
};
export default config;
-15
View File
@@ -1,15 +0,0 @@
import { Interaction } from "discord.js";
import Client from "../lib/client.js";
export async function run(client: Client, args: Interaction[]): Promise<void> {
const [interaction] = args;
if (interaction.isCommand()) {
const command = client.Commands.get(interaction.commandName);
if (command) command.run(client, interaction);
}
}
export const name = "interactionCreate";
+6 -2
View File
@@ -1,6 +1,10 @@
import { GatewayIntentBits } from "discord.js";
import config from "./config.js";
import Client from "./lib/client.js";
const client = new Client();
const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates]
});
client.login(config.token);
client.login(config.discordToken);
+54 -33
View File
@@ -1,47 +1,68 @@
import { Client as DiscordClient, GatewayIntentBits } from "discord.js";
import { existsSync, readdirSync } from "fs";
import { Client as DiscordClient, ClientOptions, Collection } from "discord.js";
import { existsSync, lstatSync, readdirSync } from "fs";
import path from "path";
import { pathToFileURL } from "url";
import config from "../config.js";
import Music from "./music.js";
import { command, event } from "./types.js";
import { Command, Event, Module } from "./types.js";
const MODULE_ENTRY = "module.js";
const MODULE_COMMANDS = "commands";
const MODULE_EVENTS = "events";
export default class Client extends DiscordClient {
private commands = new Map<string, command>();
public readonly commands = new Collection<string, Command>();
get Commands(): Map<string, command> {
return this.commands;
public readonly modules = new Collection<string, Module>();
public constructor(options: ClientOptions) {
super(options);
for (const module of readdirSync(config.modulesDir)) {
const modulePath = path.join(config.modulesDir, module);
if (!lstatSync(modulePath).isDirectory()) {
continue;
}
public readonly modules = {
music: new Music()
};
import(pathToFileURL(path.join(modulePath, MODULE_ENTRY)).toString()).then(
(data: Module) => this.loadModule(data, modulePath)
);
}
}
public constructor() {
super({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates]
private loadModule(data: Module, moduleDir: string): void {
this.modules.set(data.name, data);
const commandsDir = path.join(moduleDir, MODULE_COMMANDS);
if (existsSync(commandsDir)) {
for (const source of readdirSync(commandsDir)) {
const commandFile = path.join(commandsDir, source);
if (!lstatSync(commandFile).isFile() || !commandFile.endsWith(".js")) {
continue;
}
import(pathToFileURL(commandFile).toString()).then((command: Command) =>
this.commands.set(command.data.name, command)
);
}
}
const eventsDir = path.join(moduleDir, MODULE_EVENTS);
if (existsSync(eventsDir)) {
for (const source of readdirSync(eventsDir)) {
const eventFile = path.join(eventsDir, source);
if (!lstatSync(eventFile).isFile() || !eventFile.endsWith(".js")) {
continue;
}
import(pathToFileURL(eventFile).toString()).then((event: Event) => {
this.on(event.name, (...eventee: unknown[]) => event.execute(this, eventee));
});
if (existsSync(config.commands)) {
const files = readdirSync(config.commands).filter((file) => file.endsWith(".js"));
for (const file of files) {
import(pathToFileURL(path.join(config.commands, file)).toString()).then(
(cmd: command) => this.commands.set(cmd.data.name, cmd)
);
}
}
if (existsSync(config.events)) {
const files = readdirSync(config.events).filter((file) => file.endsWith(".js"));
for (const file of files) {
import(pathToFileURL(path.join(config.events, file)).toString()).then(
(evnt: event) => {
this.on(evnt.name, (...args: unknown[]) => evnt.run(this, args));
}
);
}
}
}
+15 -267
View File
@@ -1,273 +1,21 @@
import { ApplicationCommandData, CommandInteraction, User } from "discord.js";
import { ApplicationCommandData, CommandInteraction } from "discord.js";
import Client from "./client.js";
export type track = {
url: string;
title: string;
thumbnail: string;
duration: number;
requester: User;
};
export type command = {
data: ApplicationCommandData;
run: (arg0: Client, arg1: CommandInteraction) => Promise<void>;
};
export type event = {
export interface Module {
name: string;
run: (arg0: Client, arg1: unknown[]) => Promise<void>;
};
description?: string;
author: string;
tag: string;
controller?: unknown;
}
export type loopOption = "LOOP" | "SONG" | "NONE" | "AUTO";
export interface Command {
data: ApplicationCommandData;
execute: (client: Client, interaction: CommandInteraction) => Promise<void>;
}
export type ytdlArgs = {
help?: boolean;
version?: boolean;
update?: boolean;
ignoreErrors?: boolean;
abortOnError?: boolean;
dumpUserAgent?: boolean;
listExtractors?: boolean;
extractorDescriptions?: boolean;
forceGenericExtractor?: boolean;
defaultSearch?: string;
igonreConfig?: boolean;
configLocation?: string;
flatPlaylist?: boolean;
markWatched?: boolean;
noColor?: boolean;
proxy?: string;
socketTimeout?: number;
sourceAddress?: string;
forceIpv4?: boolean;
forceIpv6?: boolean;
geoVerificationProxy?: string;
geoBypass?: boolean;
geoBypassCountry?: string;
geoBypassIpBlock?: string;
playlistStart?: number;
playlistEnd?: number | "last";
playlistItems?: string;
matchTitle?: string;
rejectTitle?: string;
maxDownloads?: number;
minFilesize?: string;
maxFilesize?: string;
date?: string;
datebefore?: string;
dateafter?: string;
minViews?: number;
maxViews?: number;
matchFilter?: string;
noPlaylist?: boolean;
yesPlaylist?: boolean;
ageLimit?: number;
downloadArchive?: string;
includeAds?: boolean;
limitRate?: string;
retries?: number | "infinite";
skipUnavailableFragments?: boolean;
abortOnUnavailableFragment?: boolean;
keepFragments?: boolean;
bufferSize?: string;
noResizeBuffer?: boolean;
httpChunkSize?: string;
playlistReverse?: boolean;
playlistRandom?: boolean;
xattrSetFilesize?: boolean;
hlsPreferNative?: boolean;
hlsPreferFfmpeg?: boolean;
hlsUseMpegts?: boolean;
externalDownloader?: string;
externalDownloaderArgs?: string;
batchFile?: string;
id?: boolean;
output?: string;
outputNaPlaceholder?: string;
autonumberStart?: number;
restrictFilenames?: boolean;
noOverwrites?: boolean;
continue?: boolean;
noPart?: boolean;
noMtime?: boolean;
writeDescription?: boolean;
writeInfoJson?: boolean;
writeAnnotations?: boolean;
loadInfoJson?: string;
cookies?: string;
cacheDir?: string;
noCacheDir?: boolean;
rmCacheDir?: boolean;
writeThumbnail?: boolean;
writeAllThumbnails?: boolean;
listThumbnails?: boolean;
quiet?: boolean;
noWarnings?: boolean;
simulate?: boolean;
skipDownload?: boolean;
getUrl?: boolean;
getTitle?: boolean;
getId?: boolean;
getThumbnail?: boolean;
getDuration?: boolean;
getFilename?: boolean;
getFormat?: boolean;
dumpJson?: boolean;
dumpSingleJson?: boolean;
printJson?: boolean;
newline?: boolean;
noProgress?: boolean;
consoleTitle?: boolean;
verbose?: boolean;
dumpPages?: boolean;
writePages?: boolean;
printTraffic?: boolean;
callHome?: boolean;
encoding?: string;
noCheckCertificate?: boolean;
preferInsecure?: boolean;
userAgent?: string;
referer?: string;
addHeader?: string;
bidiWorkaround?: boolean;
sleepInterval?: number;
maxSleepInterval?: number;
format?: string;
allFormats?: boolean;
preferFreeFormats?: boolean;
listFormats?: boolean;
youtubeSkipDashManifest?: boolean;
mergeOutputFormat?: string;
writeSub?: boolean;
writeAutoSub?: boolean;
allSubs?: boolean;
listSubs?: boolean;
subFormat?: string;
subLang?: string;
username?: string;
password?: string;
twofactor?: string;
netrc?: boolean;
videoPassword?: string;
apMso?: string;
apUsername?: string;
apPassword?: string;
apListMso?: boolean;
extractAudio?: boolean;
audioFormat?: string;
audioQuality?: number;
recodeVideo?: string;
postprocessorArgs?: string;
keepVideo?: boolean;
noPostOverwrites?: boolean;
embedSubs?: boolean;
embedThumbnail?: boolean;
addMetadata?: boolean;
metadataFromFile?: string;
xattrs?: boolean;
fixup?: string;
preferAvconv?: boolean;
preferFfmpeg?: boolean;
ffmpegLocation?: string;
exec?: string;
convertSubs?: string;
};
type ytdlFormat = {
asr: number;
filesize: number;
format_id: string;
format_note: string;
fps: number;
height: number;
quality: number;
tbr: number;
url: string;
width: number;
ext: string;
vcodec: string;
acodec: string;
abr: number;
downloader_options: unknown;
container: string;
format: string;
protocol: string;
http_headers: unknown;
};
type ytdlThumbnail = {
height: number;
url: string;
width: number;
resolution: string;
id: string;
};
type ytdlEntry = {
uploader: string;
duration: number;
id: string;
title: string;
url: string;
};
export type ytdlResponse = {
id: string;
title: string;
formats: ytdlFormat[];
thumbnails: ytdlThumbnail[];
description: string;
upload_date: string;
uploader: string;
uploader_id: string;
uploader_url: string;
channel_id: string;
channel_url: string;
duration: number;
view_count: number;
average_rating: number;
age_limit: number;
webpage_url: string;
categories: string[];
tags: string[];
is_live: boolean;
like_count: number;
dislike_count: number;
channel: string;
track: string;
artist: string;
creator: string;
alt_title: string;
extractor: string;
webpage_url_basename: string;
extractor_key: string;
playlist: string;
playlist_index: number;
thumbnail: string;
display_id: string;
requested_subtitles: unknown;
asr: number;
filesize: number;
format_id: string;
format_note: string;
fps: number;
height: number;
quality: number;
tbr: number;
url: string;
width: number;
ext: string;
vcodec: string;
acodec: string;
abr: number;
downloader_options: { http_chunk_size: number };
container: string;
format: string;
protocol: string;
http_headers: unknown;
fulltitle: string;
_filename: string;
entries: ytdlEntry[];
};
export interface Event {
name: string;
execute: (client: Client, eventee: unknown[]) => Promise<void>;
}
+14
View File
@@ -0,0 +1,14 @@
import { CommandInteraction, SlashCommandBuilder } from "discord.js";
import Client from "../../../lib/client.js";
import i18n from "../lib/i18n.js";
async function execute(client: Client, interaction: CommandInteraction): Promise<void> {
await interaction.reply({ embeds: [i18n.modules(Array.from(client.modules.values()))] });
}
const data = new SlashCommandBuilder()
.setName("modules")
.setDescription("List of availiable modules.");
export { data, execute };
+21
View File
@@ -0,0 +1,21 @@
import { CommandInteraction, SlashCommandBuilder } from "discord.js";
import Client from "../../../lib/client.js";
import i18n from "../lib/i18n.js";
async function execute(client: Client, interaction: CommandInteraction): Promise<void> {
const start = Date.now();
if (!client.application) {
await interaction.reply({ embeds: [i18n.ping(undefined)] });
return;
}
await client.application.fetch();
await interaction.reply({ embeds: [i18n.ping(Date.now() - start)] });
}
const data = new SlashCommandBuilder().setName("ping").setDescription("Try to ping me.");
export { data, execute };
@@ -0,0 +1,19 @@
import { Interaction } from "discord.js";
import Client from "../../../lib/client.js";
async function execute(client: Client, eventee: Interaction[]): Promise<void> {
const [interaction] = eventee;
if (interaction.isCommand()) {
const command = client.commands.get(interaction.commandName);
if (command) {
command.execute(client, interaction);
}
}
}
const name = "interactionCreate";
export { execute, name };
@@ -1,9 +1,9 @@
import { generateDependencyReport } from "@discordjs/voice";
import { ActivityType } from "discord.js";
import Client from "../lib/client.js";
import Client from "../../../lib/client.js";
export async function run(client: Client): Promise<void> {
async function execute(client: Client): Promise<void> {
if (!client.user || !client.application) {
console.log("No user or application is provided!");
process.exit(1);
@@ -13,9 +13,11 @@ export async function run(client: Client): Promise<void> {
console.log(generateDependencyReport());
client.user.setActivity("over you", { type: ActivityType.Watching });
const restCommands = Array.from(client.Commands.values()).map((value) => value.data);
const restCommands = Array.from(client.commands.values()).map((value) => value.data);
client.application.commands.set(restCommands);
}
export const name = "ready";
const name = "ready";
export { execute, name };
+38
View File
@@ -0,0 +1,38 @@
import { EmbedBuilder } from "discord.js";
import { Module } from "../../../lib/types";
const i18n = {
modules: (modules: Module[]) => {
return new EmbedBuilder()
.setColor("Random")
.setTitle("Modules")
.addFields(
modules.map((value) => {
return {
name: value.name + "@" + value.tag,
value: (value.description ?? "") + " | " + value.author,
inline: true
};
})
);
},
ping: (ping?: number) => {
const title = ping ? `I have pinged ${ping} ms late.` : "There is no application.";
const comment = !ping
? "How am I speaking then?"
: ping > 250
? "I'm kinda slowpoke ngl."
: ping > 200
? "I can do better than this."
: "Dream's speed!";
return new EmbedBuilder()
.setColor("Random")
.setTitle(`**${title}**`)
.setDescription(`_**${comment}**_`);
}
};
export default i18n;
+6
View File
@@ -0,0 +1,6 @@
const name = "core";
const description = "Quetza core functionality";
const author = "unknowableshade";
const tag = "2302.1";
export { author, description, name, tag };
+25
View File
@@ -0,0 +1,25 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../../../lib/client.js";
import I18n from "../lib/i18n.js";
import { controller } from "../module.js";
async function execute(client: Client, interaction: CommandInteraction): Promise<void> {
if (!interaction.guild || !interaction.channel) {
return;
}
const player = controller.get(interaction.guild.id, interaction.channel as TextChannel);
if (!player) return;
const amount = player.Queue.Tracks.length;
player.clear();
await interaction.reply({ embeds: [I18n.en.cleared(amount)] });
}
const data = new SlashCommandBuilder().setName("clear").setDescription("Clear the player's queue.");
export { data, execute };
+41
View File
@@ -0,0 +1,41 @@
import { CommandInteraction, GuildMember, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../../../lib/client.js";
import I18n from "../lib/i18n.js";
import { controller } from "../module.js";
async function execute(client: Client, interaction: CommandInteraction) {
if (!interaction.guild || !interaction.channel || !interaction.member) {
return;
}
await interaction.deferReply();
const player =
controller.get(interaction.guild.id, interaction.channel as TextChannel) ||
controller.set(interaction.guild, interaction.channel as TextChannel);
if (!player.Connection) {
const channel = (interaction.member as GuildMember).voice.channel;
if (channel) {
player.connect(channel);
await interaction.editReply({ embeds: [I18n.en.okConnected(channel.name)] });
} else {
controller.del(interaction.guild.id);
await interaction.editReply({ embeds: [I18n.en.notConnected()] });
return undefined;
}
} else {
await interaction.editReply({ embeds: [I18n.en.wasConnected()] });
}
return player;
}
const data = new SlashCommandBuilder()
.setName("connect")
.setDescription("Connect me to a voice channel.");
export { data, execute };
@@ -1,20 +1,21 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/client.js";
import Client from "../../../lib/client.js";
import I18n from "../lib/i18n.js";
import { controller } from "../module.js";
export async function run(client: Client, ctx: CommandInteraction) {
const filter = ctx.options.get("filter")?.value as string;
async function execute(client: Client, interaction: CommandInteraction) {
const filter = interaction.options.get("filter")?.value as string;
if (!ctx.guild || !ctx.channel) return;
if (!interaction.guild || !interaction.channel) return;
const player = client.modules.music.get(ctx.guild.id, ctx.channel as TextChannel);
const player = controller.get(interaction.guild.id, interaction.channel as TextChannel);
if (!player) return;
player.setFilter(filter || undefined);
await ctx.reply({ embeds: [I18n.en.filtered(filter || undefined)] });
await interaction.reply({ embeds: [I18n.en.filtered(filter || undefined)] });
}
const data = new SlashCommandBuilder()
@@ -121,4 +122,4 @@ const data = new SlashCommandBuilder()
.setRequired(true)
);
export { data };
export { data, execute };
+23
View File
@@ -0,0 +1,23 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../../../lib/client.js";
import I18n from "../lib/i18n.js";
import { controller } from "../module.js";
async function execute(client: Client, interaction: CommandInteraction) {
if (!interaction.guild || !interaction.channel || !interaction.member) {
return;
}
await interaction.deferReply();
const player = controller.get(interaction.guild.id, interaction.channel as TextChannel);
await interaction.editReply({ embeds: [I18n.en.playerInfo(player)] });
}
const data = new SlashCommandBuilder()
.setName("info")
.setDescription("Information about the playback.");
export { data, execute };
+38
View File
@@ -0,0 +1,38 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../../../lib/client.js";
import I18n from "../lib/i18n.js";
import { controller } from "../module.js";
async function execute(client: Client, interaction: CommandInteraction) {
const pos = interaction.options.get("position")?.value as number;
const query = interaction.options.get("query")?.value as string;
if (!interaction.guild || !query || !pos || !interaction.channel) {
return;
}
const player = controller.get(interaction.guild.id, interaction.channel as TextChannel);
if (!player) {
return;
}
await interaction.deferReply();
const track = await player.add(query, interaction.user, pos - 1);
await interaction.editReply({ embeds: [I18n.en.appended(track)] });
}
const data = new SlashCommandBuilder()
.setName("insert")
.setDescription("Insert a new track to specific position in the queue.")
.addStringOption((option) =>
option.setName("query").setDescription("Title or URL of the song.").setRequired(true)
)
.addIntegerOption((option) =>
option.setName("position").setDescription("Position to insert.").setRequired(true)
);
export { data, execute };
@@ -1,23 +1,28 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/client.js";
import Client from "../../../lib/client.js";
import I18n from "../lib/i18n.js";
import { controller } from "../module.js";
export async function run(client: Client, ctx: CommandInteraction) {
const query = ctx.options.get("query")?.value as string;
async function execute(client: Client, interaction: CommandInteraction) {
const query = interaction.options.get("query")?.value as string;
if (!ctx.guild || !query || !ctx.channel) return;
if (!interaction.guild || !query || !interaction.channel) {
return;
}
const player = client.modules.music.get(ctx.guild.id, ctx.channel as TextChannel);
const player = controller.get(interaction.guild.id, interaction.channel as TextChannel);
if (!player) return;
if (!player) {
return;
}
const state =
!isNaN(+query) && isFinite(+query) && !/e/i.test(query)
? player.jump(parseInt(query) - 1)
: player.jump(query);
await ctx.reply({ embeds: [I18n.en.jumped(state)] });
await interaction.reply({ embeds: [I18n.en.jumped(state)] });
}
const data = new SlashCommandBuilder()
@@ -27,4 +32,4 @@ const data = new SlashCommandBuilder()
option.setName("query").setDescription("Position or title to jump to.").setRequired(true)
);
export { data };
export { data, execute };
@@ -1,21 +1,26 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/client.js";
import Client from "../../../lib/client.js";
import I18n from "../lib/i18n.js";
import { loopOption } from "../lib/types.js";
import { LoopOption } from "../lib/types.js";
import { controller } from "../module.js";
export async function run(client: Client, ctx: CommandInteraction) {
const query = ctx.options.get("option")?.value as string;
async function execute(client: Client, interaction: CommandInteraction) {
const query = interaction.options.get("option")?.value as string;
if (!ctx.guild || !query || !ctx.channel) return;
if (!interaction.guild || !query || !interaction.channel) {
return;
}
const player = client.modules.music.get(ctx.guild.id, ctx.channel as TextChannel);
const player = controller.get(interaction.guild.id, interaction.channel as TextChannel);
if (!player) return;
if (!player) {
return;
}
player.Queue.Loop = query as loopOption;
player.Queue.Loop = query as LoopOption;
await ctx.reply({ embeds: [I18n.en.looped(query as loopOption)] });
await interaction.reply({ embeds: [I18n.en.looped(query as LoopOption)] });
}
const data = new SlashCommandBuilder()
@@ -34,4 +39,4 @@ const data = new SlashCommandBuilder()
.setRequired(true)
);
export { data };
export { data, execute };
@@ -1,12 +1,15 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/client.js";
import Client from "../../../lib/client.js";
import I18n from "../lib/i18n.js";
import { controller } from "../module.js";
export async function run(client: Client, ctx: CommandInteraction) {
if (!ctx.guild || !ctx.channel) return;
async function execute(client: Client, ctx: CommandInteraction) {
if (!ctx.guild || !ctx.channel) {
return;
}
const player = client.modules.music.get(ctx.guild.id, ctx.channel as TextChannel);
const player = controller.get(ctx.guild.id, ctx.channel as TextChannel);
if (!player) return;
@@ -17,4 +20,4 @@ export async function run(client: Client, ctx: CommandInteraction) {
const data = new SlashCommandBuilder().setName("next").setDescription("Skip current song.");
export { data };
export { data, execute };
@@ -1,12 +1,15 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/client.js";
import Client from "../../../lib/client.js";
import I18n from "../lib/i18n.js";
import { controller } from "../module.js";
export async function run(client: Client, ctx: CommandInteraction) {
if (!ctx.guild || !ctx.channel) return;
async function execute(client: Client, ctx: CommandInteraction) {
if (!ctx.guild || !ctx.channel) {
return;
}
const player = client.modules.music.get(ctx.guild.id, ctx.channel as TextChannel);
const player = controller.get(ctx.guild.id, ctx.channel as TextChannel);
if (!player) return;
@@ -17,4 +20,4 @@ export async function run(client: Client, ctx: CommandInteraction) {
const data = new SlashCommandBuilder().setName("pause").setDescription("Toggle pause on player.");
export { data };
export { data, execute };
@@ -1,10 +1,10 @@
import { CommandInteraction, SlashCommandBuilder } from "discord.js";
import Client from "../lib/client.js";
import Client from "../../../lib/client.js";
import I18n from "../lib/i18n.js";
import { run as connect } from "./connect.js";
import { execute as connect } from "./connect.js";
export async function run(client: Client, ctx: CommandInteraction) {
async function execute(client: Client, ctx: CommandInteraction) {
const query = ctx.options.get("query")?.value as string;
const player = await connect(client, ctx);
@@ -29,4 +29,4 @@ const data = new SlashCommandBuilder()
option.setName("query").setDescription("Title or URL of the song.")
);
export { data };
export { data, execute };
@@ -10,13 +10,14 @@ import {
TextChannel
} from "discord.js";
import Client from "../lib/client.js";
import Client from "../../../lib/client.js";
import I18n from "../lib/i18n.js";
import { controller } from "../module.js";
export async function run(client: Client, ctx: CommandInteraction) {
async function execute(client: Client, ctx: CommandInteraction) {
if (!ctx.guild || !ctx.channel) return;
const player = client.modules.music.get(ctx.guild.id, ctx.channel as TextChannel);
const player = controller.get(ctx.guild.id, ctx.channel as TextChannel);
if (!player) return;
@@ -69,4 +70,4 @@ export async function run(client: Client, ctx: CommandInteraction) {
const data = new SlashCommandBuilder().setName("queue").setDescription("Print out the queue.");
export { data };
export { data, execute };
@@ -1,14 +1,15 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/client.js";
import Client from "../../../lib/client.js";
import I18n from "../lib/i18n.js";
import { controller } from "../module.js";
export async function run(client: Client, ctx: CommandInteraction) {
async function execute(client: Client, ctx: CommandInteraction) {
const query = ctx.options.get("query")?.value as string;
if (!ctx.guild || !query || !ctx.channel) return;
const player = client.modules.music.get(ctx.guild.id, ctx.channel as TextChannel);
const player = controller.get(ctx.guild.id, ctx.channel as TextChannel);
if (!player) return;
@@ -27,4 +28,4 @@ const data = new SlashCommandBuilder()
option.setName("query").setDescription("Position or title to remove.").setRequired(true)
);
export { data };
export { data, execute };
@@ -1,12 +1,13 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/client.js";
import Client from "../../../lib/client.js";
import I18n from "../lib/i18n.js";
import { controller } from "../module.js";
export async function run(client: Client, ctx: CommandInteraction) {
async function execute(client: Client, ctx: CommandInteraction) {
if (!ctx.guild || !ctx.channel) return;
const player = client.modules.music.get(ctx.guild.id, ctx.channel as TextChannel);
const player = controller.get(ctx.guild.id, ctx.channel as TextChannel);
if (!player) return;
@@ -17,4 +18,4 @@ export async function run(client: Client, ctx: CommandInteraction) {
const data = new SlashCommandBuilder().setName("reshuffle").setDescription("Shuffle the queue.");
export { data };
export { data, execute };
@@ -1,16 +1,17 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/client.js";
import Client from "../../../lib/client.js";
import I18n from "../lib/i18n.js";
import { controller } from "../module.js";
export async function run(client: Client, ctx: CommandInteraction) {
async function execute(client: Client, ctx: CommandInteraction) {
const hours = (ctx.options.get("hrs")?.value as number) || 0;
const minutes = (ctx.options.get("mins")?.value as number) || 0;
const seconds = (ctx.options.get("secs")?.value as number) || 0;
if (!ctx.guild || !ctx.channel) return;
const player = client.modules.music.get(ctx.guild.id, ctx.channel as TextChannel);
const player = controller.get(ctx.guild.id, ctx.channel as TextChannel);
if (!player) return;
@@ -30,4 +31,4 @@ const data = new SlashCommandBuilder()
.addIntegerOption((option) => option.setName("mins").setDescription("Minutes to seek."))
.addIntegerOption((option) => option.setName("secs").setDescription("Seconds to seek."));
export { data };
export { data, execute };
@@ -1,12 +1,13 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/client.js";
import Client from "../../../lib/client.js";
import I18n from "../lib/i18n.js";
import { controller } from "../module.js";
export async function run(client: Client, ctx: CommandInteraction) {
async function execute(client: Client, ctx: CommandInteraction) {
if (!ctx.guild || !ctx.channel) return;
const player = client.modules.music.get(ctx.guild.id, ctx.channel as TextChannel);
const player = controller.get(ctx.guild.id, ctx.channel as TextChannel);
if (!player) return;
@@ -17,4 +18,4 @@ export async function run(client: Client, ctx: CommandInteraction) {
const data = new SlashCommandBuilder().setName("stop").setDescription("Destroy the player.");
export { data };
export { data, execute };
@@ -1,14 +1,15 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/client.js";
import Client from "../../../lib/client.js";
import I18n from "../lib/i18n.js";
import { controller } from "../module.js";
export async function run(client: Client, ctx: CommandInteraction) {
async function execute(client: Client, ctx: CommandInteraction) {
const volume = (ctx.options.get("volume")?.value as number) || 100;
if (!ctx.guild || !ctx.channel) return;
const player = client.modules.music.get(ctx.guild.id, ctx.channel as TextChannel);
const player = controller.get(ctx.guild.id, ctx.channel as TextChannel);
if (!player) return;
@@ -24,4 +25,4 @@ const data = new SlashCommandBuilder()
option.setName("volume").setDescription("Volume in percents.").setRequired(true)
);
export { data };
export { data, execute };
@@ -1,13 +1,14 @@
import { VoiceState } from "discord.js";
import Client from "../lib/client.js";
import Client from "../../../lib/client.js";
import I18n from "../lib/i18n.js";
import { asleep } from "../lib/misc.js";
import { controller } from "../module.js";
export async function run(client: Client, args: VoiceState[]): Promise<void> {
const [oldState] = args;
async function execute(client: Client, eventee: VoiceState[]): Promise<void> {
const [oldState] = eventee;
const player = client.modules.music.get(oldState.guild.id);
const player = controller.get(oldState.guild.id);
if (!player || !oldState.channel || !client.user) return;
@@ -20,4 +21,6 @@ export async function run(client: Client, args: VoiceState[]): Promise<void> {
}
}
export const name = "voiceStateUpdate";
const name = "voiceStateUpdate";
export { execute, name };
@@ -2,12 +2,12 @@ import { execFile } from "child_process";
import path from "path";
import { promisify } from "util";
import config from "../config.js";
import { ytdlArgs, ytdlResponse } from "./types.js";
import config from "../../../config.js";
import { YtdlArgs, YtdlResponse } from "./types.js";
const execFileAsync = promisify(execFile);
function argumentsResolver(args: ytdlArgs): string[] {
function argumentsResolver(args: YtdlArgs): string[] {
const options = Object.entries(args);
return options.map(([key, value]) => {
if (typeof value === "boolean" && value) {
@@ -18,10 +18,10 @@ function argumentsResolver(args: ytdlArgs): string[] {
});
}
async function ytdlExec(query: string, args: ytdlArgs): Promise<ytdlResponse | undefined> {
async function ytdlExec(query: string, args: YtdlArgs): Promise<YtdlResponse | undefined> {
try {
const executable = await execFileAsync(
path.join(config.binaries, "/youtube-dl"),
path.join(config.binariesDir, "/youtube-dl"),
[query].concat(argumentsResolver(args))
);
return JSON.parse(executable.stdout);
@@ -33,8 +33,8 @@ async function ytdlExec(query: string, args: ytdlArgs): Promise<ytdlResponse | u
export async function fetchStream(
query: string,
args: ytdlArgs
): Promise<ytdlResponse | undefined> {
args: YtdlArgs
): Promise<YtdlResponse | undefined> {
let response = await ytdlExec(query, args);
if (response && !response.extractor.includes("youtube")) {
File renamed without changes.
@@ -4,11 +4,11 @@ import { EmbedBuilder } from "discord.js";
import { loopMoji, secToISO, statusBarGenerator, volumeMoji } from "./misc.js";
import Player from "./player.js";
import Queue from "./queue.js";
import { loopOption, track } from "./types.js";
import { LoopOption, Track } from "./types.js";
export default class I18n {
public static en = {
nowPlaying: (track: track) =>
nowPlaying: (track: Track) =>
new EmbedBuilder()
.setColor("LuminousVividPink")
.setAuthor({
@@ -44,7 +44,7 @@ export default class I18n {
? `**✅ ${filter} was applied to the player.**`
: "**✅ All filters were declined.**"
),
appended: (track?: track) =>
appended: (track?: Track) =>
!track
? new EmbedBuilder()
.setColor("Red")
@@ -64,7 +64,7 @@ export default class I18n {
state
? new EmbedBuilder().setColor("Green").setTitle("_**Perfoming jump.**_")
: new EmbedBuilder().setColor("Red").setTitle("**❗ Bad _jump_ request.**"),
looped: (query: loopOption) => {
looped: (query: LoopOption) => {
switch (query) {
case "AUTO": {
return new EmbedBuilder()
@@ -93,7 +93,7 @@ export default class I18n {
state
? new EmbedBuilder().setColor("Green").setTitle("_**▶️ Resumed.**_")
: new EmbedBuilder().setColor("Yellow").setTitle("_**⏸️ Paused.**_"),
removed: (track?: track) =>
removed: (track?: Track) =>
track
? new EmbedBuilder()
.setColor("Green")
@@ -208,7 +208,7 @@ export default class I18n {
]);
}
},
queueDesigner: (queue: Queue, page: number, resource: AudioResource<track> | undefined) => {
queueDesigner: (queue: Queue, page: number, resource: AudioResource<Track> | undefined) => {
page = Math.max(0, Math.min(Math.floor(queue.Tracks.length / 11), page));
return new EmbedBuilder()
@@ -249,22 +249,6 @@ export default class I18n {
.setTitle("🥬 I have decided to leave to conserve my energy.")
.setDescription("_**Since there was nobody in the voice channel.**_"),
reshuffle: () =>
new EmbedBuilder().setColor("Green").setTitle("**✅ Successfuly reshuffled queue.**"),
ping: (ping?: number) => {
const title = ping ? `I have pinged ${ping} ms late.` : "There is no application.";
const comment = !ping
? "How am I speaking then?"
: ping > 250
? "I'm kinda slowpoke ngl."
: ping > 200
? "I can do better than this."
: "Dream's speed!";
return new EmbedBuilder()
.setColor("Random")
.setTitle(`**${title}**`)
.setDescription(`_**${comment}**_`);
}
new EmbedBuilder().setColor("Green").setTitle("**✅ Successfuly reshuffled queue.**")
};
}
@@ -1,4 +1,4 @@
import { loopOption } from "./types.js";
import { LoopOption } from "./types.js";
export function statusBarGenerator(duration: number, length: number): string {
duration = Math.max(0, duration);
@@ -33,7 +33,7 @@ export function volumeMoji(amount: number): string {
return amount > 100 ? "🔊" : amount > 50 ? "🔉" : amount > 0 ? "🔈" : "🔇";
}
export function loopMoji(loop: loopOption): string {
export function loopMoji(loop: LoopOption): string {
return loop === "AUTO" ? "🔀" : loop === "LOOP" ? "🔁" : loop === "NONE" ? "⤵️" : "🔂";
}
File renamed without changes.
@@ -19,7 +19,7 @@ import I18n from "./i18n.js";
import { largestCommonSequence } from "./misc.js";
import Music from "./music.js";
import Queue from "./queue.js";
import { track } from "./types.js";
import { Track } from "./types.js";
export default class Player {
private music: Music;
@@ -52,8 +52,8 @@ export default class Player {
return this.queue;
}
private resource: AudioResource<track> | undefined;
public get Resource(): AudioResource<track> | undefined {
private resource: AudioResource<Track> | undefined;
public get Resource(): AudioResource<Track> | undefined {
return this.resource;
}
@@ -84,7 +84,7 @@ export default class Player {
this.player.on(AudioPlayerStatus.Idle, async () => this.resourceEndResolvable());
}
private resourceGenerator(track: track, seek?: number) {
private resourceGenerator(track: Track, seek?: number) {
const stream = ytdl(track.url, {
opusEncoded: false,
fmt: "mp3",
@@ -116,7 +116,7 @@ export default class Player {
}
}
private async messageResolvable(track: track) {
private async messageResolvable(track: Track) {
if (this.message) await this.message.delete();
this.message = await this.channel.send({
@@ -124,7 +124,7 @@ export default class Player {
});
}
private async search(query: string, requester: User): Promise<track[] | undefined> {
private async search(query: string, requester: User): Promise<Track[] | undefined> {
const response = await fetchStream(query, {
dumpSingleJson: true,
noWarnings: true,
@@ -136,7 +136,7 @@ export default class Player {
if (!response) return undefined;
const tracks: track[] = [];
const tracks: Track[] = [];
if (response.entries) {
for (const entry of response.entries) {
@@ -202,7 +202,7 @@ export default class Player {
});
}
public async add(query: string, requester: User, index?: number): Promise<track | undefined> {
public async add(query: string, requester: User, index?: number): Promise<Track | undefined> {
const tracks = await this.search(query, requester);
if (!tracks) return;
@@ -234,7 +234,7 @@ export default class Player {
return false;
}
public remove(query?: number | string): track | undefined {
public remove(query?: number | string): Track | undefined {
const oldPosition = this.queue.Position;
let newPosition = typeof query === "number" ? query : this.queue.Position;
@@ -1,8 +1,8 @@
import { loopOption, track } from "./types.js";
import { LoopOption, Track } from "./types.js";
export default class Queue {
private tracks: track[] = [];
public get Tracks(): track[] {
private tracks: Track[] = [];
public get Tracks(): Track[] {
return this.tracks;
}
private position = 0;
@@ -10,11 +10,11 @@ export default class Queue {
return this.position;
}
private loop: loopOption = "NONE";
public get Loop(): loopOption {
private loop: LoopOption = "NONE";
public get Loop(): LoopOption {
return this.loop;
}
public set Loop(x: loopOption) {
public set Loop(x: LoopOption) {
this.loop = x;
}
@@ -23,7 +23,7 @@ export default class Queue {
return this.duration;
}
public next(): track | undefined {
public next(): Track | undefined {
if (this.loop === "SONG") return this.current();
if (this.loop === "AUTO") {
@@ -59,7 +59,7 @@ export default class Queue {
this.position = 0;
}
public push(tracks: track[], index?: number): track {
public push(tracks: Track[], index?: number): Track {
if (!index || index >= this.tracks.length) {
this.tracks = this.tracks.concat(tracks);
} else {
@@ -74,7 +74,7 @@ export default class Queue {
return this.tracks[index || this.tracks.length - 1];
}
public pop(query: number): track | undefined {
public pop(query: number): Track | undefined {
if (query >= 0 && query < this.tracks.length) {
const track = this.tracks[query];
@@ -87,7 +87,7 @@ export default class Queue {
return undefined;
}
public jump(query: number): track | undefined {
public jump(query: number): Track | undefined {
if (query >= 0 && query < this.tracks.length) {
const track = this.tracks[query];
this.position = query;
@@ -97,7 +97,7 @@ export default class Queue {
return undefined;
}
public current(): track {
public current(): Track {
return this.tracks[this.position];
}
+261
View File
@@ -0,0 +1,261 @@
import { User } from "discord.js";
export interface Track {
url: string;
title: string;
thumbnail: string;
duration: number;
requester: User;
}
export type LoopOption = "LOOP" | "SONG" | "NONE" | "AUTO";
export interface YtdlArgs {
help?: boolean;
version?: boolean;
update?: boolean;
ignoreErrors?: boolean;
abortOnError?: boolean;
dumpUserAgent?: boolean;
listExtractors?: boolean;
extractorDescriptions?: boolean;
forceGenericExtractor?: boolean;
defaultSearch?: string;
igonreConfig?: boolean;
configLocation?: string;
flatPlaylist?: boolean;
markWatched?: boolean;
noColor?: boolean;
proxy?: string;
socketTimeout?: number;
sourceAddress?: string;
forceIpv4?: boolean;
forceIpv6?: boolean;
geoVerificationProxy?: string;
geoBypass?: boolean;
geoBypassCountry?: string;
geoBypassIpBlock?: string;
playlistStart?: number;
playlistEnd?: number | "last";
playlistItems?: string;
matchTitle?: string;
rejectTitle?: string;
maxDownloads?: number;
minFilesize?: string;
maxFilesize?: string;
date?: string;
datebefore?: string;
dateafter?: string;
minViews?: number;
maxViews?: number;
matchFilter?: string;
noPlaylist?: boolean;
yesPlaylist?: boolean;
ageLimit?: number;
downloadArchive?: string;
includeAds?: boolean;
limitRate?: string;
retries?: number | "infinite";
skipUnavailableFragments?: boolean;
abortOnUnavailableFragment?: boolean;
keepFragments?: boolean;
bufferSize?: string;
noResizeBuffer?: boolean;
httpChunkSize?: string;
playlistReverse?: boolean;
playlistRandom?: boolean;
xattrSetFilesize?: boolean;
hlsPreferNative?: boolean;
hlsPreferFfmpeg?: boolean;
hlsUseMpegts?: boolean;
externalDownloader?: string;
externalDownloaderArgs?: string;
batchFile?: string;
id?: boolean;
output?: string;
outputNaPlaceholder?: string;
autonumberStart?: number;
restrictFilenames?: boolean;
noOverwrites?: boolean;
continue?: boolean;
noPart?: boolean;
noMtime?: boolean;
writeDescription?: boolean;
writeInfoJson?: boolean;
writeAnnotations?: boolean;
loadInfoJson?: string;
cookies?: string;
cacheDir?: string;
noCacheDir?: boolean;
rmCacheDir?: boolean;
writeThumbnail?: boolean;
writeAllThumbnails?: boolean;
listThumbnails?: boolean;
quiet?: boolean;
noWarnings?: boolean;
simulate?: boolean;
skipDownload?: boolean;
getUrl?: boolean;
getTitle?: boolean;
getId?: boolean;
getThumbnail?: boolean;
getDuration?: boolean;
getFilename?: boolean;
getFormat?: boolean;
dumpJson?: boolean;
dumpSingleJson?: boolean;
printJson?: boolean;
newline?: boolean;
noProgress?: boolean;
consoleTitle?: boolean;
verbose?: boolean;
dumpPages?: boolean;
writePages?: boolean;
printTraffic?: boolean;
callHome?: boolean;
encoding?: string;
noCheckCertificate?: boolean;
preferInsecure?: boolean;
userAgent?: string;
referer?: string;
addHeader?: string;
bidiWorkaround?: boolean;
sleepInterval?: number;
maxSleepInterval?: number;
format?: string;
allFormats?: boolean;
preferFreeFormats?: boolean;
listFormats?: boolean;
youtubeSkipDashManifest?: boolean;
mergeOutputFormat?: string;
writeSub?: boolean;
writeAutoSub?: boolean;
allSubs?: boolean;
listSubs?: boolean;
subFormat?: string;
subLang?: string;
username?: string;
password?: string;
twofactor?: string;
netrc?: boolean;
videoPassword?: string;
apMso?: string;
apUsername?: string;
apPassword?: string;
apListMso?: boolean;
extractAudio?: boolean;
audioFormat?: string;
audioQuality?: number;
recodeVideo?: string;
postprocessorArgs?: string;
keepVideo?: boolean;
noPostOverwrites?: boolean;
embedSubs?: boolean;
embedThumbnail?: boolean;
addMetadata?: boolean;
metadataFromFile?: string;
xattrs?: boolean;
fixup?: string;
preferAvconv?: boolean;
preferFfmpeg?: boolean;
ffmpegLocation?: string;
exec?: string;
convertSubs?: string;
}
interface YtdlFormat {
asr: number;
filesize: number;
format_id: string;
format_note: string;
fps: number;
height: number;
quality: number;
tbr: number;
url: string;
width: number;
ext: string;
vcodec: string;
acodec: string;
abr: number;
downloader_options: unknown;
container: string;
format: string;
protocol: string;
http_headers: unknown;
}
interface YtdlThumbnail {
height: number;
url: string;
width: number;
resolution: string;
id: string;
}
interface YtdlEntry {
uploader: string;
duration: number;
id: string;
title: string;
url: string;
}
export interface YtdlResponse {
id: string;
title: string;
formats: YtdlFormat[];
thumbnails: YtdlThumbnail[];
description: string;
upload_date: string;
uploader: string;
uploader_id: string;
uploader_url: string;
channel_id: string;
channel_url: string;
duration: number;
view_count: number;
average_rating: number;
age_limit: number;
webpage_url: string;
categories: string[];
tags: string[];
is_live: boolean;
like_count: number;
dislike_count: number;
channel: string;
track: string;
artist: string;
creator: string;
alt_title: string;
extractor: string;
webpage_url_basename: string;
extractor_key: string;
playlist: string;
playlist_index: number;
thumbnail: string;
display_id: string;
requested_subtitles: unknown;
asr: number;
filesize: number;
format_id: string;
format_note: string;
fps: number;
height: number;
quality: number;
tbr: number;
url: string;
width: number;
ext: string;
vcodec: string;
acodec: string;
abr: number;
downloader_options: { http_chunk_size: number };
container: string;
format: string;
protocol: string;
http_headers: unknown;
fulltitle: string;
_filename: string;
entries: YtdlEntry[];
}
+10
View File
@@ -0,0 +1,10 @@
import Music from "./lib/music.js";
const name = "music";
const description = "Music module";
const author = "unknowableshade";
const tag = "2302.1";
const controller = new Music();
export { author, controller, description, name, tag };
+1 -1
View File
@@ -15,5 +15,5 @@
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
"exclude": ["node_modules", "dist", "src/commands", "src/events"]
}