Modular concept
This commit is contained in:
1 parent
d068a6fb01
commit
b2bc8b928a
44 files changed
+715
-581
No files matched your search
@@ -16,6 +16,9 @@ pids
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Binaries
|
||||
bin/
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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
@@ -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;
|
||||
@@ -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
@@ -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
@@ -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
@@ -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>;
|
||||
}
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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;
|
||||
@@ -0,0 +1,6 @@
|
||||
const name = "core";
|
||||
const description = "Quetza core functionality";
|
||||
const author = "unknowableshade";
|
||||
const tag = "2302.1";
|
||||
|
||||
export { author, description, name, tag };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
@@ -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
@@ -15,5 +15,5 @@
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
"exclude": ["node_modules", "dist", "src/commands", "src/events"]
|
||||
}
|
||||
Reference in new issue
Block a user