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
+709 -575

No files matched your search

+3
View File
@@ -16,6 +16,9 @@ pids
*.seed *.seed
*.pid.lock *.pid.lock
# Binaries
bin/
# Compiled binary addons (https://nodejs.org/api/addons.html) # Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release 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 __dirname = path.dirname(fileURLToPath(import.meta.url));
const config = { const config = {
token: discordToken:
process.env.DISCORD_TOKEN ?? process.env.DISCORD_TOKEN ??
"NzgzNDQwMjU3NjE3MzYyOTQ1.GQ0lLK.VftrGanpOtqfoKUQRkoXDCcbQqwMqBsybmwjGE", "NzgzNDQwMjU3NjE3MzYyOTQ1.Ghtbbs.kCCj2thmuKu5MsThHGSV5O0UUAFO3bwPlnQ1PM",
commands: path.join(__dirname, "/commands/"), modulesDir: path.join(__dirname, "/modules/"),
events: path.join(__dirname, "/events/"), binariesDir: path.join(__dirname, "/../bin/")
binaries: path.join(__dirname, "/../bin/")
}; };
export default config; 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 config from "./config.js";
import Client from "./lib/client.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);
+48 -27
View File
@@ -1,47 +1,68 @@
import { Client as DiscordClient, GatewayIntentBits } from "discord.js"; import { Client as DiscordClient, ClientOptions, Collection } from "discord.js";
import { existsSync, readdirSync } from "fs"; import { existsSync, lstatSync, readdirSync } from "fs";
import path from "path"; import path from "path";
import { pathToFileURL } from "url"; import { pathToFileURL } from "url";
import config from "../config.js"; import config from "../config.js";
import Music from "./music.js"; import { Command, Event, Module } from "./types.js";
import { command, event } from "./types.js";
const MODULE_ENTRY = "module.js";
const MODULE_COMMANDS = "commands";
const MODULE_EVENTS = "events";
export default class Client extends DiscordClient { export default class Client extends DiscordClient {
private commands = new Map<string, command>(); public readonly commands = new Collection<string, Command>();
get Commands(): Map<string, command> { public readonly modules = new Collection<string, Module>();
return this.commands;
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;
}
import(pathToFileURL(path.join(modulePath, MODULE_ENTRY)).toString()).then(
(data: Module) => this.loadModule(data, modulePath)
);
}
} }
public readonly modules = { private loadModule(data: Module, moduleDir: string): void {
music: new Music() this.modules.set(data.name, data);
};
public constructor() { const commandsDir = path.join(moduleDir, MODULE_COMMANDS);
super({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates]
});
if (existsSync(config.commands)) { if (existsSync(commandsDir)) {
const files = readdirSync(config.commands).filter((file) => file.endsWith(".js")); for (const source of readdirSync(commandsDir)) {
const commandFile = path.join(commandsDir, source);
for (const file of files) { if (!lstatSync(commandFile).isFile() || !commandFile.endsWith(".js")) {
import(pathToFileURL(path.join(config.commands, file)).toString()).then( continue;
(cmd: command) => this.commands.set(cmd.data.name, cmd) }
import(pathToFileURL(commandFile).toString()).then((command: Command) =>
this.commands.set(command.data.name, command)
); );
} }
} }
if (existsSync(config.events)) { const eventsDir = path.join(moduleDir, MODULE_EVENTS);
const files = readdirSync(config.events).filter((file) => file.endsWith(".js"));
for (const file of files) { if (existsSync(eventsDir)) {
import(pathToFileURL(path.join(config.events, file)).toString()).then( for (const source of readdirSync(eventsDir)) {
(evnt: event) => { const eventFile = path.join(eventsDir, source);
this.on(evnt.name, (...args: unknown[]) => evnt.run(this, args));
} 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));
});
} }
} }
} }
+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"; import Client from "./client.js";
export type track = { export interface Module {
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 = {
name: string; 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 = { export interface Event {
help?: boolean; name: string;
version?: boolean; execute: (client: Client, eventee: unknown[]) => Promise<void>;
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[];
};
+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 { generateDependencyReport } from "@discordjs/voice";
import { ActivityType } from "discord.js"; 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) { if (!client.user || !client.application) {
console.log("No user or application is provided!"); console.log("No user or application is provided!");
process.exit(1); process.exit(1);
@@ -13,9 +13,11 @@ export async function run(client: Client): Promise<void> {
console.log(generateDependencyReport()); console.log(generateDependencyReport());
client.user.setActivity("over you", { type: ActivityType.Watching }); 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); 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 { 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 I18n from "../lib/i18n.js";
import { controller } from "../module.js";
export async function run(client: Client, ctx: CommandInteraction) { async function execute(client: Client, interaction: CommandInteraction) {
const filter = ctx.options.get("filter")?.value as string; 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; if (!player) return;
player.setFilter(filter || undefined); 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() const data = new SlashCommandBuilder()
@@ -121,4 +122,4 @@ const data = new SlashCommandBuilder()
.setRequired(true) .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 { 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 I18n from "../lib/i18n.js";
import { controller } from "../module.js";
export async function run(client: Client, ctx: CommandInteraction) { async function execute(client: Client, interaction: CommandInteraction) {
const query = ctx.options.get("query")?.value as string; 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 = const state =
!isNaN(+query) && isFinite(+query) && !/e/i.test(query) !isNaN(+query) && isFinite(+query) && !/e/i.test(query)
? player.jump(parseInt(query) - 1) ? player.jump(parseInt(query) - 1)
: player.jump(query); : player.jump(query);
await ctx.reply({ embeds: [I18n.en.jumped(state)] }); await interaction.reply({ embeds: [I18n.en.jumped(state)] });
} }
const data = new SlashCommandBuilder() const data = new SlashCommandBuilder()
@@ -27,4 +32,4 @@ const data = new SlashCommandBuilder()
option.setName("query").setDescription("Position or title to jump to.").setRequired(true) 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 { 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 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) { async function execute(client: Client, interaction: CommandInteraction) {
const query = ctx.options.get("option")?.value as string; 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() const data = new SlashCommandBuilder()
@@ -34,4 +39,4 @@ const data = new SlashCommandBuilder()
.setRequired(true) .setRequired(true)
); );
export { data }; export { data, execute };
@@ -1,12 +1,15 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js"; 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 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; 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; 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."); 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 { 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 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; 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; 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."); 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 { CommandInteraction, SlashCommandBuilder } from "discord.js";
import Client from "../lib/client.js"; import Client from "../../../lib/client.js";
import I18n from "../lib/i18n.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 query = ctx.options.get("query")?.value as string;
const player = await connect(client, ctx); const player = await connect(client, ctx);
@@ -29,4 +29,4 @@ const data = new SlashCommandBuilder()
option.setName("query").setDescription("Title or URL of the song.") option.setName("query").setDescription("Title or URL of the song.")
); );
export { data }; export { data, execute };
@@ -10,13 +10,14 @@ import {
TextChannel TextChannel
} from "discord.js"; } from "discord.js";
import Client from "../lib/client.js"; import Client from "../../../lib/client.js";
import I18n from "../lib/i18n.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; 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; 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."); 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 { 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 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; const query = ctx.options.get("query")?.value as string;
if (!ctx.guild || !query || !ctx.channel) return; 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; if (!player) return;
@@ -27,4 +28,4 @@ const data = new SlashCommandBuilder()
option.setName("query").setDescription("Position or title to remove.").setRequired(true) 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 { 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 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; 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; 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."); 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 { 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 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 hours = (ctx.options.get("hrs")?.value as number) || 0;
const minutes = (ctx.options.get("mins")?.value as number) || 0; const minutes = (ctx.options.get("mins")?.value as number) || 0;
const seconds = (ctx.options.get("secs")?.value as number) || 0; const seconds = (ctx.options.get("secs")?.value as number) || 0;
if (!ctx.guild || !ctx.channel) return; 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; if (!player) return;
@@ -30,4 +31,4 @@ const data = new SlashCommandBuilder()
.addIntegerOption((option) => option.setName("mins").setDescription("Minutes to seek.")) .addIntegerOption((option) => option.setName("mins").setDescription("Minutes to seek."))
.addIntegerOption((option) => option.setName("secs").setDescription("Seconds 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 { 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 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; 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; 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."); 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 { 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 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; const volume = (ctx.options.get("volume")?.value as number) || 100;
if (!ctx.guild || !ctx.channel) return; 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; if (!player) return;
@@ -24,4 +25,4 @@ const data = new SlashCommandBuilder()
option.setName("volume").setDescription("Volume in percents.").setRequired(true) option.setName("volume").setDescription("Volume in percents.").setRequired(true)
); );
export { data }; export { data, execute };
@@ -1,13 +1,14 @@
import { VoiceState } from "discord.js"; import { VoiceState } from "discord.js";
import Client from "../lib/client.js"; import Client from "../../../lib/client.js";
import I18n from "../lib/i18n.js"; import I18n from "../lib/i18n.js";
import { asleep } from "../lib/misc.js"; import { asleep } from "../lib/misc.js";
import { controller } from "../module.js";
export async function run(client: Client, args: VoiceState[]): Promise<void> { async function execute(client: Client, eventee: VoiceState[]): Promise<void> {
const [oldState] = args; 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; 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 path from "path";
import { promisify } from "util"; import { promisify } from "util";
import config from "../config.js"; import config from "../../../config.js";
import { ytdlArgs, ytdlResponse } from "./types.js"; import { YtdlArgs, YtdlResponse } from "./types.js";
const execFileAsync = promisify(execFile); const execFileAsync = promisify(execFile);
function argumentsResolver(args: ytdlArgs): string[] { function argumentsResolver(args: YtdlArgs): string[] {
const options = Object.entries(args); const options = Object.entries(args);
return options.map(([key, value]) => { return options.map(([key, value]) => {
if (typeof value === "boolean" && 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 { try {
const executable = await execFileAsync( const executable = await execFileAsync(
path.join(config.binaries, "/youtube-dl"), path.join(config.binariesDir, "/youtube-dl"),
[query].concat(argumentsResolver(args)) [query].concat(argumentsResolver(args))
); );
return JSON.parse(executable.stdout); return JSON.parse(executable.stdout);
@@ -33,8 +33,8 @@ async function ytdlExec(query: string, args: ytdlArgs): Promise<ytdlResponse | u
export async function fetchStream( export async function fetchStream(
query: string, query: string,
args: ytdlArgs args: YtdlArgs
): Promise<ytdlResponse | undefined> { ): Promise<YtdlResponse | undefined> {
let response = await ytdlExec(query, args); let response = await ytdlExec(query, args);
if (response && !response.extractor.includes("youtube")) { 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 { loopMoji, secToISO, statusBarGenerator, volumeMoji } from "./misc.js";
import Player from "./player.js"; import Player from "./player.js";
import Queue from "./queue.js"; import Queue from "./queue.js";
import { loopOption, track } from "./types.js"; import { LoopOption, Track } from "./types.js";
export default class I18n { export default class I18n {
public static en = { public static en = {
nowPlaying: (track: track) => nowPlaying: (track: Track) =>
new EmbedBuilder() new EmbedBuilder()
.setColor("LuminousVividPink") .setColor("LuminousVividPink")
.setAuthor({ .setAuthor({
@@ -44,7 +44,7 @@ export default class I18n {
? `**✅ ${filter} was applied to the player.**` ? `**✅ ${filter} was applied to the player.**`
: "**✅ All filters were declined.**" : "**✅ All filters were declined.**"
), ),
appended: (track?: track) => appended: (track?: Track) =>
!track !track
? new EmbedBuilder() ? new EmbedBuilder()
.setColor("Red") .setColor("Red")
@@ -64,7 +64,7 @@ export default class I18n {
state state
? new EmbedBuilder().setColor("Green").setTitle("_**Perfoming jump.**_") ? new EmbedBuilder().setColor("Green").setTitle("_**Perfoming jump.**_")
: new EmbedBuilder().setColor("Red").setTitle("**❗ Bad _jump_ request.**"), : new EmbedBuilder().setColor("Red").setTitle("**❗ Bad _jump_ request.**"),
looped: (query: loopOption) => { looped: (query: LoopOption) => {
switch (query) { switch (query) {
case "AUTO": { case "AUTO": {
return new EmbedBuilder() return new EmbedBuilder()
@@ -93,7 +93,7 @@ export default class I18n {
state state
? new EmbedBuilder().setColor("Green").setTitle("_**▶️ Resumed.**_") ? new EmbedBuilder().setColor("Green").setTitle("_**▶️ Resumed.**_")
: new EmbedBuilder().setColor("Yellow").setTitle("_**⏸️ Paused.**_"), : new EmbedBuilder().setColor("Yellow").setTitle("_**⏸️ Paused.**_"),
removed: (track?: track) => removed: (track?: Track) =>
track track
? new EmbedBuilder() ? new EmbedBuilder()
.setColor("Green") .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)); page = Math.max(0, Math.min(Math.floor(queue.Tracks.length / 11), page));
return new EmbedBuilder() return new EmbedBuilder()
@@ -249,22 +249,6 @@ export default class I18n {
.setTitle("🥬 I have decided to leave to conserve my energy.") .setTitle("🥬 I have decided to leave to conserve my energy.")
.setDescription("_**Since there was nobody in the voice channel.**_"), .setDescription("_**Since there was nobody in the voice channel.**_"),
reshuffle: () => reshuffle: () =>
new EmbedBuilder().setColor("Green").setTitle("**✅ Successfuly reshuffled queue.**"), 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}**_`);
}
}; };
} }
@@ -1,4 +1,4 @@
import { loopOption } from "./types.js"; import { LoopOption } from "./types.js";
export function statusBarGenerator(duration: number, length: number): string { export function statusBarGenerator(duration: number, length: number): string {
duration = Math.max(0, duration); duration = Math.max(0, duration);
@@ -33,7 +33,7 @@ export function volumeMoji(amount: number): string {
return amount > 100 ? "🔊" : amount > 50 ? "🔉" : amount > 0 ? "🔈" : "🔇"; 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" ? "⤵️" : "🔂"; 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 { largestCommonSequence } from "./misc.js";
import Music from "./music.js"; import Music from "./music.js";
import Queue from "./queue.js"; import Queue from "./queue.js";
import { track } from "./types.js"; import { Track } from "./types.js";
export default class Player { export default class Player {
private music: Music; private music: Music;
@@ -52,8 +52,8 @@ export default class Player {
return this.queue; return this.queue;
} }
private resource: AudioResource<track> | undefined; private resource: AudioResource<Track> | undefined;
public get Resource(): AudioResource<track> | undefined { public get Resource(): AudioResource<Track> | undefined {
return this.resource; return this.resource;
} }
@@ -84,7 +84,7 @@ export default class Player {
this.player.on(AudioPlayerStatus.Idle, async () => this.resourceEndResolvable()); 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, { const stream = ytdl(track.url, {
opusEncoded: false, opusEncoded: false,
fmt: "mp3", 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(); if (this.message) await this.message.delete();
this.message = await this.channel.send({ 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, { const response = await fetchStream(query, {
dumpSingleJson: true, dumpSingleJson: true,
noWarnings: true, noWarnings: true,
@@ -136,7 +136,7 @@ export default class Player {
if (!response) return undefined; if (!response) return undefined;
const tracks: track[] = []; const tracks: Track[] = [];
if (response.entries) { if (response.entries) {
for (const entry of 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); const tracks = await this.search(query, requester);
if (!tracks) return; if (!tracks) return;
@@ -234,7 +234,7 @@ export default class Player {
return false; return false;
} }
public remove(query?: number | string): track | undefined { public remove(query?: number | string): Track | undefined {
const oldPosition = this.queue.Position; const oldPosition = this.queue.Position;
let newPosition = typeof query === "number" ? query : 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 { export default class Queue {
private tracks: track[] = []; private tracks: Track[] = [];
public get Tracks(): track[] { public get Tracks(): Track[] {
return this.tracks; return this.tracks;
} }
private position = 0; private position = 0;
@@ -10,11 +10,11 @@ export default class Queue {
return this.position; return this.position;
} }
private loop: loopOption = "NONE"; private loop: LoopOption = "NONE";
public get Loop(): loopOption { public get Loop(): LoopOption {
return this.loop; return this.loop;
} }
public set Loop(x: loopOption) { public set Loop(x: LoopOption) {
this.loop = x; this.loop = x;
} }
@@ -23,7 +23,7 @@ export default class Queue {
return this.duration; return this.duration;
} }
public next(): track | undefined { public next(): Track | undefined {
if (this.loop === "SONG") return this.current(); if (this.loop === "SONG") return this.current();
if (this.loop === "AUTO") { if (this.loop === "AUTO") {
@@ -59,7 +59,7 @@ export default class Queue {
this.position = 0; this.position = 0;
} }
public push(tracks: track[], index?: number): track { public push(tracks: Track[], index?: number): Track {
if (!index || index >= this.tracks.length) { if (!index || index >= this.tracks.length) {
this.tracks = this.tracks.concat(tracks); this.tracks = this.tracks.concat(tracks);
} else { } else {
@@ -74,7 +74,7 @@ export default class Queue {
return this.tracks[index || this.tracks.length - 1]; 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) { if (query >= 0 && query < this.tracks.length) {
const track = this.tracks[query]; const track = this.tracks[query];
@@ -87,7 +87,7 @@ export default class Queue {
return undefined; return undefined;
} }
public jump(query: number): track | undefined { public jump(query: number): Track | undefined {
if (query >= 0 && query < this.tracks.length) { if (query >= 0 && query < this.tracks.length) {
const track = this.tracks[query]; const track = this.tracks[query];
this.position = query; this.position = query;
@@ -97,7 +97,7 @@ export default class Queue {
return undefined; return undefined;
} }
public current(): track { public current(): Track {
return this.tracks[this.position]; 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 "skipLibCheck": true
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["node_modules", "dist"] "exclude": ["node_modules", "dist", "src/commands", "src/events"]
} }