Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
076d9a71cb | ||
|
|
b562d74a5c | ||
|
|
dea1a5564e | ||
|
|
c178fb21f3 | ||
|
|
b2bc8b928a | ||
|
|
8cff02e081 |
No files matched your search
@@ -14,6 +14,7 @@
|
|||||||
"plugins": ["@typescript-eslint", "import", "simple-import-sort"],
|
"plugins": ["@typescript-eslint", "import", "simple-import-sort"],
|
||||||
"ignorePatterns": ["**/node_modules/**", "/dist/**", "/tests/**"],
|
"ignorePatterns": ["**/node_modules/**", "/dist/**", "/tests/**"],
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"curly": ["error", "all"],
|
||||||
"simple-import-sort/imports": "error",
|
"simple-import-sort/imports": "error",
|
||||||
"simple-import-sort/exports": "error"
|
"simple-import-sort/exports": "error"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
+1
-4
@@ -12,10 +12,7 @@ RUN mkdir bin
|
|||||||
RUN curl -L https://yt-dl.org/downloads/latest/youtube-dl -o /usr/src/app/bin/youtube-dl
|
RUN curl -L https://yt-dl.org/downloads/latest/youtube-dl -o /usr/src/app/bin/youtube-dl
|
||||||
RUN chmod a+rx /usr/src/app/bin/youtube-dl
|
RUN chmod a+rx /usr/src/app/bin/youtube-dl
|
||||||
|
|
||||||
COPY package*.json ./
|
COPY . .
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
|
|
||||||
COPY . .
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
CMD [ "npm", "start" ]
|
CMD [ "npm", "start" ]
|
||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "quetza-bot",
|
"name": "quetza-bot",
|
||||||
"version": "2302.1.0",
|
"version": "2302.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "quetza-bot",
|
"name": "quetza-bot",
|
||||||
"version": "2302.1.0",
|
"version": "2302.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordjs/opus": "^0.8.0",
|
"@discordjs/opus": "^0.8.0",
|
||||||
|
|||||||
+10
-10
@@ -5,7 +5,7 @@
|
|||||||
"npm": "*"
|
"npm": "*"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "2302.1",
|
"version": "2303.1",
|
||||||
"description": "Discord multifunctional bot",
|
"description": "Discord multifunctional bot",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "unknowableshade",
|
"name": "unknowableshade",
|
||||||
@@ -26,25 +26,25 @@
|
|||||||
"start": "node dist/index.js",
|
"start": "node dist/index.js",
|
||||||
"build": "npx tsc --build",
|
"build": "npx tsc --build",
|
||||||
"clean": "npx tsc --build --clean",
|
"clean": "npx tsc --build --clean",
|
||||||
"lint": "npx prettier --write . && npx eslint --fix .",
|
"lint": "npx eslint --fix . && npx prettier --write .",
|
||||||
"prestart": "npm run build"
|
"postinstall": "npm run build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"discord.js": "^14.3.0",
|
|
||||||
"@discordjs/opus": "^0.8.0",
|
"@discordjs/opus": "^0.8.0",
|
||||||
"@discordjs/voice": "^0.11.0",
|
"@discordjs/voice": "^0.11.0",
|
||||||
"ytdl-core": "^4.11.1",
|
|
||||||
"discord-ytdl-core": "^5.0.4",
|
"discord-ytdl-core": "^5.0.4",
|
||||||
"tweetnacl": "^1.0.3"
|
"discord.js": "^14.3.0",
|
||||||
|
"tweetnacl": "^1.0.3",
|
||||||
|
"ytdl-core": "^4.11.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^4.8.2",
|
|
||||||
"@types/node": "^18.7.13",
|
"@types/node": "^18.7.13",
|
||||||
"eslint": "^8.22.0",
|
|
||||||
"eslint-config-prettier": "^8.6.0",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^5.35.1",
|
"@typescript-eslint/eslint-plugin": "^5.35.1",
|
||||||
"@typescript-eslint/parser": "^5.35.1",
|
"@typescript-eslint/parser": "^5.35.1",
|
||||||
|
"eslint": "^8.22.0",
|
||||||
|
"eslint-config-prettier": "^8.6.0",
|
||||||
"eslint-plugin-import": "^2.27.5",
|
"eslint-plugin-import": "^2.27.5",
|
||||||
"eslint-plugin-simple-import-sort": "^10.0.0"
|
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||||
|
"typescript": "^4.8.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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,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) return;
|
|
||||||
|
|
||||||
const player = client.modules.music.get(ctx.guild.id, ctx.channel as TextChannel);
|
|
||||||
|
|
||||||
if (!player) return;
|
|
||||||
|
|
||||||
player.skip();
|
|
||||||
|
|
||||||
await ctx.reply({ embeds: [I18n.en.skipped()] });
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = new SlashCommandBuilder().setName("next").setDescription("Skip current song.");
|
|
||||||
|
|
||||||
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) return;
|
|
||||||
|
|
||||||
const player = client.modules.music.get(ctx.guild.id, ctx.channel as TextChannel);
|
|
||||||
|
|
||||||
if (!player) return;
|
|
||||||
|
|
||||||
const state = player.pause();
|
|
||||||
|
|
||||||
await ctx.reply({ embeds: [I18n.en.paused(state)] });
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = new SlashCommandBuilder().setName("pause").setDescription("Toggle pause on player.");
|
|
||||||
|
|
||||||
export { data };
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { CommandInteraction, SlashCommandBuilder } from "discord.js";
|
|
||||||
|
|
||||||
import Client from "../lib/client.js";
|
|
||||||
import I18n from "../lib/i18n.js";
|
|
||||||
import { run as connect } from "./connect.js";
|
|
||||||
|
|
||||||
export async function run(client: Client, ctx: CommandInteraction) {
|
|
||||||
const query = ctx.options.get("query")?.value as string;
|
|
||||||
|
|
||||||
const player = await connect(client, ctx);
|
|
||||||
|
|
||||||
if (!player) return;
|
|
||||||
|
|
||||||
if (query) {
|
|
||||||
const track = await player.add(query, ctx.user);
|
|
||||||
|
|
||||||
await ctx.editReply({ embeds: [I18n.en.appended(track)] });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!player.Resource) {
|
|
||||||
player.play();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = new SlashCommandBuilder()
|
|
||||||
.setName("play")
|
|
||||||
.setDescription("Launch player and add songs to the queue.")
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option.setName("query").setDescription("Title or URL of the song.")
|
|
||||||
);
|
|
||||||
|
|
||||||
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) return;
|
|
||||||
|
|
||||||
const player = client.modules.music.get(ctx.guild.id, ctx.channel as TextChannel);
|
|
||||||
|
|
||||||
if (!player) return;
|
|
||||||
|
|
||||||
player.shuffle();
|
|
||||||
|
|
||||||
await ctx.reply({ embeds: [I18n.en.reshuffle()] });
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = new SlashCommandBuilder().setName("reshuffle").setDescription("Shuffle the queue.");
|
|
||||||
|
|
||||||
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 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);
|
|
||||||
|
|
||||||
if (!player) return;
|
|
||||||
|
|
||||||
const time = hours * 3600 + minutes * 60 + seconds;
|
|
||||||
|
|
||||||
player.seek(time * 1000);
|
|
||||||
|
|
||||||
await ctx.reply({
|
|
||||||
embeds: [I18n.en.fastForwarded(time)]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = new SlashCommandBuilder()
|
|
||||||
.setName("seek")
|
|
||||||
.setDescription("Fast-forward by the time.")
|
|
||||||
.addIntegerOption((option) => option.setName("hrs").setDescription("Hours to seek."))
|
|
||||||
.addIntegerOption((option) => option.setName("mins").setDescription("Minutes to seek."))
|
|
||||||
.addIntegerOption((option) => option.setName("secs").setDescription("Seconds to seek."));
|
|
||||||
|
|
||||||
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) return;
|
|
||||||
|
|
||||||
const player = client.modules.music.get(ctx.guild.id, ctx.channel as TextChannel);
|
|
||||||
|
|
||||||
if (!player) return;
|
|
||||||
|
|
||||||
player.destroy();
|
|
||||||
|
|
||||||
await ctx.reply({ embeds: [I18n.en.destroyed()] });
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = new SlashCommandBuilder().setName("stop").setDescription("Destroy the player.");
|
|
||||||
|
|
||||||
export { data };
|
|
||||||
@@ -1,27 +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 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);
|
|
||||||
|
|
||||||
if (!player) return;
|
|
||||||
|
|
||||||
player.Volume = volume;
|
|
||||||
|
|
||||||
await ctx.reply({ embeds: [I18n.en.volumeSet(player.Volume)] });
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = new SlashCommandBuilder()
|
|
||||||
.setName("volume")
|
|
||||||
.setDescription("Change player's volume amount.")
|
|
||||||
.addIntegerOption((option) =>
|
|
||||||
option.setName("volume").setDescription("Volume in percents.").setRequired(true)
|
|
||||||
);
|
|
||||||
|
|
||||||
export { data };
|
|
||||||
+4
-7
@@ -1,15 +1,12 @@
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
const ROOT = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
token:
|
discordToken: process.env.DISCORD_TOKEN,
|
||||||
process.env.DISCORD_TOKEN ??
|
modulesDir: path.join(ROOT, "/modules/"),
|
||||||
"NzgzNDQwMjU3NjE3MzYyOTQ1.GQ0lLK.VftrGanpOtqfoKUQRkoXDCcbQqwMqBsybmwjGE",
|
binariesDir: path.join(ROOT, "/../bin/")
|
||||||
commands: path.join(__dirname, "/commands/"),
|
|
||||||
events: path.join(__dirname, "/events/"),
|
|
||||||
binaries: path.join(__dirname, "/../bin/")
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
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";
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { VoiceState } from "discord.js";
|
|
||||||
|
|
||||||
import Client from "../lib/client.js";
|
|
||||||
import I18n from "../lib/i18n.js";
|
|
||||||
import { asleep } from "../lib/misc.js";
|
|
||||||
|
|
||||||
export async function run(client: Client, args: VoiceState[]): Promise<void> {
|
|
||||||
const [oldState] = args;
|
|
||||||
|
|
||||||
const player = client.modules.music.get(oldState.guild.id);
|
|
||||||
|
|
||||||
if (!player || !oldState.channel || !client.user) return;
|
|
||||||
|
|
||||||
if (oldState.channel.members.filter((value) => value.user !== client.user).size === 0) {
|
|
||||||
await asleep(30000);
|
|
||||||
if (oldState.channel.members.filter((value) => value.user !== client.user).size === 0) {
|
|
||||||
await player.Channel.send({ embeds: [I18n.en.fullyAlone()] });
|
|
||||||
player.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const name = "voiceStateUpdate";
|
|
||||||
+6
-2
@@ -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);
|
||||||
+67
-30
@@ -1,48 +1,85 @@
|
|||||||
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, ModuleMetadata } 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, ModuleMetadata>();
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly modules = {
|
import(pathToFileURL(path.join(modulePath, MODULE_ENTRY)).toString()).then(
|
||||||
music: new Music()
|
(data: Module) => this.loadModule(data)
|
||||||
};
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public constructor() {
|
private loadModule(data: Module): void {
|
||||||
super({
|
const commands: string[] = [];
|
||||||
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates]
|
const events: string[] = [];
|
||||||
|
|
||||||
|
function importModule(dir: string, importThen: (data: Command & Event) => void) {
|
||||||
|
if (existsSync(dir)) {
|
||||||
|
for (const source of readdirSync(dir)) {
|
||||||
|
const file = path.join(dir, source);
|
||||||
|
|
||||||
|
if (!lstatSync(file).isFile() || !file.endsWith(".js")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
import(pathToFileURL(file).toString()).then(importThen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
importModule(path.join(data.rootDir, MODULE_COMMANDS), (command: Command) => {
|
||||||
|
this.commands.set(command.data.name, command)
|
||||||
|
commands.push(command.data.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existsSync(config.commands)) {
|
importModule(path.join(data.rootDir, MODULE_EVENTS), (event: Event) => {
|
||||||
const files = readdirSync(config.commands).filter((file) => file.endsWith(".js"));
|
this.on(event.name, (...eventee: unknown[]) => event.execute(this, eventee));
|
||||||
|
events.push(event.name);
|
||||||
|
});
|
||||||
|
|
||||||
for (const file of files) {
|
this.modules.set(data.name, {
|
||||||
import(pathToFileURL(path.join(config.commands, file)).toString()).then(
|
...data,
|
||||||
(cmd: command) => this.commands.set(cmd.data.name, cmd)
|
commands,
|
||||||
);
|
events
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existsSync(config.events)) {
|
public generateApplicationStatus(): string {
|
||||||
const files = readdirSync(config.events).filter((file) => file.endsWith(".js"));
|
const modulesStatus = this.modules.map((module) => {
|
||||||
|
const tag = `✓ ${module.name}@${module.tag} module is loaded.`
|
||||||
|
const commands = module.commands.map((name) => " - " + name).join("\n");
|
||||||
|
const events = module.events.map((name) => " - " + name).join("\n");
|
||||||
|
|
||||||
for (const file of files) {
|
return [`${tag}\n`, "commands:", `${commands}`, "events:", `${events}`].join("\n");
|
||||||
import(pathToFileURL(path.join(config.events, file)).toString()).then(
|
}).join("\n" + "-".repeat(50) + "\n");
|
||||||
(evnt: event) => {
|
|
||||||
this.on(evnt.name, (...args: unknown[]) => evnt.run(this, args));
|
const clientStatus =
|
||||||
}
|
this.user && this.application
|
||||||
);
|
? `✓ ${this.application.id} successfully logged in as ${this.user.tag}.`
|
||||||
}
|
: "✖ Application is not availiable.";
|
||||||
}
|
|
||||||
|
return ["-".repeat(50), modulesStatus, "-".repeat(50), clientStatus].join("\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
export default class Filter {
|
|
||||||
private filters: Record<string, [boolean, string]> = {
|
|
||||||
bassboost_low: [false, "bass=g=15:f=110:w=0.3"],
|
|
||||||
bassboost: [false, "bass=g=20:f=110:w=0.3"],
|
|
||||||
bassboost_high: [false, "bass=g=30:f=110:w=0.3"],
|
|
||||||
"8D": [false, "apulsator=hz=0.09"],
|
|
||||||
vaporwave: [false, "aresample=48000,asetrate=48000*0.75"],
|
|
||||||
nightcore: [false, "aresample=48000,asetrate=48000*1.25"],
|
|
||||||
phaser: [false, "aphaser=in_gain=0.4"],
|
|
||||||
tremolo: [false, "tremolo"],
|
|
||||||
vibrato: [false, "vibrato=f=6.5"],
|
|
||||||
reverse: [false, "areverse"],
|
|
||||||
treble: [false, "treble=g=5"],
|
|
||||||
normalizer: [false, "dynaudnorm=g=101"],
|
|
||||||
normalizer2: [false, "acompressor"],
|
|
||||||
surrounding: [false, "surround"],
|
|
||||||
pulsator: [false, "apulsator=hz=1"],
|
|
||||||
subboost: [false, "asubboost"],
|
|
||||||
karaoke: [false, "stereotools=mlev=0.03"],
|
|
||||||
flanger: [false, "flanger"],
|
|
||||||
gate: [false, "agate"],
|
|
||||||
haas: [false, "haas"],
|
|
||||||
mcompand: [false, "mcompand"],
|
|
||||||
mono: [false, "pan=mono|c0=.5*c0+.5*c1"],
|
|
||||||
mstlr: [false, "stereotools=mode=ms>lr"],
|
|
||||||
mstrr: [false, "stereotools=mode=ms>rr"],
|
|
||||||
compressor: [false, "compand=points=-80/-105|-62/-80|-15.4/-15.4|0/-12|20/-7.6"],
|
|
||||||
expander: [
|
|
||||||
false,
|
|
||||||
"compand=attacks=0:points=-80/-169|-54/-80|-49.5/-64.6|-41.1/-41.1|-25.8/-15|-10.8/-4.5|0/0|20/8.3"
|
|
||||||
],
|
|
||||||
softlimiter: [false, "compand=attacks=0:points=-80/-80|-12.4/-12.4|-6/-8|0/-6.8|20/-2.8"],
|
|
||||||
chorus: [false, "chorus=0.7:0.9:55:0.4:0.25:2"],
|
|
||||||
chorus2d: [false, "chorus=0.6:0.9:50|60:0.4|0.32:0.25|0.4:2|1.3"],
|
|
||||||
chorus3d: [false, "chorus=0.5:0.9:50|60|40:0.4|0.32|0.3:0.25|0.4|0.3:2|2.3|1.3"],
|
|
||||||
fadein: [false, "afade=t=in:ss=0:d=10"],
|
|
||||||
dim: [
|
|
||||||
false,
|
|
||||||
"afftfilt=\"'real=re * (1-clip((b/nb)*b,0,1))':imag='im * (1-clip((b/nb)*b,0,1))'\""
|
|
||||||
],
|
|
||||||
earrape: [false, "channelsplit,sidechaingate=level_in=64"]
|
|
||||||
};
|
|
||||||
|
|
||||||
public get Filters(): [string, string][] {
|
|
||||||
return Object.entries(this.filters)
|
|
||||||
.filter((value) => value[1][0])
|
|
||||||
.map((value) => [value[0], value[1][1]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public toggle(filter?: string): void {
|
|
||||||
if (!filter) {
|
|
||||||
Object.values(this.filters).forEach((value) => {
|
|
||||||
value[0] = false;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Object.prototype.hasOwnProperty.call(this.filters, filter))
|
|
||||||
this.filters[filter][0] = !this.filters[filter][0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public toString(): string {
|
|
||||||
return this.Filters.map((value) => value[1]).join(",");
|
|
||||||
}
|
|
||||||
|
|
||||||
public empty(): boolean {
|
|
||||||
return this.Filters.length === 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
import { loopOption, track } from "./types.js";
|
|
||||||
|
|
||||||
export default class Queue {
|
|
||||||
private tracks: track[] = [];
|
|
||||||
public get Tracks(): track[] {
|
|
||||||
return this.tracks;
|
|
||||||
}
|
|
||||||
private position = 0;
|
|
||||||
public get Position(): number {
|
|
||||||
return this.position;
|
|
||||||
}
|
|
||||||
|
|
||||||
private loop: loopOption = "NONE";
|
|
||||||
public get Loop(): loopOption {
|
|
||||||
return this.loop;
|
|
||||||
}
|
|
||||||
public set Loop(x: loopOption) {
|
|
||||||
this.loop = x;
|
|
||||||
}
|
|
||||||
|
|
||||||
private duration = 0;
|
|
||||||
public get Duration(): number {
|
|
||||||
return this.duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
public next(): track | undefined {
|
|
||||||
if (this.loop === "SONG") return this.current();
|
|
||||||
|
|
||||||
if (this.loop === "AUTO") {
|
|
||||||
this.position = Math.floor(Math.random() * this.tracks.length);
|
|
||||||
return this.current();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.position + 1 < this.tracks.length) {
|
|
||||||
this.position++;
|
|
||||||
return this.current();
|
|
||||||
} else if (this.loop === "LOOP") {
|
|
||||||
this.position = 0;
|
|
||||||
return this.current();
|
|
||||||
} else if (this.loop === "NONE") {
|
|
||||||
this.position = 0;
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public shuffle(): void {
|
|
||||||
let currentIndex = this.tracks.length;
|
|
||||||
|
|
||||||
while (currentIndex != 0) {
|
|
||||||
const randomIndex = Math.floor(Math.random() * currentIndex);
|
|
||||||
currentIndex--;
|
|
||||||
|
|
||||||
[this.tracks[currentIndex], this.tracks[randomIndex]] = [
|
|
||||||
this.tracks[randomIndex],
|
|
||||||
this.tracks[currentIndex]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.position = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public push(tracks: track[], index?: number): track {
|
|
||||||
if (!index || index >= this.tracks.length) {
|
|
||||||
this.tracks = this.tracks.concat(tracks);
|
|
||||||
} else {
|
|
||||||
this.tracks.splice(Math.max(0, index), 0, ...tracks);
|
|
||||||
if (this.position >= index) this.position += tracks.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const track of tracks) {
|
|
||||||
this.duration += track.duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.tracks[index || this.tracks.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
public pop(query: number): track | undefined {
|
|
||||||
if (query >= 0 && query < this.tracks.length) {
|
|
||||||
const track = this.tracks[query];
|
|
||||||
|
|
||||||
this.tracks.splice(query, 1);
|
|
||||||
this.position = Math.max(0, this.position - 1);
|
|
||||||
this.duration -= track.duration;
|
|
||||||
return track;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
public jump(query: number): track | undefined {
|
|
||||||
if (query >= 0 && query < this.tracks.length) {
|
|
||||||
const track = this.tracks[query];
|
|
||||||
this.position = query;
|
|
||||||
return track;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
public current(): track {
|
|
||||||
return this.tracks[this.position];
|
|
||||||
}
|
|
||||||
|
|
||||||
public empty(): boolean {
|
|
||||||
return this.tracks.length === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public clear(): void {
|
|
||||||
this.tracks = [];
|
|
||||||
this.position = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+20
-266
@@ -1,273 +1,27 @@
|
|||||||
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;
|
||||||
|
rootDir: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type loopOption = "LOOP" | "SONG" | "NONE" | "AUTO";
|
export interface ModuleMetadata extends Module {
|
||||||
|
commands: string[];
|
||||||
|
events: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export type ytdlArgs = {
|
export interface Command {
|
||||||
help?: boolean;
|
data: ApplicationCommandData;
|
||||||
version?: boolean;
|
execute: (client: Client, interaction: CommandInteraction) => 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 = {
|
export interface Event {
|
||||||
asr: number;
|
name: string;
|
||||||
filesize: number;
|
execute: (client: Client, eventee: unknown[]) => Promise<void>;
|
||||||
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[];
|
|
||||||
};
|
|
||||||
@@ -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(I18n.replies.modules(client.modules));
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = new SlashCommandBuilder()
|
||||||
|
.setName("modules")
|
||||||
|
.setDescription("List of availiable modules.");
|
||||||
|
|
||||||
|
export { data, execute };
|
||||||
@@ -1,21 +1,21 @@
|
|||||||
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";
|
||||||
|
|
||||||
export async function run(client: Client, ctx: CommandInteraction) {
|
async function execute(client: Client, interaction: CommandInteraction): Promise<void> {
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
|
|
||||||
if (!client.application) {
|
if (!client.application) {
|
||||||
await ctx.reply({ embeds: [I18n.en.ping(undefined)] });
|
await interaction.reply(I18n.replies.ping());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.application.fetch();
|
await client.application.fetch();
|
||||||
|
|
||||||
await ctx.reply({ embeds: [I18n.en.ping(Date.now() - start)] });
|
await interaction.reply(I18n.replies.ping(Date.now() - start));
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = new SlashCommandBuilder().setName("ping").setDescription("Try to ping me.");
|
const data = new SlashCommandBuilder().setName("ping").setDescription("Try to ping me.");
|
||||||
|
|
||||||
export { data };
|
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,21 +1,23 @@
|
|||||||
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!");
|
throw new Error("No application or user for it is provided.");
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`${client.user.username} logged in as ${client.user.tag}!`);
|
|
||||||
console.log(generateDependencyReport());
|
console.log(generateDependencyReport());
|
||||||
|
console.log(client.generateApplicationStatus());
|
||||||
|
|
||||||
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 };
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { bold, Collection, EmbedBuilder, InteractionReplyOptions, italic } from "discord.js";
|
||||||
|
|
||||||
|
import { ModuleMetadata } from "../../../lib/types.js";
|
||||||
|
export default class I18n {
|
||||||
|
public static readonly replies = {
|
||||||
|
modules: (modules: Collection<string, ModuleMetadata>): InteractionReplyOptions => {
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setColor("Random")
|
||||||
|
.setTitle("Modules")
|
||||||
|
.addFields(
|
||||||
|
modules.map((value) => {
|
||||||
|
return {
|
||||||
|
name: value.name + "@" + value.tag,
|
||||||
|
value: (value.description ?? "") + " | " + value.author,
|
||||||
|
inline: true
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return { embeds: [embed] };
|
||||||
|
},
|
||||||
|
ping: (ping?: number): InteractionReplyOptions => {
|
||||||
|
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!";
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setColor("Random")
|
||||||
|
.setTitle(bold(title))
|
||||||
|
.setDescription(italic(bold(comment)));
|
||||||
|
|
||||||
|
return { embeds: [embed] };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import path from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
|
const name = "core";
|
||||||
|
const description = "Quetza core functionality";
|
||||||
|
const author = "unknowableshade";
|
||||||
|
const tag = "2302.1";
|
||||||
|
|
||||||
|
const rootDir = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
export { author, description, name, rootDir, tag };
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
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.embeds.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.embeds.okConnected(channel.name)] });
|
||||||
|
} else {
|
||||||
|
controller.delete(interaction.guild.id);
|
||||||
|
|
||||||
|
await interaction.editReply({ embeds: [I18n.embeds.notConnected()] });
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await interaction.editReply({ embeds: [I18n.embeds.wasConnected()] });
|
||||||
|
}
|
||||||
|
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = new SlashCommandBuilder()
|
||||||
|
.setName("connect")
|
||||||
|
.setDescription("Connect me to a voice channel.");
|
||||||
|
|
||||||
|
export { data, execute };
|
||||||
@@ -1,20 +1,25 @@
|
|||||||
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.embeds.filtered(filter || undefined)] });
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = new SlashCommandBuilder()
|
const data = new SlashCommandBuilder()
|
||||||
@@ -26,15 +31,15 @@ const data = new SlashCommandBuilder()
|
|||||||
.setDescription("Filter to apply.")
|
.setDescription("Filter to apply.")
|
||||||
.setChoices(
|
.setChoices(
|
||||||
{
|
{
|
||||||
name: "bassboost_low",
|
name: "Bassboost low",
|
||||||
value: "bassboost_low"
|
value: "bassboost_low"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "bassboost",
|
name: "Bassboost",
|
||||||
value: "bassboost"
|
value: "bassboost"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "bassboost_high",
|
name: "Bassboost high",
|
||||||
value: "bassboost_high"
|
value: "bassboost_high"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -42,83 +47,83 @@ const data = new SlashCommandBuilder()
|
|||||||
value: "8D"
|
value: "8D"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "vaporwave",
|
name: "Vaporwave",
|
||||||
value: "vaporwave"
|
value: "vaporwave"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "nightcore",
|
name: "Nightcore",
|
||||||
value: "nightcore"
|
value: "nightcore"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "phaser",
|
name: "Phaser",
|
||||||
value: "phaser"
|
value: "phaser"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "tremolo",
|
name: "Tremolo",
|
||||||
value: "tremolo"
|
value: "tremolo"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "vibrato",
|
name: "Vibrato",
|
||||||
value: "vibrato"
|
value: "vibrato"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "reverse",
|
name: "Reverse",
|
||||||
value: "reverse"
|
value: "reverse"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "treble",
|
name: "Treble",
|
||||||
value: "treble"
|
value: "treble"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "normalizer",
|
name: "Normalizer",
|
||||||
value: "normalizer"
|
value: "normalizer"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "surrounding",
|
name: "Surround Sound",
|
||||||
value: "surrounding"
|
value: "surrounding"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "pulsator",
|
name: "Pulsator",
|
||||||
value: "pulsator"
|
value: "pulsator"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "subboost",
|
name: "Subboost",
|
||||||
value: "subboost"
|
value: "subboost"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "mono",
|
name: "Mono Audio",
|
||||||
value: "mono"
|
value: "mono"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "compressor",
|
name: "Compressor",
|
||||||
value: "compressor"
|
value: "compressor"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "expander",
|
name: "Expander",
|
||||||
value: "expander"
|
value: "expander"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "softlimiter",
|
name: "Softlimiter",
|
||||||
value: "softlimiter"
|
value: "softlimiter"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "chorus",
|
name: "Chorus",
|
||||||
value: "chorus"
|
value: "chorus"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fadein",
|
name: "Fadein",
|
||||||
value: "fadein"
|
value: "fadein"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "dim",
|
name: "Dimming",
|
||||||
value: "dim"
|
value: "dim"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "earrape",
|
name: "Earrape",
|
||||||
value: "earrape"
|
value: "earrape"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.setRequired(true)
|
.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.embeds.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.embeds.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.embeds.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.embeds.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 };
|
||||||
@@ -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) {
|
||||||
|
if (!interaction.guild || !interaction.channel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const player = controller.get(interaction.guild.id, interaction.channel as TextChannel);
|
||||||
|
|
||||||
|
if (!player) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.skip();
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [I18n.embeds.skipped()] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = new SlashCommandBuilder().setName("next").setDescription("Skip current song.");
|
||||||
|
|
||||||
|
export { data, execute };
|
||||||
@@ -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) {
|
||||||
|
if (!interaction.guild || !interaction.channel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const player = controller.get(interaction.guild.id, interaction.channel as TextChannel);
|
||||||
|
|
||||||
|
if (!player) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = player.pause();
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [I18n.embeds.paused(state)] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = new SlashCommandBuilder().setName("pause").setDescription("Toggle pause on player.");
|
||||||
|
|
||||||
|
export { data, execute };
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { CommandInteraction, SlashCommandBuilder } from "discord.js";
|
||||||
|
|
||||||
|
import Client from "../../../lib/client.js";
|
||||||
|
import I18n from "../lib/i18n.js";
|
||||||
|
import { execute as connect } from "./connect.js";
|
||||||
|
|
||||||
|
async function execute(client: Client, interaction: CommandInteraction) {
|
||||||
|
const query = interaction.options.get("query")?.value as string;
|
||||||
|
|
||||||
|
const player = await connect(client, interaction);
|
||||||
|
|
||||||
|
if (!player) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query) {
|
||||||
|
const track = await player.add(query, interaction.user);
|
||||||
|
|
||||||
|
await interaction.editReply({ embeds: [I18n.embeds.appended(track)] });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player.resource) {
|
||||||
|
player.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = new SlashCommandBuilder()
|
||||||
|
.setName("play")
|
||||||
|
.setDescription("Launch player and add songs to the queue.")
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option.setName("query").setDescription("Title or URL of the song.")
|
||||||
|
);
|
||||||
|
|
||||||
|
export { data, execute };
|
||||||
@@ -10,20 +10,25 @@ 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, interaction: CommandInteraction) {
|
||||||
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;
|
||||||
|
}
|
||||||
|
|
||||||
await ctx.deferReply();
|
await interaction.deferReply();
|
||||||
|
|
||||||
if (player.Queue.empty()) {
|
if (player.queue.empty()) {
|
||||||
await ctx.editReply({ embeds: [I18n.en.queueEmpty()] });
|
await interaction.editReply({ embeds: [I18n.embeds.queueEmpty()] });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,8 +41,8 @@ export async function run(client: Client, ctx: CommandInteraction) {
|
|||||||
new ButtonBuilder().setCustomId("QueueEnd").setLabel("🔽").setStyle(ButtonStyle.Primary)
|
new ButtonBuilder().setCustomId("QueueEnd").setLabel("🔽").setStyle(ButtonStyle.Primary)
|
||||||
);
|
);
|
||||||
|
|
||||||
const message = (await ctx.editReply({
|
const message = (await interaction.editReply({
|
||||||
embeds: [I18n.en.queueDesigner(player.Queue, 0, player.Resource)],
|
embeds: [I18n.embeds.queueDesigner(player.queue, 0, player.resource)],
|
||||||
components: [row]
|
components: [row]
|
||||||
})) as Message;
|
})) as Message;
|
||||||
|
|
||||||
@@ -54,19 +59,19 @@ export async function run(client: Client, ctx: CommandInteraction) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "QueueDown": {
|
case "QueueDown": {
|
||||||
page = Math.min(page + 1, Math.floor(player.Queue.Tracks.length / 11));
|
page = Math.min(page + 1, Math.floor(player.queue.tracks.length / 11));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "QueueEnd":
|
case "QueueEnd":
|
||||||
page = Math.floor(player.Queue.Tracks.length / 11);
|
page = Math.floor(player.queue.tracks.length / 11);
|
||||||
}
|
}
|
||||||
|
|
||||||
await btn.update({
|
await btn.update({
|
||||||
embeds: [I18n.en.queueDesigner(player.Queue, page, player.Resource)]
|
embeds: [I18n.embeds.queueDesigner(player.queue, page, player.resource)]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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,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 track =
|
const track =
|
||||||
!isNaN(+query) && isFinite(+query) && !/e/i.test(query)
|
!isNaN(+query) && isFinite(+query) && !/e/i.test(query)
|
||||||
? player.remove(parseInt(query) - 1)
|
? player.remove(parseInt(query) - 1)
|
||||||
: player.remove(query);
|
: player.remove(query);
|
||||||
|
|
||||||
await ctx.reply({ embeds: [I18n.en.removed(track)] });
|
await interaction.reply({ embeds: [I18n.embeds.removed(track)] });
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = new SlashCommandBuilder()
|
const data = new SlashCommandBuilder()
|
||||||
@@ -27,4 +32,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 };
|
||||||
@@ -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) {
|
||||||
|
if (!interaction.guild || !interaction.channel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const player = controller.get(interaction.guild.id, interaction.channel as TextChannel);
|
||||||
|
|
||||||
|
if (!player) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.shuffle();
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [I18n.embeds.reshuffle()] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = new SlashCommandBuilder().setName("reshuffle").setDescription("Shuffle the queue.");
|
||||||
|
|
||||||
|
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 hours = (interaction.options.get("hrs")?.value as number) || 0;
|
||||||
|
const minutes = (interaction.options.get("mins")?.value as number) || 0;
|
||||||
|
const seconds = (interaction.options.get("secs")?.value as number) || 0;
|
||||||
|
|
||||||
|
if (!interaction.guild || !interaction.channel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const player = controller.get(interaction.guild.id, interaction.channel as TextChannel);
|
||||||
|
|
||||||
|
if (!player) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const time = hours * 3600 + minutes * 60 + seconds;
|
||||||
|
|
||||||
|
player.seek(time * 1000);
|
||||||
|
|
||||||
|
await interaction.reply({
|
||||||
|
embeds: [I18n.embeds.fastForwarded(time)]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = new SlashCommandBuilder()
|
||||||
|
.setName("seek")
|
||||||
|
.setDescription("Fast-forward by the time.")
|
||||||
|
.addIntegerOption((option) => option.setName("hrs").setDescription("Hours to seek."))
|
||||||
|
.addIntegerOption((option) => option.setName("mins").setDescription("Minutes to seek."))
|
||||||
|
.addIntegerOption((option) => option.setName("secs").setDescription("Seconds to seek."));
|
||||||
|
|
||||||
|
export { data, execute };
|
||||||
@@ -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) {
|
||||||
|
if (!interaction.guild || !interaction.channel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const player = controller.get(interaction.guild.id, interaction.channel as TextChannel);
|
||||||
|
|
||||||
|
if (!player) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.destroy();
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [I18n.embeds.destroyed()] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = new SlashCommandBuilder().setName("stop").setDescription("Destroy the player.");
|
||||||
|
|
||||||
|
export { data, execute };
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
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 volume = (interaction.options.get("volume")?.value as number) || 100;
|
||||||
|
|
||||||
|
if (!interaction.guild || !interaction.channel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const player = controller.get(interaction.guild.id, interaction.channel as TextChannel);
|
||||||
|
|
||||||
|
if (!player) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.volume = volume;
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [I18n.embeds.volumeSet(player.volume)] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = new SlashCommandBuilder()
|
||||||
|
.setName("volume")
|
||||||
|
.setDescription("Change player's volume amount.")
|
||||||
|
.addIntegerOption((option) =>
|
||||||
|
option.setName("volume").setDescription("Volume in percents.").setRequired(true)
|
||||||
|
);
|
||||||
|
|
||||||
|
export { data, execute };
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { VoiceState } from "discord.js";
|
||||||
|
|
||||||
|
import Client from "../../../lib/client.js";
|
||||||
|
import I18n from "../lib/i18n.js";
|
||||||
|
import { asleep } from "../lib/misc.js";
|
||||||
|
import { controller } from "../module.js";
|
||||||
|
|
||||||
|
async function execute(client: Client, eventee: VoiceState[]): Promise<void> {
|
||||||
|
const [oldState] = eventee;
|
||||||
|
|
||||||
|
const player = controller.get(oldState.guild.id);
|
||||||
|
|
||||||
|
if (!player || !oldState.channel || !client.user) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldState.channel.members.filter((value) => value.user !== client.user).size === 0) {
|
||||||
|
await asleep(30000);
|
||||||
|
if (oldState.channel.members.filter((value) => value.user !== client.user).size === 0) {
|
||||||
|
await player.channel.send({ embeds: [I18n.embeds.fullyAlone()] });
|
||||||
|
player.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = "voiceStateUpdate";
|
||||||
|
|
||||||
|
export { execute, name };
|
||||||
@@ -2,13 +2,14 @@ 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) {
|
||||||
return `--${key.split(/(?=[A-Z])/).join("-")}`.toLowerCase();
|
return `--${key.split(/(?=[A-Z])/).join("-")}`.toLowerCase();
|
||||||
@@ -18,23 +19,23 @@ 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);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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")) {
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
export default class Filter {
|
||||||
|
public static readonly filters: Record<string, string> = {
|
||||||
|
bassboost_low: "bass=g=15:f=110:w=0.3",
|
||||||
|
bassboost: "bass=g=20:f=110:w=0.3",
|
||||||
|
bassboost_high: "bass=g=30:f=110:w=0.3",
|
||||||
|
"8D": "apulsator=hz=0.09",
|
||||||
|
vaporwave: "aresample=48000,asetrate=48000*0.75",
|
||||||
|
nightcore: "aresample=48000,asetrate=48000*1.25",
|
||||||
|
phaser: "aphaser=in_gain=0.4",
|
||||||
|
tremolo: "tremolo",
|
||||||
|
vibrato: "vibrato=f=6.5",
|
||||||
|
reverse: "areverse",
|
||||||
|
treble: "treble=g=5",
|
||||||
|
normalizer: "dynaudnorm=g=101",
|
||||||
|
normalizer2: "acompressor",
|
||||||
|
surrounding: "surround",
|
||||||
|
pulsator: "apulsator=hz=1",
|
||||||
|
subboost: "asubboost",
|
||||||
|
karaoke: "stereotools=mlev=0.03",
|
||||||
|
flanger: "flanger",
|
||||||
|
gate: "agate",
|
||||||
|
haas: "haas",
|
||||||
|
mcompand: "mcompand",
|
||||||
|
mono: "pan=mono|c0=.5*c0+.5*c1",
|
||||||
|
mstlr: "stereotools=mode=ms>lr",
|
||||||
|
mstrr: "stereotools=mode=ms>rr",
|
||||||
|
compressor: "compand=points=-80/-105|-62/-80|-15.4/-15.4|0/-12|20/-7.6",
|
||||||
|
expander:
|
||||||
|
"compand=attacks=0:points=-80/-169|-54/-80|-49.5/-64.6|-41.1/-41.1|-25.8/-15|-10.8/-4.5|0/0|20/8.3",
|
||||||
|
softlimiter: "compand=attacks=0:points=-80/-80|-12.4/-12.4|-6/-8|0/-6.8|20/-2.8",
|
||||||
|
chorus: "chorus=0.7:0.9:55:0.4:0.25:2",
|
||||||
|
chorus2d: "chorus=0.6:0.9:50|60:0.4|0.32:0.25|0.4:2|1.3",
|
||||||
|
chorus3d: "chorus=0.5:0.9:50|60|40:0.4|0.32|0.3:0.25|0.4|0.3:2|2.3|1.3",
|
||||||
|
fadein: "afade=t=in:ss=0:d=10",
|
||||||
|
dim: "afftfilt=\"'real=re * (1-clip((b/nb)*b,0,1))':imag='im * (1-clip((b/nb)*b,0,1))'\"",
|
||||||
|
earrape: "channelsplit,sidechaingate=level_in=64"
|
||||||
|
};
|
||||||
|
|
||||||
|
private switches = new Set<string>();
|
||||||
|
|
||||||
|
public toggled(): [string, string][] {
|
||||||
|
return Object.entries(Filter.filters)
|
||||||
|
.filter((value) => this.switches.has(value[0]))
|
||||||
|
.map((value) => [value[0], value[1]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggle(query?: string): void {
|
||||||
|
if (!query) {
|
||||||
|
this.switches.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.switches.delete(query) && Object.hasOwn(Filter.filters, query)) {
|
||||||
|
this.switches.add(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public size(): number {
|
||||||
|
return this.switches.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public empty(): boolean {
|
||||||
|
return this.size() === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString(): string {
|
||||||
|
return this.toggled()
|
||||||
|
.map((value) => value[1])
|
||||||
|
.join(",");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 readonly embeds = {
|
||||||
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")
|
||||||
@@ -115,54 +115,57 @@ export default class I18n {
|
|||||||
.setTitle(`**${volumeMoji(amount)} Volume has been set to ${amount}%**`),
|
.setTitle(`**${volumeMoji(amount)} Volume has been set to ${amount}%**`),
|
||||||
queueEmpty: () => new EmbedBuilder().setColor("Yellow").setTitle("**❎ Queue is empty.**"),
|
queueEmpty: () => new EmbedBuilder().setColor("Yellow").setTitle("**❎ Queue is empty.**"),
|
||||||
playerInfo: (player?: Player) => {
|
playerInfo: (player?: Player) => {
|
||||||
if (!player)
|
if (!player) {
|
||||||
return new EmbedBuilder()
|
return new EmbedBuilder()
|
||||||
.setColor("Red")
|
.setColor("Red")
|
||||||
.setTitle("**🚫 Player does not exist.**");
|
.setTitle("**🚫 Player does not exist.**");
|
||||||
|
}
|
||||||
|
|
||||||
if (player.Resource) {
|
if (player.resource) {
|
||||||
return new EmbedBuilder()
|
return new EmbedBuilder()
|
||||||
.setColor("LuminousVividPink")
|
.setColor("LuminousVividPink")
|
||||||
.setTitle(`${player.Queue.Position + 1}. ${player.Resource.metadata.title}`)
|
.setTitle(`${player.queue.position + 1}. ${player.resource.metadata.title}`)
|
||||||
.setDescription(
|
.setDescription(
|
||||||
statusBarGenerator(
|
statusBarGenerator(
|
||||||
player.Resource.playbackDuration / 1000,
|
player.resource.playbackDuration / 1000,
|
||||||
player.Resource.metadata.duration
|
player.resource.metadata.duration
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.setThumbnail(player.Resource.metadata.thumbnail)
|
.setThumbnail(player.resource.metadata.thumbnail)
|
||||||
.setURL(player.Resource.metadata.url)
|
.setURL(player.resource.metadata.url)
|
||||||
.addFields([
|
.addFields([
|
||||||
{
|
{
|
||||||
name: "Applied Filters",
|
name: "Applied Filters",
|
||||||
value:
|
value:
|
||||||
player.Filter.Filters.map((value) => value[0]).join("\n") +
|
player.filter
|
||||||
"\ndefault",
|
.toggled()
|
||||||
|
.map((value) => value[0])
|
||||||
|
.join("\n") + "\ndefault",
|
||||||
inline: true
|
inline: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Queue Length",
|
name: "Queue Length",
|
||||||
value: `🔢 ${player.Queue.Tracks.length}`,
|
value: `🔢 ${player.queue.tracks.length}`,
|
||||||
inline: true
|
inline: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Queue Duration",
|
name: "Queue Duration",
|
||||||
value: `💿 ${secToISO(player.Queue.Duration)}`,
|
value: `💿 ${secToISO(player.queue.duration)}`,
|
||||||
inline: true
|
inline: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Queue Looping",
|
name: "Queue Looping",
|
||||||
value: `${loopMoji(player.Queue.Loop)} ${player.Queue.Loop}`,
|
value: `${loopMoji(player.queue.loop)} ${player.queue.loop}`,
|
||||||
inline: true
|
inline: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Volume amount",
|
name: "Volume amount",
|
||||||
value: `${volumeMoji(player.Volume)} ${player.Volume}/150`,
|
value: `${volumeMoji(player.volume)} ${player.volume}/150`,
|
||||||
inline: true
|
inline: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Player lifetime",
|
name: "Player lifetime",
|
||||||
value: `🕓 ${secToISO((Date.now() - player.Created.getTime()) / 1000)}`,
|
value: `🕓 ${secToISO((Date.now() - player.created.getTime()) / 1000)}`,
|
||||||
inline: true
|
inline: true
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
@@ -176,46 +179,48 @@ export default class I18n {
|
|||||||
{
|
{
|
||||||
name: "Applied Filters",
|
name: "Applied Filters",
|
||||||
value:
|
value:
|
||||||
player.Filter.Filters.map((value) => value[0]).join("\n") +
|
player.filter
|
||||||
"\ndefault",
|
.toggled()
|
||||||
|
.map((value) => value[0])
|
||||||
|
.join("\n") + "\ndefault",
|
||||||
inline: true
|
inline: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Queue Length",
|
name: "Queue Length",
|
||||||
value: `🔢 ${player.Queue.Tracks.length}`,
|
value: `🔢 ${player.queue.tracks.length}`,
|
||||||
inline: true
|
inline: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Queue Duration",
|
name: "Queue Duration",
|
||||||
value: `💿 ${secToISO(player.Queue.Duration)}`,
|
value: `💿 ${secToISO(player.queue.duration)}`,
|
||||||
inline: true
|
inline: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Queue Looping",
|
name: "Queue Looping",
|
||||||
value: `${loopMoji(player.Queue.Loop)} ${player.Queue.Loop}`,
|
value: `${loopMoji(player.queue.loop)} ${player.queue.loop}`,
|
||||||
inline: true
|
inline: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Volume amount",
|
name: "Volume amount",
|
||||||
value: `${volumeMoji(player.Volume)} ${player.Volume}/150`,
|
value: `${volumeMoji(player.volume)} ${player.volume}/150`,
|
||||||
inline: true
|
inline: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Player lifetime",
|
name: "Player lifetime",
|
||||||
value: `🕓 ${secToISO((Date.now() - player.Created.getTime()) / 1000)}`,
|
value: `🕓 ${secToISO((Date.now() - player.created.getTime()) / 1000)}`,
|
||||||
inline: true
|
inline: true
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
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()
|
||||||
.setColor("LuminousVividPink")
|
.setColor("LuminousVividPink")
|
||||||
.setTitle(
|
.setTitle(
|
||||||
resource
|
resource
|
||||||
? `${queue.Position + 1}. ${resource.metadata.title}`
|
? `${queue.position + 1}. ${resource.metadata.title}`
|
||||||
: "💿 Insert the disk..."
|
: "💿 Insert the disk..."
|
||||||
)
|
)
|
||||||
.setURL(
|
.setURL(
|
||||||
@@ -237,7 +242,7 @@ export default class I18n {
|
|||||||
: statusBarGenerator(0, 0)
|
: statusBarGenerator(0, 0)
|
||||||
)
|
)
|
||||||
.setFields(
|
.setFields(
|
||||||
queue.Tracks.slice(page * 10, page * 10 + 10).map((value, index) => ({
|
queue.tracks.slice(page * 10, page * 10 + 10).map((value, index) => ({
|
||||||
name: `${page * 10 + index + 1}. ${value.title.padEnd(35)}`,
|
name: `${page * 10 + index + 1}. ${value.title.padEnd(35)}`,
|
||||||
value: `**[${secToISO(value.duration)}]**`
|
value: `**[${secToISO(value.duration)}]**`
|
||||||
}))
|
}))
|
||||||
@@ -249,22 +254,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,17 +1,17 @@
|
|||||||
import { loopOption } from "./types.js";
|
import { LoopOption } from "./types.js";
|
||||||
|
|
||||||
|
const PLAYBACK_LENGTH = 15;
|
||||||
|
|
||||||
|
export function statusBarGenerator(playback: number, duration: number): string {
|
||||||
|
const filled = Array<string>(
|
||||||
|
Math.floor((PLAYBACK_LENGTH * playback) / (duration === 0 ? 1 : duration))
|
||||||
|
).fill("🔹");
|
||||||
|
|
||||||
export function statusBarGenerator(duration: number, length: number): string {
|
|
||||||
duration = Math.max(0, duration);
|
|
||||||
length = Math.max(0, length);
|
|
||||||
if (duration > length) duration = length;
|
|
||||||
const filled = Array<string>(Math.floor((15 * duration) / (length === 0 ? 1 : length))).fill(
|
|
||||||
":small_blue_diamond:"
|
|
||||||
);
|
|
||||||
const empty = Array<string>(
|
const empty = Array<string>(
|
||||||
15 - Math.floor((15 * duration) / (length === 0 ? 1 : length))
|
PLAYBACK_LENGTH - Math.floor((PLAYBACK_LENGTH * playback) / (duration === 0 ? 1 : duration))
|
||||||
).fill(":white_small_square:");
|
).fill("▫️");
|
||||||
|
|
||||||
return `**[${filled.join("")}${empty.join("")}] ${secToISO(duration)}/${secToISO(length)}**`;
|
return `**[${filled.join("")}${empty.join("")}] ${secToISO(playback)}/${secToISO(duration)}**`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function secToISO(amount: number): string {
|
export function secToISO(amount: number): string {
|
||||||
@@ -22,6 +22,7 @@ export function secToISO(amount: number): string {
|
|||||||
const hDisplay = h > 0 ? `${h}:` : "";
|
const hDisplay = h > 0 ? `${h}:` : "";
|
||||||
const mDisplay = `${Math.floor(m / 10)}${Math.floor(m % 10)}:`;
|
const mDisplay = `${Math.floor(m / 10)}${Math.floor(m % 10)}:`;
|
||||||
const sDisplay = `${Math.floor(s / 10)}${Math.floor(s % 10)}`;
|
const sDisplay = `${Math.floor(s / 10)}${Math.floor(s % 10)}`;
|
||||||
|
|
||||||
return hDisplay + mDisplay + sDisplay;
|
return hDisplay + mDisplay + sDisplay;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +34,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" ? "⤵️" : "🔂";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,10 +43,14 @@ export function largestCommonSequence(s1: string, s2: string) {
|
|||||||
s2 = s2.toLowerCase().slice(0, 100);
|
s2 = s2.toLowerCase().slice(0, 100);
|
||||||
|
|
||||||
const buffer = Array.from(Array(s1.length + 1), () => new Array<number>(s2.length + 1).fill(0));
|
const buffer = Array.from(Array(s1.length + 1), () => new Array<number>(s2.length + 1).fill(0));
|
||||||
|
|
||||||
for (let i = 1; i <= s1.length; i++) {
|
for (let i = 1; i <= s1.length; i++) {
|
||||||
for (let j = 1; j <= s2.length; j++) {
|
for (let j = 1; j <= s2.length; j++) {
|
||||||
if (s1[i - 1] === s2[j - 1]) buffer[i][j] = buffer[i - 1][j - 1] + 1;
|
if (s1[i - 1] === s2[j - 1]) {
|
||||||
else buffer[i][j] = Math.max(buffer[i - 1][j], buffer[i][j - 1]);
|
buffer[i][j] = buffer[i - 1][j - 1] + 1;
|
||||||
|
} else {
|
||||||
|
buffer[i][j] = Math.max(buffer[i - 1][j], buffer[i][j - 1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,26 +1,31 @@
|
|||||||
import { Guild, TextChannel } from "discord.js";
|
import { Collection, Guild, TextChannel } from "discord.js";
|
||||||
|
|
||||||
import Player from "./player.js";
|
import Player from "./player.js";
|
||||||
|
|
||||||
export default class Music {
|
export default class Music {
|
||||||
private players = new Map<string, Player>();
|
private players = new Collection<string, Player>();
|
||||||
|
|
||||||
public get(guildId: string, channel?: TextChannel): Player | undefined {
|
public get(guildId: string, channel?: TextChannel): Player | undefined {
|
||||||
const player = this.players.get(guildId);
|
const player = this.players.get(guildId);
|
||||||
|
|
||||||
if (player) {
|
if (player) {
|
||||||
if (channel) player.Channel = channel;
|
if (channel) {
|
||||||
|
player.channel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
return player;
|
return player;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public set(guild: Guild, channel: TextChannel): Player {
|
public set(guild: Guild, channel: TextChannel): Player {
|
||||||
const player = new Player(guild, this, channel);
|
const player = new Player(guild, this, channel);
|
||||||
|
|
||||||
this.players.set(guild.id, player);
|
this.players.set(guild.id, player);
|
||||||
|
|
||||||
return player;
|
return player;
|
||||||
}
|
}
|
||||||
|
|
||||||
public del(guildId: string): void {
|
public delete(guildId: string): void {
|
||||||
this.players.delete(guildId);
|
this.players.delete(guildId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,72 +19,66 @@ 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;
|
||||||
private guild: Guild;
|
private guild: Guild;
|
||||||
|
|
||||||
private channel: TextChannel;
|
private msgChannel: TextChannel;
|
||||||
public get Channel(): TextChannel {
|
public get channel(): TextChannel {
|
||||||
return this.channel;
|
return this.msgChannel;
|
||||||
}
|
}
|
||||||
public set Channel(x: TextChannel) {
|
public set channel(x: TextChannel) {
|
||||||
this.channel = x;
|
this.msgChannel = x;
|
||||||
}
|
|
||||||
|
|
||||||
private connection: VoiceConnection | undefined;
|
|
||||||
public get Connection(): VoiceConnection | undefined {
|
|
||||||
return this.connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
private player: AudioPlayer;
|
|
||||||
public get Player(): AudioPlayer {
|
|
||||||
return this.player;
|
|
||||||
}
|
|
||||||
|
|
||||||
private filter = new Filter();
|
|
||||||
public get Filter(): Filter {
|
|
||||||
return this.filter;
|
|
||||||
}
|
|
||||||
private queue = new Queue();
|
|
||||||
public get Queue(): Queue {
|
|
||||||
return this.queue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private resource: AudioResource<track> | undefined;
|
|
||||||
public get Resource(): AudioResource<track> | undefined {
|
|
||||||
return this.resource;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private message: Message | undefined;
|
private message: Message | undefined;
|
||||||
private created: Date = new Date();
|
|
||||||
public get Created(): Date {
|
public internalConnection: VoiceConnection | undefined;
|
||||||
return this.created;
|
public get connection(): VoiceConnection | undefined {
|
||||||
|
return this.internalConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
private volume = 100;
|
private internalPlayer: AudioPlayer;
|
||||||
public get Volume(): number {
|
public get player(): AudioPlayer {
|
||||||
return this.volume;
|
return this.internalPlayer;
|
||||||
}
|
}
|
||||||
public set Volume(x: number) {
|
|
||||||
this.volume = Math.max(0, Math.min(150, x));
|
|
||||||
|
|
||||||
if (this.resource && this.resource.volume)
|
private playerResource: AudioResource<Track> | undefined;
|
||||||
this.resource.volume.setVolumeLogarithmic(this.volume / 100);
|
public get resource(): AudioResource<Track> | undefined {
|
||||||
|
return this.playerResource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private resourceVolume = 100;
|
||||||
|
public get volume(): number {
|
||||||
|
return this.resourceVolume;
|
||||||
|
}
|
||||||
|
public set volume(x: number) {
|
||||||
|
this.resourceVolume = Math.max(0, Math.min(150, x));
|
||||||
|
|
||||||
|
if (this.playerResource && this.playerResource.volume) {
|
||||||
|
this.playerResource.volume.setVolumeLogarithmic(this.resourceVolume / 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly queue = new Queue();
|
||||||
|
|
||||||
|
public readonly filter = new Filter();
|
||||||
|
|
||||||
|
public readonly created: Date = new Date();
|
||||||
|
|
||||||
public constructor(guild: Guild, music: Music, textChannel: TextChannel) {
|
public constructor(guild: Guild, music: Music, textChannel: TextChannel) {
|
||||||
this.music = music;
|
this.music = music;
|
||||||
this.guild = guild;
|
this.guild = guild;
|
||||||
this.channel = textChannel;
|
this.msgChannel = textChannel;
|
||||||
|
|
||||||
this.player = createAudioPlayer();
|
this.internalPlayer = createAudioPlayer();
|
||||||
|
|
||||||
this.player.on(AudioPlayerStatus.Idle, async () => this.resourceEndResolvable());
|
this.internalPlayer.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",
|
||||||
@@ -94,37 +88,46 @@ export default class Player {
|
|||||||
encoderArgs: !this.filter.empty() ? ["-af", this.filter.toString()] : undefined
|
encoderArgs: !this.filter.empty() ? ["-af", this.filter.toString()] : undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
this.resource = createAudioResource(stream, {
|
this.playerResource = createAudioResource(stream, {
|
||||||
inlineVolume: true,
|
inlineVolume: true,
|
||||||
metadata: track
|
metadata: track
|
||||||
});
|
});
|
||||||
|
|
||||||
if (seek) this.resource.playbackDuration = seek;
|
if (seek) {
|
||||||
if (this.resource.volume) this.resource.volume.setVolumeLogarithmic(this.volume / 100);
|
this.playerResource.playbackDuration = seek;
|
||||||
|
}
|
||||||
|
|
||||||
return this.resource;
|
if (this.playerResource.volume) {
|
||||||
|
this.playerResource.volume.setVolumeLogarithmic(this.resourceVolume / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.playerResource;
|
||||||
}
|
}
|
||||||
|
|
||||||
private resourceEndResolvable(): void {
|
private resourceEndResolvable(): void {
|
||||||
if (this.resource) this.resource.encoder?.destroy();
|
if (this.playerResource) {
|
||||||
|
this.playerResource.encoder?.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.queue.next()) {
|
if (this.queue.next()) {
|
||||||
this.play();
|
this.play();
|
||||||
} else {
|
} else {
|
||||||
this.resource = undefined;
|
this.playerResource = undefined;
|
||||||
this.player.stop();
|
this.internalPlayer.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.msgChannel.send({
|
||||||
embeds: [I18n.en.nowPlaying(track)]
|
embeds: [I18n.embeds.nowPlaying(track)]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
@@ -134,9 +137,11 @@ export default class Player {
|
|||||||
defaultSearch: "ytsearch"
|
defaultSearch: "ytsearch"
|
||||||
});
|
});
|
||||||
|
|
||||||
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) {
|
||||||
@@ -164,34 +169,38 @@ export default class Player {
|
|||||||
public play(seek?: number): void {
|
public play(seek?: number): void {
|
||||||
const track = this.queue.current();
|
const track = this.queue.current();
|
||||||
|
|
||||||
if (!this.connection || !track) return;
|
if (!this.internalConnection || !track) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.player.play(this.resourceGenerator(track, seek));
|
this.internalPlayer.play(this.resourceGenerator(track, seek));
|
||||||
|
|
||||||
this.connection.subscribe(this.player);
|
this.internalConnection.subscribe(this.internalPlayer);
|
||||||
|
|
||||||
this.messageResolvable(track);
|
this.messageResolvable(track);
|
||||||
}
|
}
|
||||||
|
|
||||||
public connect(channel: VoiceBasedChannel): void {
|
public connect(channel: VoiceBasedChannel): void {
|
||||||
if (this.connection) this.connection.destroy();
|
if (this.internalConnection) {
|
||||||
|
this.internalConnection.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
this.connection = joinVoiceChannel({
|
this.internalConnection = joinVoiceChannel({
|
||||||
channelId: channel.id,
|
channelId: channel.id,
|
||||||
guildId: channel.guildId,
|
guildId: channel.guildId,
|
||||||
adapterCreator: channel.guild.voiceAdapterCreator as DiscordGatewayAdapterCreator
|
adapterCreator: channel.guild.voiceAdapterCreator as DiscordGatewayAdapterCreator
|
||||||
});
|
});
|
||||||
|
|
||||||
this.connection.on(VoiceConnectionStatus.Disconnected, async () => {
|
this.internalConnection.on(VoiceConnectionStatus.Disconnected, async () => {
|
||||||
try {
|
try {
|
||||||
await Promise.race([
|
await Promise.race([
|
||||||
entersState(
|
entersState(
|
||||||
this.connection as VoiceConnection,
|
this.internalConnection as VoiceConnection,
|
||||||
VoiceConnectionStatus.Signalling,
|
VoiceConnectionStatus.Signalling,
|
||||||
10_000
|
10_000
|
||||||
),
|
),
|
||||||
entersState(
|
entersState(
|
||||||
this.connection as VoiceConnection,
|
this.internalConnection as VoiceConnection,
|
||||||
VoiceConnectionStatus.Connecting,
|
VoiceConnectionStatus.Connecting,
|
||||||
10_000
|
10_000
|
||||||
)
|
)
|
||||||
@@ -202,10 +211,12 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
this.queue.push(tracks, index);
|
this.queue.push(tracks, index);
|
||||||
|
|
||||||
@@ -217,7 +228,7 @@ export default class Player {
|
|||||||
let diff = 0;
|
let diff = 0;
|
||||||
|
|
||||||
if (typeof query === "string") {
|
if (typeof query === "string") {
|
||||||
this.queue.Tracks.forEach((track, index) => {
|
this.queue.tracks.forEach((track, index) => {
|
||||||
const tempDiff = largestCommonSequence(track.title, query);
|
const tempDiff = largestCommonSequence(track.title, query);
|
||||||
if (diff < tempDiff) {
|
if (diff < tempDiff) {
|
||||||
diff = tempDiff;
|
diff = tempDiff;
|
||||||
@@ -234,14 +245,14 @@ 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;
|
||||||
let diff = 0;
|
let diff = 0;
|
||||||
|
|
||||||
if (typeof query === "string") {
|
if (typeof query === "string") {
|
||||||
this.queue.Tracks.forEach((track, index) => {
|
this.queue.tracks.forEach((track, index) => {
|
||||||
const tempDiff = largestCommonSequence(track.title, query);
|
const tempDiff = largestCommonSequence(track.title, query);
|
||||||
if (diff < tempDiff) {
|
if (diff < tempDiff) {
|
||||||
diff = tempDiff;
|
diff = tempDiff;
|
||||||
@@ -253,7 +264,9 @@ export default class Player {
|
|||||||
const track = this.queue.pop(newPosition);
|
const track = this.queue.pop(newPosition);
|
||||||
|
|
||||||
if (track) {
|
if (track) {
|
||||||
if (oldPosition === newPosition) this.play();
|
if (oldPosition === newPosition) {
|
||||||
|
this.play();
|
||||||
|
}
|
||||||
return track;
|
return track;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,40 +279,50 @@ export default class Player {
|
|||||||
|
|
||||||
public clear(): void {
|
public clear(): void {
|
||||||
this.queue.clear();
|
this.queue.clear();
|
||||||
this.resource = undefined;
|
this.playerResource = undefined;
|
||||||
if (this.player) this.player.stop();
|
|
||||||
|
if (this.internalPlayer) {
|
||||||
|
this.internalPlayer.stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy(): void {
|
public destroy(): void {
|
||||||
if (this.connection) this.connection.destroy();
|
if (this.internalConnection) {
|
||||||
this.player.stop();
|
this.internalConnection.destroy();
|
||||||
this.music.del(this.guild.id);
|
}
|
||||||
|
|
||||||
|
this.internalPlayer.stop();
|
||||||
|
this.music.delete(this.guild.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public pause(): boolean {
|
public pause(): boolean {
|
||||||
if (this.player.state.status === AudioPlayerStatus.Paused) {
|
if (this.internalPlayer.state.status === AudioPlayerStatus.Paused) {
|
||||||
this.player.unpause();
|
this.internalPlayer.unpause();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
this.player.pause();
|
this.internalPlayer.pause();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public shuffle(): void {
|
public shuffle(): void {
|
||||||
this.queue.shuffle();
|
this.queue.shuffle();
|
||||||
if (this.resource) {
|
|
||||||
this.player.stop();
|
if (this.playerResource) {
|
||||||
|
this.internalPlayer.stop();
|
||||||
this.play();
|
this.play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public skip(): void {
|
public skip(): void {
|
||||||
this.player.stop();
|
this.internalPlayer.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public setFilter(filter?: string): void {
|
public setFilter(filter?: string): void {
|
||||||
this.filter.toggle(filter);
|
this.filter.toggle(filter);
|
||||||
if (this.resource) this.play(this.resource.playbackDuration);
|
|
||||||
|
if (this.playerResource) {
|
||||||
|
this.play(this.playerResource.playbackDuration);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
import { LoopOption, Track } from "./types.js";
|
||||||
|
|
||||||
|
export default class Queue {
|
||||||
|
private entries: Track[] = [];
|
||||||
|
public get tracks(): Track[] {
|
||||||
|
return this.entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private currentIndex = 0;
|
||||||
|
public get position(): number {
|
||||||
|
return this.currentIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private loopStatus: LoopOption = "NONE";
|
||||||
|
public get loop(): LoopOption {
|
||||||
|
return this.loopStatus;
|
||||||
|
}
|
||||||
|
public set loop(x: LoopOption) {
|
||||||
|
this.loopStatus = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
private overallDuration = 0;
|
||||||
|
public get duration(): number {
|
||||||
|
return this.overallDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public next(): Track | undefined {
|
||||||
|
if (this.loopStatus === "SONG") {
|
||||||
|
return this.current();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.loopStatus === "AUTO") {
|
||||||
|
this.currentIndex = Math.floor(Math.random() * this.entries.length);
|
||||||
|
return this.current();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentIndex + 1 < this.entries.length) {
|
||||||
|
this.currentIndex++;
|
||||||
|
return this.current();
|
||||||
|
} else if (this.loopStatus === "LOOP") {
|
||||||
|
this.currentIndex = 0;
|
||||||
|
return this.current();
|
||||||
|
} else if (this.loopStatus === "NONE") {
|
||||||
|
this.currentIndex = 0;
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public shuffle(): void {
|
||||||
|
let currentIndex = this.entries.length;
|
||||||
|
|
||||||
|
while (currentIndex != 0) {
|
||||||
|
const randomIndex = Math.floor(Math.random() * currentIndex);
|
||||||
|
currentIndex--;
|
||||||
|
|
||||||
|
[this.entries[currentIndex], this.entries[randomIndex]] = [
|
||||||
|
this.entries[randomIndex],
|
||||||
|
this.entries[currentIndex]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public push(tracks: Track[], index?: number): Track {
|
||||||
|
if (!index || index >= this.entries.length) {
|
||||||
|
this.entries = this.entries.concat(tracks);
|
||||||
|
} else {
|
||||||
|
this.entries.splice(Math.max(0, index), 0, ...tracks);
|
||||||
|
if (this.currentIndex >= index) {
|
||||||
|
this.currentIndex += tracks.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const track of tracks) {
|
||||||
|
this.overallDuration += track.duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.entries[index || this.entries.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public pop(query: number): Track | undefined {
|
||||||
|
if (query >= 0 && query < this.entries.length) {
|
||||||
|
const track = this.entries[query];
|
||||||
|
|
||||||
|
this.entries.splice(query, 1);
|
||||||
|
this.currentIndex = Math.max(0, this.currentIndex - 1);
|
||||||
|
this.overallDuration -= track.duration;
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public jump(query: number): Track | undefined {
|
||||||
|
if (query >= 0 && query < this.entries.length) {
|
||||||
|
const track = this.entries[query];
|
||||||
|
this.currentIndex = query;
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public current(): Track {
|
||||||
|
return this.entries[this.currentIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
public empty(): boolean {
|
||||||
|
return this.entries.length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear(): void {
|
||||||
|
this.entries = [];
|
||||||
|
this.currentIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,15 @@
|
|||||||
|
import path from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
|
import Music from "./lib/music.js";
|
||||||
|
|
||||||
|
const name = "music";
|
||||||
|
const description = "Music module";
|
||||||
|
const author = "unknowableshade";
|
||||||
|
const tag = "2302.1";
|
||||||
|
|
||||||
|
const controller = new Music();
|
||||||
|
|
||||||
|
const rootDir = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
export { author, controller, description, name, rootDir, tag };
|
||||||
+1
-1
@@ -15,5 +15,5 @@
|
|||||||
"skipLibCheck": true
|
"skipLibCheck": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"],
|
"include": ["src/**/*"],
|
||||||
"exclude": ["node_modules", "dist"]
|
"exclude": ["node_modules", "dist", "src/commands", "src/events"]
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user