feat: Music support (#62)
Closes #9 via [discord-player](https://github.com/Androz2091/discord-player/) Co-authored-by: Mylloon <kennel.anri@tutanota.com> Reviewed-on: #62
This commit is contained in:
parent
70237b56c0
commit
2770bd15bb
25 changed files with 2328 additions and 367 deletions
|
@ -12,10 +12,10 @@ WORKDIR /app
|
||||||
|
|
||||||
COPY --chown=node:node . .
|
COPY --chown=node:node . .
|
||||||
|
|
||||||
RUN npm ci --only=production
|
RUN npm ci --only=production --legacy-peer-deps
|
||||||
RUN npx tsc
|
RUN npx tsc
|
||||||
|
|
||||||
RUN rm -r src/ tsconfig.json
|
RUN rm -r src/ tsconfig.json
|
||||||
RUN npm uninstall typescript @types/sqlite3
|
RUN npm uninstall typescript @types/sqlite3 --legacy-peer-deps
|
||||||
|
|
||||||
CMD ["dumb-init", "node", "./dist/index.js"]
|
CMD ["dumb-init", "node", "./dist/index.js"]
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
> Installer les dépendences du bot.
|
> Installer les dépendences du bot.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install --legacy-peer-deps
|
||||||
```
|
```
|
||||||
|
|
||||||
> Lancer le bot.
|
> Lancer le bot.
|
||||||
|
|
1674
package-lock.json
generated
1674
package-lock.json
generated
File diff suppressed because it is too large
Load diff
17
package.json
17
package.json
|
@ -16,13 +16,20 @@
|
||||||
"author": "La confrérie du Kassoulait",
|
"author": "La confrérie du Kassoulait",
|
||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordjs/rest": "^1.1.0",
|
"@discord-player/extractor": "^4.0.0",
|
||||||
|
"@discordjs/opus": "^0.9.0",
|
||||||
|
"@discordjs/rest": "^1.5.0",
|
||||||
"@types/sqlite3": "^3.1.8",
|
"@types/sqlite3": "^3.1.8",
|
||||||
"@types/uuid": "^9.0.0",
|
"@types/uuid": "^9.0.0",
|
||||||
"discord-api-types": "^0.36.3",
|
"discord-api-types": "^0.37.32",
|
||||||
"discord.js": "^14.3.0",
|
"discord-player": "^5.4.1-dev.0",
|
||||||
"sqlite3": "^5.0.11",
|
"discord.js": "^14.7.1",
|
||||||
"typescript": "^4.7.4",
|
"ffmpeg-static": "^5.1.0",
|
||||||
|
"node-fetch": "^2.6.9",
|
||||||
|
"play-dl": "^1.9.6",
|
||||||
|
"prism-media": "^1.3.4",
|
||||||
|
"sqlite3": "^5.1.4",
|
||||||
|
"typescript": "^4.9.5",
|
||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
72
src/buttons/music/queueList-next.ts
Normal file
72
src/buttons/music/queueList-next.ts
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import {
|
||||||
|
ActionRowBuilder,
|
||||||
|
ButtonBuilder,
|
||||||
|
ButtonStyle,
|
||||||
|
Client,
|
||||||
|
EmbedBuilder,
|
||||||
|
MessageComponentInteraction,
|
||||||
|
} from "discord.js";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
import { getLocale } from "../../utils/locales";
|
||||||
|
import { getFilename } from "../../utils/misc";
|
||||||
|
import { embedListQueue } from "../../utils/music";
|
||||||
|
import { collect } from "../loader";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: {
|
||||||
|
name: getFilename(__filename),
|
||||||
|
},
|
||||||
|
interaction: async (interaction: MessageComponentInteraction, client: Client) => {
|
||||||
|
const loc = getLocale(client, interaction.locale);
|
||||||
|
const embed_desc = interaction.message.embeds.at(0)?.author?.name as string;
|
||||||
|
|
||||||
|
// Retrieve Pages
|
||||||
|
const pageMax = Number(/(\d+)(?!.*\d)/gm.exec(embed_desc)?.[0]);
|
||||||
|
let page = Number(/(?!• \s+)\d(?=\/)/gm.exec(embed_desc)?.[0]);
|
||||||
|
if (page + 1 > pageMax) {
|
||||||
|
page = 1;
|
||||||
|
} else {
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get queue
|
||||||
|
const queue = client.player.queues.get(interaction.guildId ?? "");
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder();
|
||||||
|
const rows = [];
|
||||||
|
if (queue) {
|
||||||
|
// Create the embed
|
||||||
|
embedListQueue(client, embed, queue.tracks, page, interaction.locale);
|
||||||
|
|
||||||
|
// Create buttons
|
||||||
|
const idPrec = "queueList-prec_" + uuidv4();
|
||||||
|
const idNext = "queueList-next_" + uuidv4();
|
||||||
|
rows.push(
|
||||||
|
new ActionRowBuilder<ButtonBuilder>()
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(idPrec)
|
||||||
|
.setLabel(loc.get("c_queue8"))
|
||||||
|
.setStyle(ButtonStyle.Primary)
|
||||||
|
)
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(idNext)
|
||||||
|
.setLabel(loc.get("c_queue9"))
|
||||||
|
.setStyle(ButtonStyle.Primary)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Buttons interactions
|
||||||
|
collect(client, interaction, idPrec);
|
||||||
|
collect(client, interaction, idNext);
|
||||||
|
} else {
|
||||||
|
// In case queue doesn't exists
|
||||||
|
embed.setDescription(loc.get("c_queue2"));
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
embeds: [embed],
|
||||||
|
components: rows,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
72
src/buttons/music/queueList-prec.ts
Normal file
72
src/buttons/music/queueList-prec.ts
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import {
|
||||||
|
ActionRowBuilder,
|
||||||
|
ButtonBuilder,
|
||||||
|
ButtonStyle,
|
||||||
|
Client,
|
||||||
|
EmbedBuilder,
|
||||||
|
MessageComponentInteraction,
|
||||||
|
} from "discord.js";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
import { getLocale } from "../../utils/locales";
|
||||||
|
import { getFilename } from "../../utils/misc";
|
||||||
|
import { embedListQueue } from "../../utils/music";
|
||||||
|
import { collect } from "../loader";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: {
|
||||||
|
name: getFilename(__filename),
|
||||||
|
},
|
||||||
|
interaction: async (interaction: MessageComponentInteraction, client: Client) => {
|
||||||
|
const loc = getLocale(client, interaction.locale);
|
||||||
|
const embed_desc = interaction.message.embeds.at(0)?.author?.name as string;
|
||||||
|
|
||||||
|
// Retrieve Pages
|
||||||
|
const pageMax = Number(/(\d+)(?!.*\d)/gm.exec(embed_desc)?.[0]);
|
||||||
|
let page = Number(/(?!• \s+)\d(?=\/)/gm.exec(embed_desc)?.[0]);
|
||||||
|
if (page - 1 == 0) {
|
||||||
|
page = pageMax;
|
||||||
|
} else {
|
||||||
|
page--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get queue
|
||||||
|
const queue = client.player.queues.get(interaction.guildId ?? "");
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder();
|
||||||
|
const rows = [];
|
||||||
|
if (queue) {
|
||||||
|
// Create the embed
|
||||||
|
embedListQueue(client, embed, queue.tracks, page, interaction.locale);
|
||||||
|
|
||||||
|
// Create buttons
|
||||||
|
const idPrec = "queueList-prec_" + uuidv4();
|
||||||
|
const idNext = "queueList-next_" + uuidv4();
|
||||||
|
rows.push(
|
||||||
|
new ActionRowBuilder<ButtonBuilder>()
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(idPrec)
|
||||||
|
.setLabel(loc.get("c_queue8"))
|
||||||
|
.setStyle(ButtonStyle.Primary)
|
||||||
|
)
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(idNext)
|
||||||
|
.setLabel(loc.get("c_queue9"))
|
||||||
|
.setStyle(ButtonStyle.Primary)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Buttons interactions
|
||||||
|
collect(client, interaction, idPrec);
|
||||||
|
collect(client, interaction, idNext);
|
||||||
|
} else {
|
||||||
|
// In case queue doesn't exists
|
||||||
|
embed.setDescription(loc.get("c_queue2"));
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
embeds: [embed],
|
||||||
|
components: rows,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
|
@ -51,6 +51,7 @@ export default {
|
||||||
|
|
||||||
// Load all the command per categories
|
// Load all the command per categories
|
||||||
// TODO: Check if the command exist in the context (guild)
|
// TODO: Check if the command exist in the context (guild)
|
||||||
|
// TODO: List subcommands too
|
||||||
client.commands.categories.forEach((commands_name, category) => {
|
client.commands.categories.forEach((commands_name, category) => {
|
||||||
const commands = commands_name.reduce((data, command_name) => {
|
const commands = commands_name.reduce((data, command_name) => {
|
||||||
return data + `\`/${command_name}\`, `;
|
return data + `\`/${command_name}\`, `;
|
||||||
|
|
120
src/commands/music/lyrics.ts
Normal file
120
src/commands/music/lyrics.ts
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
import { SlashCommandBuilder } from "@discordjs/builders";
|
||||||
|
import { ChatInputCommandInteraction, Client, EmbedBuilder } from "discord.js";
|
||||||
|
import { getLocale, getLocalizations } from "../../utils/locales";
|
||||||
|
import { getFilename } from "../../utils/misc";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
scope: () => [],
|
||||||
|
|
||||||
|
data: (client: Client) => {
|
||||||
|
const filename = getFilename(__filename);
|
||||||
|
const loc_default = client.locales.get(client.config.default_lang);
|
||||||
|
if (!loc_default) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
new SlashCommandBuilder()
|
||||||
|
.setName(filename.toLowerCase())
|
||||||
|
.setDescription(loc_default.get(`c_${filename}_desc`) ?? "")
|
||||||
|
.setNameLocalizations(getLocalizations(client, `c_${filename}_name`, true))
|
||||||
|
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_desc`))
|
||||||
|
|
||||||
|
// Command option
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName(loc_default.get(`c_${filename}_opt1_name`)?.toLowerCase() ?? "")
|
||||||
|
.setDescription(loc_default.get(`c_${filename}_opt1_desc`) ?? "")
|
||||||
|
.setNameLocalizations(getLocalizations(client, `c_${filename}_opt1_name`, true))
|
||||||
|
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_opt1_desc`))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
interaction: async (interaction: ChatInputCommandInteraction, client: Client) => {
|
||||||
|
const loc_default = client.locales.get(client.config.default_lang);
|
||||||
|
const filename = getFilename(__filename);
|
||||||
|
|
||||||
|
const loc = getLocale(client, interaction.locale);
|
||||||
|
|
||||||
|
const request = interaction.options.getString(
|
||||||
|
loc_default?.get(`c_${filename}_opt1_name`) as string
|
||||||
|
);
|
||||||
|
|
||||||
|
let data = null;
|
||||||
|
if (request) {
|
||||||
|
try {
|
||||||
|
data = await client.player.lyrics.search(request);
|
||||||
|
} catch {
|
||||||
|
return await interaction.reply(loc.get("c_lyrics2") + ` \`${request}\``);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const queue = client.player.queues.get(interaction.guildId ?? "");
|
||||||
|
if (queue) {
|
||||||
|
const title = queue.current?.title;
|
||||||
|
if (title) {
|
||||||
|
try {
|
||||||
|
data = await client.player.lyrics.search(title);
|
||||||
|
} catch {
|
||||||
|
return await interaction.reply(loc.get("c_lyrics2") + ` \`${title}\``);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
const limit_desc = 4096;
|
||||||
|
const nb_embed = Math.ceil(data.lyrics.length / limit_desc);
|
||||||
|
|
||||||
|
// May send multiples message
|
||||||
|
await interaction.deferReply();
|
||||||
|
|
||||||
|
// TODO: If lyrics < 6000, only send one message with multiples embed
|
||||||
|
for (let i = 0, j = 0; i < nb_embed; i++, j += limit_desc) {
|
||||||
|
// TODO: Better cut in lyrics
|
||||||
|
const lyrics = data.lyrics.slice(j, j + limit_desc);
|
||||||
|
|
||||||
|
let embed;
|
||||||
|
switch (i) {
|
||||||
|
case 0: {
|
||||||
|
// First embed
|
||||||
|
embed = new EmbedBuilder();
|
||||||
|
embed
|
||||||
|
.setTitle(data.title)
|
||||||
|
.setURL(data.url)
|
||||||
|
.setAuthor({
|
||||||
|
name: data?.artist.name,
|
||||||
|
iconURL: data?.artist.image,
|
||||||
|
url: data?.artist.url,
|
||||||
|
})
|
||||||
|
.setDescription(lyrics)
|
||||||
|
.setThumbnail(data.thumbnail);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case nb_embed - 1: {
|
||||||
|
// Footer of last embed in case of multiple embed
|
||||||
|
embed = new EmbedBuilder().setDescription(lyrics).setFooter({
|
||||||
|
text: `${data?.artist.name} · ${data.title}`,
|
||||||
|
iconURL: data?.artist.image,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
// Embed with only lyrics in case of multiple embed
|
||||||
|
embed = new EmbedBuilder().setDescription(lyrics);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send embed
|
||||||
|
await interaction.followUp({ embeds: [embed] });
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await interaction.reply(loc.get("c_lyrics1"));
|
||||||
|
},
|
||||||
|
};
|
44
src/commands/music/pause.ts
Normal file
44
src/commands/music/pause.ts
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import { SlashCommandBuilder } from "@discordjs/builders";
|
||||||
|
import { ChatInputCommandInteraction, Client } from "discord.js";
|
||||||
|
import { getLocale, getLocalizations } from "../../utils/locales";
|
||||||
|
import { getFilename } from "../../utils/misc";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
scope: () => [],
|
||||||
|
|
||||||
|
data: (client: Client) => {
|
||||||
|
const filename = getFilename(__filename);
|
||||||
|
const loc_default = client.locales.get(client.config.default_lang);
|
||||||
|
if (!loc_default) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SlashCommandBuilder()
|
||||||
|
.setName(filename.toLowerCase())
|
||||||
|
.setDescription(loc_default.get(`c_${filename}_desc`) ?? "")
|
||||||
|
.setNameLocalizations(getLocalizations(client, `c_${filename}_name`, true))
|
||||||
|
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_desc`));
|
||||||
|
},
|
||||||
|
|
||||||
|
interaction: async (interaction: ChatInputCommandInteraction, client: Client) => {
|
||||||
|
const loc = getLocale(client, interaction.locale);
|
||||||
|
const queue = client.player.queues.get(interaction.guildId ?? "");
|
||||||
|
|
||||||
|
if (queue) {
|
||||||
|
if (queue.paused) {
|
||||||
|
queue.resume();
|
||||||
|
|
||||||
|
// TODO: Pretty embed
|
||||||
|
return await interaction.reply(loc.get("c_pause1"));
|
||||||
|
} else {
|
||||||
|
queue.pause();
|
||||||
|
|
||||||
|
// TODO: Pretty embed
|
||||||
|
return await interaction.reply(loc.get("c_pause2"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Pretty embed
|
||||||
|
return await interaction.reply(loc.get("c_pause3"));
|
||||||
|
},
|
||||||
|
};
|
140
src/commands/music/play.ts
Normal file
140
src/commands/music/play.ts
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
import { SlashCommandBuilder } from "@discordjs/builders";
|
||||||
|
import {
|
||||||
|
ChatInputCommandInteraction,
|
||||||
|
Client,
|
||||||
|
GuildResolvable,
|
||||||
|
VoiceBasedChannel,
|
||||||
|
} from "discord.js";
|
||||||
|
import { Metadata } from "../../utils/metadata";
|
||||||
|
import { getLocale, getLocalizations } from "../../utils/locales";
|
||||||
|
import { getFilename } from "../../utils/misc";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
scope: () => [],
|
||||||
|
|
||||||
|
data: (client: Client) => {
|
||||||
|
const filename = getFilename(__filename);
|
||||||
|
const loc_default = client.locales.get(client.config.default_lang);
|
||||||
|
if (!loc_default) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
new SlashCommandBuilder()
|
||||||
|
.setName(filename.toLowerCase())
|
||||||
|
.setDescription(loc_default.get(`c_${filename}_desc`) ?? "")
|
||||||
|
.setNameLocalizations(getLocalizations(client, `c_${filename}_name`, true))
|
||||||
|
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_desc`))
|
||||||
|
|
||||||
|
// Command option
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName(loc_default.get(`c_${filename}_opt1_name`)?.toLowerCase() ?? "")
|
||||||
|
.setDescription(loc_default.get(`c_${filename}_opt1_desc`) ?? "")
|
||||||
|
.setNameLocalizations(getLocalizations(client, `c_${filename}_opt1_name`, true))
|
||||||
|
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_opt1_desc`))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
interaction: async (interaction: ChatInputCommandInteraction, client: Client) => {
|
||||||
|
const loc = getLocale(client, interaction.locale);
|
||||||
|
const loc_default = client.locales.get(client.config.default_lang);
|
||||||
|
const filename = getFilename(__filename);
|
||||||
|
|
||||||
|
const member = client.guilds.cache
|
||||||
|
.get(interaction.guildId ?? "")
|
||||||
|
?.members.cache.get(interaction.member?.user.id ?? "");
|
||||||
|
|
||||||
|
if (!member?.voice.channelId) {
|
||||||
|
return await interaction.reply({
|
||||||
|
content: loc.get("c_play1"),
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
interaction.guild?.members.me?.voice.channelId &&
|
||||||
|
member.voice.channelId !== interaction.guild.members.me.voice.channelId
|
||||||
|
) {
|
||||||
|
return await interaction.reply({
|
||||||
|
content: loc.get("c_play2"),
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = interaction.options.getString(
|
||||||
|
loc_default?.get(`c_${filename}_opt1_name`) as string
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!query) {
|
||||||
|
// Now playing
|
||||||
|
|
||||||
|
const queue = client.player.queues.get(interaction.guildId ?? "");
|
||||||
|
|
||||||
|
if (queue) {
|
||||||
|
const track = queue.nowPlaying();
|
||||||
|
if (track) {
|
||||||
|
// TODO: Pretty embed
|
||||||
|
// Use: createProgressBar
|
||||||
|
return await interaction.reply(
|
||||||
|
`${loc.get("c_play7")} \`${track.title}\` - *${track.author}*`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Pretty embed
|
||||||
|
return await interaction.reply(loc.get("c_play6"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const queue = client.player.createQueue(interaction.guild as GuildResolvable, {
|
||||||
|
metadata: {
|
||||||
|
channel: interaction.channel,
|
||||||
|
} as Metadata,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify vc connection
|
||||||
|
try {
|
||||||
|
if (!queue.connection) await queue.connect(member.voice.channel as VoiceBasedChannel);
|
||||||
|
} catch {
|
||||||
|
queue.destroy();
|
||||||
|
return await interaction.reply({
|
||||||
|
content: loc.get("c_play3"),
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await interaction.deferReply();
|
||||||
|
const result = await client.player
|
||||||
|
.search(query, {
|
||||||
|
requestedBy: interaction.user,
|
||||||
|
})
|
||||||
|
.then((x) => x);
|
||||||
|
|
||||||
|
if (!result.tracks[0]) {
|
||||||
|
// TODO: Pretty embed
|
||||||
|
return await interaction.followUp({ content: `❌ | \`${query}\` ${loc.get("c_play4")}.` });
|
||||||
|
}
|
||||||
|
|
||||||
|
let title;
|
||||||
|
if (result.playlist) {
|
||||||
|
queue.addTracks(result.playlist.tracks);
|
||||||
|
title = result.playlist.title;
|
||||||
|
} else {
|
||||||
|
// TODO: Ask user which result to choose
|
||||||
|
const track = result.tracks[0];
|
||||||
|
|
||||||
|
queue.addTrack(track);
|
||||||
|
title = track.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!queue.playing) {
|
||||||
|
queue.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Pretty embed
|
||||||
|
return await interaction.followUp({
|
||||||
|
content: `⏱️ | \`${title}\` ${loc.get("c_play5")}.`,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
170
src/commands/music/queue.ts
Normal file
170
src/commands/music/queue.ts
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
import { SlashCommandBuilder } from "@discordjs/builders";
|
||||||
|
import {
|
||||||
|
ActionRowBuilder,
|
||||||
|
ButtonBuilder,
|
||||||
|
ButtonStyle,
|
||||||
|
ChatInputCommandInteraction,
|
||||||
|
Client,
|
||||||
|
EmbedBuilder,
|
||||||
|
} from "discord.js";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
import { collect } from "../../buttons/loader";
|
||||||
|
import { getLocale, getLocalizations } from "../../utils/locales";
|
||||||
|
import { getFilename } from "../../utils/misc";
|
||||||
|
import { embedListQueue } from "../../utils/music";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
scope: () => [],
|
||||||
|
|
||||||
|
data: (client: Client) => {
|
||||||
|
const filename = getFilename(__filename);
|
||||||
|
const loc_default = client.locales.get(client.config.default_lang);
|
||||||
|
if (!loc_default) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
new SlashCommandBuilder()
|
||||||
|
.setName(filename.toLowerCase())
|
||||||
|
.setDescription(loc_default.get(`c_${filename}_desc`) ?? "")
|
||||||
|
.setNameLocalizations(getLocalizations(client, `c_${filename}_name`, true))
|
||||||
|
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_desc`))
|
||||||
|
|
||||||
|
// Show the queue
|
||||||
|
.addSubcommand((subcommand) =>
|
||||||
|
subcommand
|
||||||
|
.setName(loc_default.get(`c_${filename}_sub1_name`)?.toLowerCase() ?? "")
|
||||||
|
.setDescription(loc_default.get(`c_${filename}_sub1_desc`) ?? "")
|
||||||
|
.setNameLocalizations(getLocalizations(client, `c_${filename}_sub1_name`, true))
|
||||||
|
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_sub1_desc`))
|
||||||
|
|
||||||
|
// Specified Page
|
||||||
|
.addNumberOption((option) =>
|
||||||
|
option
|
||||||
|
.setName(loc_default.get(`c_${filename}_sub1_opt1_name`)?.toLowerCase() ?? "")
|
||||||
|
.setDescription(loc_default.get(`c_${filename}_sub1_opt1_desc`) ?? "")
|
||||||
|
.setNameLocalizations(
|
||||||
|
getLocalizations(client, `c_${filename}_sub1_opt1_name`, true)
|
||||||
|
)
|
||||||
|
.setDescriptionLocalizations(
|
||||||
|
getLocalizations(client, `c_${filename}_sub1_opt1_desc`)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Shuffle Queue
|
||||||
|
.addSubcommand((subcommand) =>
|
||||||
|
subcommand
|
||||||
|
.setName(loc_default.get(`c_${filename}_sub2_name`)?.toLowerCase() ?? "")
|
||||||
|
.setDescription(loc_default.get(`c_${filename}_sub2_desc`) ?? "")
|
||||||
|
.setNameLocalizations(getLocalizations(client, `c_${filename}_sub2_name`, true))
|
||||||
|
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_sub2_desc`))
|
||||||
|
)
|
||||||
|
|
||||||
|
// Remove <ID>
|
||||||
|
.addSubcommand((subcommand) =>
|
||||||
|
subcommand
|
||||||
|
.setName(loc_default.get(`c_${filename}_sub3_name`)?.toLowerCase() ?? "")
|
||||||
|
.setDescription(loc_default.get(`c_${filename}_sub3_desc`) ?? "")
|
||||||
|
.setNameLocalizations(getLocalizations(client, `c_${filename}_sub3_name`, true))
|
||||||
|
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_sub3_desc`))
|
||||||
|
|
||||||
|
// Specified ID
|
||||||
|
// TODO?: ID range -> as a string: 5-8 remove 5, 6, 7, 8
|
||||||
|
.addNumberOption((option) =>
|
||||||
|
option
|
||||||
|
.setName(loc_default.get(`c_${filename}_sub3_opt1_name`)?.toLowerCase() ?? "")
|
||||||
|
.setDescription(loc_default.get(`c_${filename}_sub3_opt1_desc`) ?? "")
|
||||||
|
.setNameLocalizations(
|
||||||
|
getLocalizations(client, `c_${filename}_sub3_opt1_name`, true)
|
||||||
|
)
|
||||||
|
.setDescriptionLocalizations(
|
||||||
|
getLocalizations(client, `c_${filename}_sub3_opt1_desc`)
|
||||||
|
)
|
||||||
|
.setRequired(true)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
interaction: async (interaction: ChatInputCommandInteraction, client: Client) => {
|
||||||
|
const loc_default = client.locales.get(client.config.default_lang);
|
||||||
|
const filename = getFilename(__filename);
|
||||||
|
|
||||||
|
const loc = getLocale(client, interaction.locale);
|
||||||
|
const queue = client.player.queues.get(interaction.guildId ?? "");
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder();
|
||||||
|
const rows = [];
|
||||||
|
|
||||||
|
if (queue) {
|
||||||
|
const subcommand = interaction.options.getSubcommand();
|
||||||
|
switch (subcommand) {
|
||||||
|
// Show the queue
|
||||||
|
case loc_default?.get(`c_${filename}_sub1_name`)?.toLowerCase() ?? "": {
|
||||||
|
const page =
|
||||||
|
interaction.options.getNumber(
|
||||||
|
loc_default?.get(`c_${filename}_sub1_opt1_name`) as string
|
||||||
|
) ?? 1;
|
||||||
|
|
||||||
|
embedListQueue(client, embed, queue.tracks, page, interaction.locale);
|
||||||
|
|
||||||
|
const idPrec = "queueList-prec_" + uuidv4();
|
||||||
|
const idNext = "queueList-next_" + uuidv4();
|
||||||
|
rows.push(
|
||||||
|
new ActionRowBuilder<ButtonBuilder>()
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(idPrec)
|
||||||
|
.setLabel(loc.get(`c_${filename}8`))
|
||||||
|
.setStyle(ButtonStyle.Primary)
|
||||||
|
)
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(idNext)
|
||||||
|
.setLabel(loc.get(`c_${filename}9`))
|
||||||
|
.setStyle(ButtonStyle.Primary)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Buttons interactions
|
||||||
|
collect(client, interaction, idPrec);
|
||||||
|
collect(client, interaction, idNext);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shuffle Queue
|
||||||
|
case loc_default?.get(`c_${filename}_sub2_name`)?.toLowerCase() ?? "": {
|
||||||
|
queue.shuffle();
|
||||||
|
|
||||||
|
embed.setDescription(loc.get("c_queue3"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove <ID>
|
||||||
|
case loc_default?.get(`c_${filename}_sub3_name`)?.toLowerCase() ?? "": {
|
||||||
|
const id = interaction.options.getNumber(
|
||||||
|
loc_default?.get(`c_${filename}_sub3_opt1_name`) as string
|
||||||
|
) as number;
|
||||||
|
|
||||||
|
const track = queue.remove(id - 1);
|
||||||
|
|
||||||
|
if (track) {
|
||||||
|
embed.setDescription(
|
||||||
|
`${loc.get("c_queue4")} #${id} \`${track.title}\` ${loc.get("c_queue5")}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
embed.setDescription(loc.get("c_queue6"));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
embed.setDescription(loc.get("c_queue2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return await interaction.reply({ embeds: [embed], components: rows });
|
||||||
|
},
|
||||||
|
};
|
102
src/commands/music/repeat.ts
Normal file
102
src/commands/music/repeat.ts
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
import { SlashCommandBuilder } from "@discordjs/builders";
|
||||||
|
import { QueueRepeatMode } from "discord-player";
|
||||||
|
import { ChatInputCommandInteraction, Client } from "discord.js";
|
||||||
|
import { getLocale, getLocalizations } from "../../utils/locales";
|
||||||
|
import { getFilename } from "../../utils/misc";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
scope: () => [],
|
||||||
|
|
||||||
|
data: (client: Client) => {
|
||||||
|
const filename = getFilename(__filename);
|
||||||
|
const loc_default = client.locales.get(client.config.default_lang);
|
||||||
|
if (!loc_default) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
new SlashCommandBuilder()
|
||||||
|
.setName(filename.toLowerCase())
|
||||||
|
.setDescription(loc_default.get(`c_${filename}_desc`) ?? "")
|
||||||
|
.setNameLocalizations(getLocalizations(client, `c_${filename}_name`, true))
|
||||||
|
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_desc`))
|
||||||
|
|
||||||
|
// Disable repeat
|
||||||
|
.addSubcommand((subcommand) =>
|
||||||
|
subcommand
|
||||||
|
.setName(loc_default.get(`c_${filename}_sub1_name`)?.toLowerCase() ?? "")
|
||||||
|
.setDescription(loc_default.get(`c_${filename}_sub1_desc`) ?? "")
|
||||||
|
.setNameLocalizations(getLocalizations(client, `c_${filename}_sub1_name`, true))
|
||||||
|
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_sub1_desc`))
|
||||||
|
)
|
||||||
|
|
||||||
|
// Repeat current track
|
||||||
|
.addSubcommand((subcommand) =>
|
||||||
|
subcommand
|
||||||
|
.setName(loc_default.get(`c_${filename}_sub2_name`)?.toLowerCase() ?? "")
|
||||||
|
.setDescription(loc_default.get(`c_${filename}_sub2_desc`) ?? "")
|
||||||
|
.setNameLocalizations(getLocalizations(client, `c_${filename}_sub2_name`, true))
|
||||||
|
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_sub2_desc`))
|
||||||
|
)
|
||||||
|
|
||||||
|
// Repeat queue
|
||||||
|
.addSubcommand((subcommand) =>
|
||||||
|
subcommand
|
||||||
|
.setName(loc_default.get(`c_${filename}_sub3_name`)?.toLowerCase() ?? "")
|
||||||
|
.setDescription(loc_default.get(`c_${filename}_sub3_desc`) ?? "")
|
||||||
|
.setNameLocalizations(getLocalizations(client, `c_${filename}_sub3_name`, true))
|
||||||
|
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_sub3_desc`))
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enable autoplay
|
||||||
|
.addSubcommand((subcommand) =>
|
||||||
|
subcommand
|
||||||
|
.setName(loc_default.get(`c_${filename}_sub4_name`)?.toLowerCase() ?? "")
|
||||||
|
.setDescription(loc_default.get(`c_${filename}_sub4_desc`) ?? "")
|
||||||
|
.setNameLocalizations(getLocalizations(client, `c_${filename}_sub4_name`, true))
|
||||||
|
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_sub4_desc`))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
interaction: async (interaction: ChatInputCommandInteraction, client: Client) => {
|
||||||
|
const loc_default = client.locales.get(client.config.default_lang);
|
||||||
|
const filename = getFilename(__filename);
|
||||||
|
|
||||||
|
const loc = getLocale(client, interaction.locale);
|
||||||
|
const queue = client.player.queues.get(interaction.guildId ?? "");
|
||||||
|
|
||||||
|
if (queue) {
|
||||||
|
const subcommand = interaction.options.getSubcommand();
|
||||||
|
switch (subcommand) {
|
||||||
|
// Disable
|
||||||
|
case loc_default?.get(`c_${filename}_sub1_name`)?.toLowerCase() ?? "": {
|
||||||
|
queue.setRepeatMode(QueueRepeatMode.OFF);
|
||||||
|
return interaction.reply(loc.get("c_repeat2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queue Repeat
|
||||||
|
case loc_default?.get(`c_${filename}_sub3_name`)?.toLowerCase() ?? "": {
|
||||||
|
queue.setRepeatMode(QueueRepeatMode.QUEUE);
|
||||||
|
return interaction.reply(loc.get("c_repeat3") + " " + loc.get("c_repeat6"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Autoplay
|
||||||
|
case loc_default?.get(`c_${filename}_sub4_name`)?.toLowerCase() ?? "": {
|
||||||
|
queue.setRepeatMode(QueueRepeatMode.AUTOPLAY);
|
||||||
|
return interaction.reply(loc.get("c_repeat4") + " " + loc.get("c_repeat6"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track repeat
|
||||||
|
case loc_default?.get(`c_${filename}_sub2_name`)?.toLowerCase() ?? "": {
|
||||||
|
queue.setRepeatMode(QueueRepeatMode.TRACK);
|
||||||
|
return interaction.reply(
|
||||||
|
loc.get("c_repeat5") + ` ${queue.nowPlaying()?.title} ` + loc.get("c_repeat6")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await interaction.reply(loc.get("c_repeat1"));
|
||||||
|
},
|
||||||
|
};
|
58
src/commands/music/skip.ts
Normal file
58
src/commands/music/skip.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import { SlashCommandBuilder } from "@discordjs/builders";
|
||||||
|
import { ChatInputCommandInteraction, Client } from "discord.js";
|
||||||
|
import { getLocale, getLocalizations } from "../../utils/locales";
|
||||||
|
import { getFilename } from "../../utils/misc";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
scope: () => [],
|
||||||
|
|
||||||
|
data: (client: Client) => {
|
||||||
|
const filename = getFilename(__filename);
|
||||||
|
const loc_default = client.locales.get(client.config.default_lang);
|
||||||
|
if (!loc_default) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
new SlashCommandBuilder()
|
||||||
|
.setName(filename.toLowerCase())
|
||||||
|
.setDescription(loc_default.get(`c_${filename}_desc`) ?? "")
|
||||||
|
.setNameLocalizations(getLocalizations(client, `c_${filename}_name`, true))
|
||||||
|
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_desc`))
|
||||||
|
|
||||||
|
// Command option
|
||||||
|
.addNumberOption((option) =>
|
||||||
|
option
|
||||||
|
.setName(loc_default.get(`c_${filename}_opt1_name`)?.toLowerCase() ?? "")
|
||||||
|
.setDescription(loc_default.get(`c_${filename}_opt1_desc`) ?? "")
|
||||||
|
.setNameLocalizations(getLocalizations(client, `c_${filename}_opt1_name`, true))
|
||||||
|
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_opt1_desc`))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
interaction: async (interaction: ChatInputCommandInteraction, client: Client) => {
|
||||||
|
const loc_default = client.locales.get(client.config.default_lang);
|
||||||
|
const filename = getFilename(__filename);
|
||||||
|
|
||||||
|
const loc = getLocale(client, interaction.locale);
|
||||||
|
const queue = client.player.queues.get(interaction.guildId ?? "");
|
||||||
|
|
||||||
|
const id = interaction.options.getNumber(loc_default?.get(`c_${filename}_opt1_name`) as string);
|
||||||
|
|
||||||
|
if (queue) {
|
||||||
|
let msg;
|
||||||
|
if (id) {
|
||||||
|
queue.skipTo(id - 1);
|
||||||
|
msg = loc.get("c_skip3") + " #" + id + "...";
|
||||||
|
} else {
|
||||||
|
queue.skip();
|
||||||
|
msg = loc.get("c_skip1") + "...";
|
||||||
|
}
|
||||||
|
|
||||||
|
return await interaction.reply(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await interaction.reply(loc.get("c_skip2"));
|
||||||
|
},
|
||||||
|
};
|
41
src/commands/music/stop.ts
Normal file
41
src/commands/music/stop.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { SlashCommandBuilder } from "@discordjs/builders";
|
||||||
|
import { ChatInputCommandInteraction, Client, GuildResolvable } from "discord.js";
|
||||||
|
import { Metadata } from "../../utils/metadata";
|
||||||
|
import { getLocale, getLocalizations } from "../../utils/locales";
|
||||||
|
import { getFilename } from "../../utils/misc";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
scope: () => [],
|
||||||
|
|
||||||
|
data: (client: Client) => {
|
||||||
|
const filename = getFilename(__filename);
|
||||||
|
const loc_default = client.locales.get(client.config.default_lang);
|
||||||
|
if (!loc_default) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SlashCommandBuilder()
|
||||||
|
.setName(filename.toLowerCase())
|
||||||
|
.setDescription(loc_default.get(`c_${filename}_desc`) ?? "")
|
||||||
|
.setNameLocalizations(getLocalizations(client, `c_${filename}_name`, true))
|
||||||
|
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_desc`));
|
||||||
|
},
|
||||||
|
|
||||||
|
interaction: async (interaction: ChatInputCommandInteraction, client: Client) => {
|
||||||
|
const loc = getLocale(client, interaction.locale);
|
||||||
|
|
||||||
|
const queue = client.player.createQueue(interaction.guild as GuildResolvable, {
|
||||||
|
metadata: {
|
||||||
|
channel: interaction.channel,
|
||||||
|
} as Metadata,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!(queue.connection || queue.playing)) {
|
||||||
|
return interaction.reply(loc.get("c_stop1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.destroy();
|
||||||
|
|
||||||
|
interaction.reply(loc.get("c_stop2"));
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { PlayerEvents } from "discord-player";
|
||||||
import { Client } from "discord.js";
|
import { Client } from "discord.js";
|
||||||
import { readdir } from "fs/promises";
|
import { readdir } from "fs/promises";
|
||||||
|
|
||||||
|
@ -27,6 +28,19 @@ export default async (client: Client) => {
|
||||||
}
|
}
|
||||||
const event_type = event_type_ext.join(".");
|
const event_type = event_type_ext.join(".");
|
||||||
|
|
||||||
|
if (event_category == "player") {
|
||||||
|
if (once) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
return client.player.once(event_type as keyof PlayerEvents, (...args: any[]) => {
|
||||||
|
execute(...args, client);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line
|
||||||
|
return client.player.on(event_type as keyof PlayerEvents, (...args: any[]) => {
|
||||||
|
execute(...args, client);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (once) {
|
if (once) {
|
||||||
return client.once(event_type, (...args) => {
|
return client.once(event_type, (...args) => {
|
||||||
execute(...args, client);
|
execute(...args, client);
|
||||||
|
|
7
src/events/player/connectionError.ts
Normal file
7
src/events/player/connectionError.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { Queue } from "discord-player";
|
||||||
|
import { Metadata } from "../../utils/metadata";
|
||||||
|
|
||||||
|
/** https://discord-player.js.org/docs/main/master/typedef/PlayerEvents */
|
||||||
|
export default (_: Queue<Metadata>, error: Error) => {
|
||||||
|
console.error(error);
|
||||||
|
};
|
7
src/events/player/error.ts
Normal file
7
src/events/player/error.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { Queue } from "discord-player";
|
||||||
|
import { Metadata } from "../../utils/metadata";
|
||||||
|
|
||||||
|
/** https://discord-player.js.org/docs/main/master/typedef/PlayerEvents */
|
||||||
|
export default (_: Queue<Metadata>, error: Error) => {
|
||||||
|
console.error(error);
|
||||||
|
};
|
7
src/events/player/trackStart.ts
Normal file
7
src/events/player/trackStart.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { Queue, Track } from "discord-player";
|
||||||
|
import { Metadata } from "../../utils/metadata";
|
||||||
|
|
||||||
|
/** https://discord-player.js.org/docs/main/master/typedef/PlayerEvents */
|
||||||
|
export default (queue: Queue<Metadata>, track: Track) => {
|
||||||
|
queue.metadata?.channel?.send(`🎶 | Joue \`${track.title}\` demandé par ${track.requestedBy}.`);
|
||||||
|
};
|
|
@ -11,7 +11,7 @@
|
||||||
"c_help_name": "Aide",
|
"c_help_name": "Aide",
|
||||||
"c_help_desc": "Informations sur les commandes",
|
"c_help_desc": "Informations sur les commandes",
|
||||||
"c_help_opt1_name": "commande",
|
"c_help_opt1_name": "commande",
|
||||||
"c_help_opt1_desc": "Commande voulu en détail.",
|
"c_help_opt1_desc": "Commande voulu en détail",
|
||||||
"c_help1": "Liste des catégories et des commandes associées",
|
"c_help1": "Liste des catégories et des commandes associées",
|
||||||
"c_help2": "`/help <commande>` pour obtenir plus d'informations sur une commande.",
|
"c_help2": "`/help <commande>` pour obtenir plus d'informations sur une commande.",
|
||||||
"c_help3": "Impossible de trouver :",
|
"c_help3": "Impossible de trouver :",
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
"c_prep_name": "Préparation",
|
"c_prep_name": "Préparation",
|
||||||
"c_prep_desc": "Préparation des salons généraux pour la nouvelle année",
|
"c_prep_desc": "Préparation des salons généraux pour la nouvelle année",
|
||||||
"c_prep_opt1_name": "année",
|
"c_prep_opt1_name": "année",
|
||||||
"c_prep_opt1_desc": "Nom de l'année à préparer'",
|
"c_prep_opt1_desc": "Nom de l'année à préparer",
|
||||||
"c_prep1": "Liste des catégories soumis à la préparation",
|
"c_prep1": "Liste des catégories soumis à la préparation",
|
||||||
"c_prep2": "`L1`, `L2`, `L3`, `M1`, `M2`",
|
"c_prep2": "`L1`, `L2`, `L3`, `M1`, `M2`",
|
||||||
"c_prep3": "Impossible de trouver/nettoyer le salon :",
|
"c_prep3": "Impossible de trouver/nettoyer le salon :",
|
||||||
|
@ -76,5 +76,77 @@
|
||||||
"c_reminder15": "Message envoyé en DM car vous avez quitté",
|
"c_reminder15": "Message envoyé en DM car vous avez quitté",
|
||||||
"c_reminder16": "Message envoyé en DM car le serveur Discord n'est plus disponible.",
|
"c_reminder16": "Message envoyé en DM car le serveur Discord n'est plus disponible.",
|
||||||
"c_reminder17": "Message d'il y a",
|
"c_reminder17": "Message d'il y a",
|
||||||
"c_reminder18": "Pas de message"
|
"c_reminder18": "Pas de message",
|
||||||
|
|
||||||
|
"c_play_name": "play",
|
||||||
|
"c_play_desc": "Joue une chanson/playlist, pas de requête affiche la chanson en cours actuellement",
|
||||||
|
"c_play_opt1_name": "requête",
|
||||||
|
"c_play_opt1_desc": "Ce que vous voulez écouter",
|
||||||
|
"c_play1": "Tu n'es dans aucun salon vocal.",
|
||||||
|
"c_play2": "Je suis déjà en vocal.",
|
||||||
|
"c_play3": "Impossible de rejoindre le salon vocal.",
|
||||||
|
"c_play4": "introuvable",
|
||||||
|
"c_play5": "ajouté à la file d'attente",
|
||||||
|
"c_play6": "Le bot ne joue rien en ce moment.",
|
||||||
|
"c_play7": "Joue actuellement",
|
||||||
|
"c_stop_name": "stop",
|
||||||
|
"c_stop_desc": "Stop la musique",
|
||||||
|
"c_stop1": "Le bot ne joue rien en ce moment.",
|
||||||
|
"c_stop2": "La musique à été arrêtée.",
|
||||||
|
"c_pause_name": "pause",
|
||||||
|
"c_pause_desc": "Met en pause ou relance la musique",
|
||||||
|
"c_pause1": "Relance la musique...",
|
||||||
|
"c_pause2": "Met en pause la musique.",
|
||||||
|
"c_pause3": "Le bot ne joue rien en ce moment.",
|
||||||
|
"c_queue_name": "queue",
|
||||||
|
"c_queue_desc": "Commande relative à la file d'attente des musiques",
|
||||||
|
"c_queue_sub1_name": "affiche",
|
||||||
|
"c_queue_sub1_desc": "Affiche la file d'attente des musiques",
|
||||||
|
"c_queue_sub1_opt1_name": "page",
|
||||||
|
"c_queue_sub1_opt1_desc": "Page à afficher",
|
||||||
|
"c_queue_sub2_name": "melange",
|
||||||
|
"c_queue_sub2_desc": "Mélange la file d'attente",
|
||||||
|
"c_queue_sub3_name": "retire",
|
||||||
|
"c_queue_sub3_desc": "Retire une chanson de la file d'attente",
|
||||||
|
"c_queue_sub3_opt1_name": "id",
|
||||||
|
"c_queue_sub3_opt1_desc": "ID de la chanson a retirer",
|
||||||
|
"c_queue1": "File d'attente",
|
||||||
|
"c_queue2": "La liste est vide.",
|
||||||
|
"c_queue3": "Liste d'attente mélangée",
|
||||||
|
"c_queue4": "Musique",
|
||||||
|
"c_queue5": "supprimée",
|
||||||
|
"c_queue6": "Cette ID n'existe pas.",
|
||||||
|
"c_queue7": "Page",
|
||||||
|
"c_queue8": "Précédent",
|
||||||
|
"c_queue9": "Suivant",
|
||||||
|
"c_queue10": "Désolé, une erreur est survenue.",
|
||||||
|
"c_skip_name": "skip",
|
||||||
|
"c_skip_desc": "Passe la chanson en cours",
|
||||||
|
"c_skip_opt1_name": "id",
|
||||||
|
"c_skip_opt1_desc": "ID de la chanson que vous voulez écouter",
|
||||||
|
"c_skip1": "Passe la chanson",
|
||||||
|
"c_skip2": "Le bot ne joue rien en ce moment.",
|
||||||
|
"c_skip3": "Passe à la chanson",
|
||||||
|
"c_lyrics_name": "paroles",
|
||||||
|
"c_lyrics_desc": "Affiche les paroles d'une chanson",
|
||||||
|
"c_lyrics_opt1_name": "chanson",
|
||||||
|
"c_lyrics_opt1_desc": "Chanson recherchée",
|
||||||
|
"c_lyrics1": "Le bot ne joue rien en ce moment et aucune chanson n'est renseignée.",
|
||||||
|
"c_lyrics2": "Impossible de trouver les paroles pour",
|
||||||
|
"c_repeat_name": "repeat",
|
||||||
|
"c_repeat_desc": "Commande relative à la répétition des musiques",
|
||||||
|
"c_repeat_sub1_name": "stop",
|
||||||
|
"c_repeat_sub1_desc": "Désactive la répétition",
|
||||||
|
"c_repeat_sub2_name": "track",
|
||||||
|
"c_repeat_sub2_desc": "Active la répétition pour la chanson en cours",
|
||||||
|
"c_repeat_sub3_name": "queue",
|
||||||
|
"c_repeat_sub3_desc": "Active la répétition pour la file actuelle",
|
||||||
|
"c_repeat_sub4_name": "autoplay",
|
||||||
|
"c_repeat_sub4_desc": "Active la lecture automatique",
|
||||||
|
"c_repeat1": "Le bot ne joue rien en ce moment.",
|
||||||
|
"c_repeat2": "Répétition désactivé.",
|
||||||
|
"c_repeat3": "Répétition de la file d'attente",
|
||||||
|
"c_repeat4": "Lecture automatique",
|
||||||
|
"c_repeat5": "Répétition de la chanson",
|
||||||
|
"c_repeat6": "activé."
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { Collection } from "discord.js";
|
import { Collection } from "discord.js";
|
||||||
import { SlashCommandBuilder } from "@discordjs/builders";
|
import { SlashCommandBuilder } from "@discordjs/builders";
|
||||||
import { Database } from "sqlite3";
|
import { Database } from "sqlite3";
|
||||||
|
import { Player } from "discord-player";
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|
||||||
declare module "discord.js" {
|
declare module "discord.js" {
|
||||||
// eslint-disable-next-line no-shadow
|
|
||||||
export interface Client {
|
export interface Client {
|
||||||
/** Store the configuration */
|
/** Store the configuration */
|
||||||
config: {
|
config: {
|
||||||
|
@ -83,6 +83,8 @@ declare module "discord.js" {
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
|
/** Music player */
|
||||||
|
player: Player;
|
||||||
/** Store all the localizations */
|
/** Store all the localizations */
|
||||||
locales: Map<string, Map<string, string>>;
|
locales: Map<string, Map<string, string>>;
|
||||||
db: Database;
|
db: Database;
|
||||||
|
|
16
src/modules/player.ts
Normal file
16
src/modules/player.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { LyricsData } from "@discord-player/extractor";
|
||||||
|
import { Client } from "genius-lyrics";
|
||||||
|
|
||||||
|
type LyricsClient = {
|
||||||
|
search: (query: string) => Promise<LyricsData | null>;
|
||||||
|
client: Client;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {};
|
||||||
|
|
||||||
|
declare module "discord-player" {
|
||||||
|
export interface Player {
|
||||||
|
/** Lyrics client */
|
||||||
|
lyrics: LyricsClient;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,8 @@ import { readFileSync } from "fs";
|
||||||
import { loadLocales } from "./locales";
|
import { loadLocales } from "./locales";
|
||||||
import "../modules/client";
|
import "../modules/client";
|
||||||
import { Database } from "sqlite3";
|
import { Database } from "sqlite3";
|
||||||
|
import { Player } from "discord-player";
|
||||||
|
import { lyricsExtractor } from "@discord-player/extractor";
|
||||||
|
|
||||||
/** Creation of the client and definition of its properties. */
|
/** Creation of the client and definition of its properties. */
|
||||||
export default async () => {
|
export default async () => {
|
||||||
|
@ -11,6 +13,7 @@ export default async () => {
|
||||||
GatewayIntentBits.Guilds,
|
GatewayIntentBits.Guilds,
|
||||||
GatewayIntentBits.GuildMessages,
|
GatewayIntentBits.GuildMessages,
|
||||||
GatewayIntentBits.MessageContent,
|
GatewayIntentBits.MessageContent,
|
||||||
|
GatewayIntentBits.GuildVoiceStates,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -35,6 +38,14 @@ export default async () => {
|
||||||
list: new Collection(),
|
list: new Collection(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
client.player = new Player(client, {
|
||||||
|
ytdlOptions: {
|
||||||
|
filter: "audioonly",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
client.player.lyrics = lyricsExtractor();
|
||||||
|
|
||||||
console.log("Translations progression :");
|
console.log("Translations progression :");
|
||||||
client.locales = await loadLocales(client.config.default_lang);
|
client.locales = await loadLocales(client.config.default_lang);
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,7 @@ export const loadLocales = async (default_lang: string) => {
|
||||||
* we fallback to default lang.
|
* we fallback to default lang.
|
||||||
* @param client Client
|
* @param client Client
|
||||||
* @param text Name of string to fetch
|
* @param text Name of string to fetch
|
||||||
|
* @param lowercase Should the output be lowercased?
|
||||||
* @returns the dictionary
|
* @returns the dictionary
|
||||||
*/
|
*/
|
||||||
export const getLocalizations = (client: Client, text: string, lowercase = false) => {
|
export const getLocalizations = (client: Client, text: string, lowercase = false) => {
|
||||||
|
|
5
src/utils/metadata.ts
Normal file
5
src/utils/metadata.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { TextBasedChannel } from "discord.js";
|
||||||
|
|
||||||
|
export type Metadata = {
|
||||||
|
channel: TextBasedChannel | null;
|
||||||
|
};
|
28
src/utils/music.ts
Normal file
28
src/utils/music.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { EmbedBuilder } from "@discordjs/builders";
|
||||||
|
import { Track } from "discord-player";
|
||||||
|
import { Client } from "discord.js";
|
||||||
|
import { getLocale } from "./locales";
|
||||||
|
|
||||||
|
export const embedListQueue = (
|
||||||
|
client: Client,
|
||||||
|
embed: EmbedBuilder,
|
||||||
|
tracks: Track[],
|
||||||
|
page: number,
|
||||||
|
local: string
|
||||||
|
) => {
|
||||||
|
const loc = getLocale(client, local);
|
||||||
|
|
||||||
|
// Limit of discord is 25
|
||||||
|
const limit_fields = 25;
|
||||||
|
|
||||||
|
const pageMax = Math.ceil(tracks.length / limit_fields);
|
||||||
|
|
||||||
|
embed.setAuthor({ name: `${loc.get("c_queue1")} • ${loc.get("c_queue7")} ${page}/${pageMax}` });
|
||||||
|
|
||||||
|
tracks.slice((page - 1) * limit_fields, page * limit_fields).forEach((t, idx) =>
|
||||||
|
embed.addFields({
|
||||||
|
name: "\u200b",
|
||||||
|
value: `${(idx + 1) * page}. [${t.title}](${t.url}) (${t.duration})`,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in a new issue