Compare commits

...

5 commits

Author SHA1 Message Date
088693d2d2
feat: returns to @distube/ytdl-core (#176)
All checks were successful
Publish latest version / build (push) Successful in 2m11s
Close #170

Reviewed-on: #176
Co-authored-by: Mylloon <kennel.anri@tutanota.com>
Co-committed-by: Mylloon <kennel.anri@tutanota.com>
2024-09-18 19:35:09 +02:00
929312e0ed
override temporary youtubei.js dependency (#175)
All checks were successful
Publish latest version / build (push) Successful in 2m40s
Ref #170

Reviewed-on: #175
Co-authored-by: Mylloon <kennel.anri@tutanota.com>
Co-committed-by: Mylloon <kennel.anri@tutanota.com>
2024-09-18 19:23:54 +02:00
e4d1e307df
fix: no longer get token from youtube (#174)
All checks were successful
Publish latest version / build (push) Successful in 2m10s
Close #173

Reviewed-on: #174
Co-authored-by: Mylloon <kennel.anri@tutanota.com>
Co-committed-by: Mylloon <kennel.anri@tutanota.com>
2024-09-18 19:12:35 +02:00
767612a000
chore: merge dev to main (#172)
All checks were successful
Publish latest version / build (push) Successful in 2m2s
Reviewed-on: #172
Co-authored-by: Mylloon <kennel.anri@tutanota.com>
Co-committed-by: Mylloon <kennel.anri@tutanota.com>
2024-09-18 18:25:03 +02:00
23d3918459
merge dev to main (#169)
All checks were successful
Publish latest version / build (push) Successful in 1m33s
Close #63
Close #78
Close #79
Close #137

Mutiples fixed, mainly aimed to reminder commands

Also add the Rich Presence, and fix a bug for forwarded messages

Reviewed-on: #169
Co-authored-by: Mylloon <kennel.anri@tutanota.com>
Co-committed-by: Mylloon <kennel.anri@tutanota.com>
2024-09-17 22:57:55 +02:00
26 changed files with 1273 additions and 268 deletions

1217
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -19,12 +19,13 @@
"dependencies": { "dependencies": {
"@discord-player/extractor": "^4.5.1", "@discord-player/extractor": "^4.5.1",
"@discordjs/rest": "^2.4.0", "@discordjs/rest": "^2.4.0",
"@distube/ytdl-core": "^4.14.4",
"@types/sqlite3": "^3.1.11", "@types/sqlite3": "^3.1.11",
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"discord-player": "^6.7.1", "discord-player": "^6.7.1",
"discord-player-youtubei": "^1.3.1",
"discord.js": "^14.16.2", "discord.js": "^14.16.2",
"mediaplex": "^0.0.9", "mediaplex": "^0.0.9",
"puppeteer": "^23.4.0",
"sqlite3": "^5.1.7", "sqlite3": "^5.1.7",
"typescript": "^5.6.2", "typescript": "^5.6.2",
"uuid": "^10.0.0" "uuid": "^10.0.0"

View file

@ -10,9 +10,9 @@ import { getLocale } from "../utils/locales";
export default async (client: Client) => { export default async (client: Client) => {
// Dossier des buttons // Dossier des buttons
const buttons_categories = (await readdir(__dirname)).filter( const buttons_categories = (await readdir(__dirname, { withFileTypes: true }))
(element) => !element.endsWith(".js") && !element.endsWith(".ts"), .filter((element) => element.isDirectory())
); .map((element) => element.name);
await Promise.all( await Promise.all(
// For each categorie // For each categorie

View file

@ -4,7 +4,6 @@ import {
ButtonStyle, ButtonStyle,
Client, Client,
MessageComponentInteraction, MessageComponentInteraction,
User,
} from "discord.js"; } from "discord.js";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import { getLocale } from "../../utils/locales"; import { getLocale } from "../../utils/locales";
@ -18,11 +17,11 @@ export default {
}, },
interaction: async (interaction: MessageComponentInteraction, client: Client) => { interaction: async (interaction: MessageComponentInteraction, client: Client) => {
const loc = getLocale(client, interaction.locale); const loc = getLocale(client, interaction.locale);
const embed_desc = interaction.message.embeds.at(0)?.description as string; const embed_desc = interaction.message.embeds.at(0)?.description;
// Retrieve Pages // Retrieve Pages
const pageMax = Number(/(\d+)(?!.*\d)/gm.exec(embed_desc)?.[0]); const pageMax = Number(/(\d+)(?!.*\d)/gm.exec(embed_desc!)?.[0]);
let page = Number(/(?!• \s+)\d(?=\/)/gm.exec(embed_desc)?.[0]); let page = Number(/(?!• \s+)\d(?=\/)/gm.exec(embed_desc!)?.[0]);
if (page + 1 > pageMax) { if (page + 1 > pageMax) {
page = 1; page = 1;
} else { } else {
@ -30,8 +29,8 @@ export default {
} }
// Retrieve user // Retrieve user
const userId = /(?!<@)\d+(?=>)/gm.exec(embed_desc)?.[0] as string; const userId = /(?!<@)\d+(?=>)/gm.exec(embed_desc!)?.[0];
const user = client.users.cache.get(userId) as User; const user = client.users.cache.get(userId!)!;
// Fetch list // Fetch list
const list = await embedListReminders( const list = await embedListReminders(

View file

@ -4,7 +4,6 @@ import {
ButtonStyle, ButtonStyle,
Client, Client,
MessageComponentInteraction, MessageComponentInteraction,
User,
} from "discord.js"; } from "discord.js";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import { getLocale } from "../../utils/locales"; import { getLocale } from "../../utils/locales";
@ -18,20 +17,20 @@ export default {
}, },
interaction: async (interaction: MessageComponentInteraction, client: Client) => { interaction: async (interaction: MessageComponentInteraction, client: Client) => {
const loc = getLocale(client, interaction.locale); const loc = getLocale(client, interaction.locale);
const embed_desc = interaction.message.embeds.at(0)?.description as string; const embed_desc = interaction.message.embeds.at(0)?.description;
// Retrieve Pages // Retrieve Pages
const pageMax = Number(/(\d+)(?!.*\d)/gm.exec(embed_desc)?.[0]); const pageMax = Number(/(\d+)(?!.*\d)/gm.exec(embed_desc!)?.[0]);
let page = Number(/(?!• \s+)\d(?=\/)/gm.exec(embed_desc)?.[0]); let page = Number(/(?!• \s+)\d(?=\/)/gm.exec(embed_desc!)?.[0]);
if (page - 1 == 0) { if (page - 1 === 0) {
page = pageMax; page = pageMax;
} else { } else {
page--; page--;
} }
// Retrieve user // Retrieve user
const userId = /(?!<@)\d+(?=>)/gm.exec(embed_desc)?.[0] as string; const userId = /(?!<@)\d+(?=>)/gm.exec(embed_desc!)?.[0];
const user = client.users.cache.get(userId) as User; const user = client.users.cache.get(userId!)!;
// Fetch list // Fetch list
const list = await embedListReminders( const list = await embedListReminders(

View file

@ -19,11 +19,11 @@ export default {
}, },
interaction: async (interaction: MessageComponentInteraction, client: Client) => { interaction: async (interaction: MessageComponentInteraction, client: Client) => {
const loc = getLocale(client, interaction.locale); const loc = getLocale(client, interaction.locale);
const embed_desc = interaction.message.embeds.at(0)?.author?.name as string; const embed_desc = interaction.message.embeds.at(0)?.author?.name;
// Retrieve Pages // Retrieve Pages
const pageMax = Number(/(\d+)(?!.*\d)/gm.exec(embed_desc)?.[0]); const pageMax = Number(/(\d+)(?!.*\d)/gm.exec(embed_desc!)?.[0]);
let page = Number(/(?!• \s+)\d(?=\/)/gm.exec(embed_desc)?.[0]); let page = Number(/(?!• \s+)\d(?=\/)/gm.exec(embed_desc!)?.[0]);
if (page + 1 > pageMax) { if (page + 1 > pageMax) {
page = 1; page = 1;
} else { } else {

View file

@ -24,7 +24,7 @@ export default {
// Retrieve Pages // Retrieve Pages
const pageMax = Number(/(\d+)(?!.*\d)/gm.exec(embed_desc)?.[0]); const pageMax = Number(/(\d+)(?!.*\d)/gm.exec(embed_desc)?.[0]);
let page = Number(/(?!• \s+)\d(?=\/)/gm.exec(embed_desc)?.[0]); let page = Number(/(?!• \s+)\d(?=\/)/gm.exec(embed_desc)?.[0]);
if (page - 1 == 0) { if (page - 1 === 0) {
page = pageMax; page = pageMax;
} else { } else {
page--; page--;

View file

@ -8,9 +8,9 @@ import { removeExtension } from "../utils/misc";
export default async (client: Client) => { export default async (client: Client) => {
const rest = new REST({ version: "10" }).setToken(client.token!); const rest = new REST({ version: "10" }).setToken(client.token!);
const command_categories = (await readdir(__dirname)).filter( const command_categories = (await readdir(__dirname, { withFileTypes: true }))
(element) => !element.endsWith(".js") && !element.endsWith(".ts"), .filter((element) => element.isDirectory())
); .map((element) => element.name);
const commands = ( const commands = (
await Promise.all( await Promise.all(
@ -70,7 +70,7 @@ export default async (client: Client) => {
); );
// Send global commands to Discord // Send global commands to Discord
const globalCommands = commands.filter((c) => c.scope().length == 0); const globalCommands = commands.filter((c) => c.scope().length === 0);
return await rest.put(Routes.applicationCommands(client.user?.id as string), { return await rest.put(Routes.applicationCommands(client.user?.id as string), {
body: globalCommands.map((c) => c.data.toJSON()), body: globalCommands.map((c) => c.data.toJSON()),
}); });

View file

@ -74,14 +74,14 @@ export default {
allChannel?.then(async (channelGuild) => { allChannel?.then(async (channelGuild) => {
// Retrieve category to archive // Retrieve category to archive
const catToArchive = channelGuild const catToArchive = channelGuild
.filter((chan) => chan?.type == ChannelType.GuildCategory) .filter((chan) => chan?.type === ChannelType.GuildCategory)
.filter((chan) => chan?.name == desiredCat); .filter((chan) => chan?.name === desiredCat);
// Create/Retrieve the archive category // Create/Retrieve the archive category
const catArchivedName = "archive - " + desiredCat; const catArchivedName = "archive - " + desiredCat;
const catArchivedMap = channelGuild const catArchivedMap = channelGuild
.filter((chan) => chan?.type == ChannelType.GuildCategory) .filter((chan) => chan?.type === ChannelType.GuildCategory)
.filter((chan) => chan?.name == catArchivedName); .filter((chan) => chan?.name === catArchivedName);
let catArchived: NonThreadGuildBasedChannel | null | undefined; let catArchived: NonThreadGuildBasedChannel | null | undefined;
if (catArchivedMap.size > 0) { if (catArchivedMap.size > 0) {
@ -94,11 +94,11 @@ export default {
} }
const allChannelDesired = channelGuild const allChannelDesired = channelGuild
.filter((chan) => chan?.type == 0) .filter((chan) => chan?.type === 0)
.filter((chan) => chan?.parentId == catToArchive.map((cat) => cat?.id)[0]); .filter((chan) => chan?.parentId === catToArchive.map((cat) => cat?.id)[0]);
// If no channels in the source category // If no channels in the source category
if (allChannelDesired.size == 0) { if (allChannelDesired.size === 0) {
return interaction.reply({ return interaction.reply({
embeds: [ embeds: [
new EmbedBuilder() new EmbedBuilder()

View file

@ -1,5 +1,4 @@
import { SlashCommandBuilder } from "@discordjs/builders"; import { SlashCommandBuilder } from "@discordjs/builders";
import { Locale } from "discord-api-types/v9";
import { ChatInputCommandInteraction, Client, Colors, EmbedBuilder } from "discord.js"; import { ChatInputCommandInteraction, Client, Colors, EmbedBuilder } from "discord.js";
import "../../modules/string"; import "../../modules/string";
import { getLocale, getLocalizations } from "../../utils/locales"; import { getLocale, getLocalizations } from "../../utils/locales";
@ -93,7 +92,7 @@ export default {
.setDescription( .setDescription(
// Loads the description // Loads the description
// according to the user's locals // according to the user's locals
command.data.description_localizations?.[interaction.locale as Locale] ?? command.data.description_localizations?.[interaction.locale] ??
command.data.description, command.data.description,
), ),
], ],

View file

@ -67,21 +67,21 @@ export default {
const allChannel = interaction.guild?.channels.fetch(); const allChannel = interaction.guild?.channels.fetch();
allChannel?.then((channel_guild) => { allChannel?.then((channel_guild) => {
const cat_to_prep = channel_guild const cat_to_prep = channel_guild
.filter((chan) => chan?.type == ChannelType.GuildCategory) .filter((chan) => chan?.type === ChannelType.GuildCategory)
.filter((chan) => chan?.name == desired_cat); .filter((chan) => chan?.name === desired_cat);
const cat_to_prep_id = cat_to_prep.map((cat) => cat?.id); const cat_to_prep_id = cat_to_prep.map((cat) => cat?.id);
const cat_to_prep_name = cat_to_prep.map((cat) => cat?.name); const cat_to_prep_name = cat_to_prep.map((cat) => cat?.name);
// console.log(cat_to_prep); // console.log(cat_to_prep);
const all_channel_desired = channel_guild const all_channel_desired = channel_guild
.filter((chan) => chan?.type == 0) .filter((chan) => chan?.type === 0)
.filter((chan) => chan?.parentId == cat_to_prep_id[0]); .filter((chan) => chan?.parentId === cat_to_prep_id[0]);
const all_channel_desired_name = all_channel_desired.map((c_d) => c_d?.name); const all_channel_desired_name = all_channel_desired.map((c_d) => c_d?.name);
let desc = ""; let desc = "";
const general = "général"; const general = "général";
if (all_channel_desired_name.filter((cdn) => cdn == general).length == 0) { if (all_channel_desired_name.filter((cdn) => cdn === general).length === 0) {
interaction.guild?.channels.create({ interaction.guild?.channels.create({
name: general, name: general,
type: 0, type: 0,
@ -91,7 +91,7 @@ export default {
} }
const info = "informations"; const info = "informations";
if (all_channel_desired_name.filter((cdn) => cdn == info).length == 0) { if (all_channel_desired_name.filter((cdn) => cdn === info).length === 0) {
interaction.guild?.channels.create({ interaction.guild?.channels.create({
name: info, name: info,
type: 0, type: 0,
@ -101,7 +101,7 @@ export default {
desc += "`" + info + "` " + loc.get("c_prep5") + "\n"; desc += "`" + info + "` " + loc.get("c_prep5") + "\n";
} }
if (desc == "") { if (desc === "") {
desc = loc.get("c_prep6"); desc = loc.get("c_prep6");
} }

View file

@ -158,12 +158,19 @@ export default {
channelId: interaction.channelId, channelId: interaction.channelId,
userId: interaction.user.id, userId: interaction.user.id,
guildId: interaction.guildId, guildId: interaction.guildId,
}).then((msg) => })
interaction.reply({ .then((msg) =>
content: msg as string, interaction.reply({
ephemeral: true, content: msg as string,
}), ephemeral: true,
); }),
)
.catch((err) => {
interaction.reply({
content: err,
ephemeral: true,
});
});
} else { } else {
// Show modal to user to get at least the time // Show modal to user to get at least the time
const modal = new ModalBuilder() const modal = new ModalBuilder()
@ -198,7 +205,7 @@ export default {
let user = interaction.options.getUser( let user = interaction.options.getUser(
loc_default?.get(`c_${filename}_sub2_opt1_name`) as string, loc_default?.get(`c_${filename}_sub2_opt1_name`) as string,
); );
if (user == null) { if (user === null) {
user = interaction.user; user = interaction.user;
} }

View file

@ -1,5 +1,5 @@
import { SlashCommandBuilder } from "@discordjs/builders"; import { SlashCommandBuilder } from "@discordjs/builders";
import { Player, useMainPlayer, useQueue } from "discord-player"; import { useMainPlayer, useQueue } from "discord-player";
import { ChatInputCommandInteraction, Client, EmbedBuilder } from "discord.js"; import { ChatInputCommandInteraction, Client, EmbedBuilder } from "discord.js";
import { getLocale, getLocalizations } from "../../utils/locales"; import { getLocale, getLocalizations } from "../../utils/locales";
import { getFilename } from "../../utils/misc"; import { getFilename } from "../../utils/misc";
@ -81,11 +81,11 @@ export default {
let data = null; let data = null;
await interaction.deferReply(); await interaction.deferReply();
const player = useMainPlayer() as Player; const player = useMainPlayer();
const queue = useQueue(interaction.guildId!); const queue = useQueue(interaction.guildId!);
if (request) { if (request) {
if ( if (
interaction.options.getSubcommand() == interaction.options.getSubcommand() ===
loc_default?.get(`c_${filename}_sub2_name`)?.toLowerCase() loc_default?.get(`c_${filename}_sub2_name`)?.toLowerCase()
) { ) {
// Romanized // Romanized
@ -111,7 +111,7 @@ export default {
} }
if ( if (
interaction.options.getSubcommand() == interaction.options.getSubcommand() ===
loc_default?.get(`c_${filename}_sub3_name`)?.toLowerCase() loc_default?.get(`c_${filename}_sub3_name`)?.toLowerCase()
) { ) {
if (queue === null) { if (queue === null) {

View file

@ -1,12 +1,10 @@
import { SlashCommandBuilder } from "@discordjs/builders"; import { SlashCommandBuilder } from "@discordjs/builders";
import { Player, SearchResult, useMainPlayer, useQueue } from "discord-player"; import { SearchResult, useMainPlayer, useQueue } from "discord-player";
import { import {
AutocompleteInteraction, AutocompleteInteraction,
ChatInputCommandInteraction, ChatInputCommandInteraction,
Client, Client,
EmbedBuilder, EmbedBuilder,
GuildResolvable,
VoiceBasedChannel,
} from "discord.js"; } from "discord.js";
import { getLocale, getLocalizations } from "../../utils/locales"; import { getLocale, getLocalizations } from "../../utils/locales";
import { Metadata } from "../../utils/metadata"; import { Metadata } from "../../utils/metadata";
@ -71,7 +69,7 @@ export default {
loc_default?.get(`c_${filename}_opt1_name`) as string, loc_default?.get(`c_${filename}_opt1_name`) as string,
); );
const player = useMainPlayer() as Player; const player = useMainPlayer();
if (!query) { if (!query) {
// Now playing // Now playing
@ -97,7 +95,7 @@ export default {
return await interaction.reply({ embeds: [embed] }); return await interaction.reply({ embeds: [embed] });
} }
const queue = player.nodes.create(interaction.guild as GuildResolvable, { const queue = player.nodes.create(interaction.guild!, {
defaultFFmpegFilters: ["silenceremove"], defaultFFmpegFilters: ["silenceremove"],
metadata: { metadata: {
channel: interaction.channel, channel: interaction.channel,
@ -106,7 +104,7 @@ export default {
// Verify vc connection // Verify vc connection
try { try {
if (!queue.connection) await queue.connect(member.voice.channel as VoiceBasedChannel); if (!queue.connection) await queue.connect(member.voice.channel!);
} catch { } catch {
queue.delete(); queue.delete();
return await interaction.reply({ return await interaction.reply({
@ -155,7 +153,7 @@ export default {
const loc_default = interaction.client.locales.get(interaction.client.config.default_lang); const loc_default = interaction.client.locales.get(interaction.client.config.default_lang);
const filename = getFilename(__filename); const filename = getFilename(__filename);
const player = useMainPlayer() as Player; const player = useMainPlayer();
const query = interaction.options.getString( const query = interaction.options.getString(
loc_default?.get(`c_${filename}_opt1_name`) as string, loc_default?.get(`c_${filename}_opt1_name`) as string,
true, true,

View file

@ -153,7 +153,7 @@ export default {
case loc_default?.get(`c_${filename}_sub3_name`)?.toLowerCase(): { case loc_default?.get(`c_${filename}_sub3_name`)?.toLowerCase(): {
const id = interaction.options.getNumber( const id = interaction.options.getNumber(
loc_default?.get(`c_${filename}_sub3_opt1_name`) as string, loc_default?.get(`c_${filename}_sub3_opt1_name`) as string,
) as number; )!;
const track = queue.removeTrack(id - 1); const track = queue.removeTrack(id - 1);

View file

@ -1,8 +1,7 @@
import { SlashCommandBuilder } from "@discordjs/builders"; import { SlashCommandBuilder } from "@discordjs/builders";
import { Player, useMainPlayer } from "discord-player"; import { useMainPlayer } from "discord-player";
import { ChatInputCommandInteraction, Client, GuildResolvable } from "discord.js"; import { ChatInputCommandInteraction, Client } from "discord.js";
import { getLocale, getLocalizations } from "../../utils/locales"; import { getLocale, getLocalizations } from "../../utils/locales";
import { Metadata } from "../../utils/metadata";
import { getFilename } from "../../utils/misc"; import { getFilename } from "../../utils/misc";
export default { export default {
@ -25,11 +24,11 @@ export default {
interaction: async (interaction: ChatInputCommandInteraction, client: Client) => { interaction: async (interaction: ChatInputCommandInteraction, client: Client) => {
const loc = getLocale(client, interaction.locale); const loc = getLocale(client, interaction.locale);
const player = useMainPlayer() as Player; const player = useMainPlayer();
const queue = player.nodes.create(interaction.guild as GuildResolvable, { const queue = player.nodes.create(interaction.guild!, {
metadata: { metadata: {
channel: interaction.channel, channel: interaction.channel,
} as Metadata, },
}); });
if (!(queue.connection || queue.node.isPlaying())) { if (!(queue.connection || queue.node.isPlaying())) {

View file

@ -1,12 +1,37 @@
import { Player, PlayerEvents, useMainPlayer } from "discord-player"; import { PlayerEvents, useMainPlayer } from "discord-player";
import { Client } from "discord.js"; import { Client } from "discord.js";
import { readdir } from "fs/promises"; import { readdir } from "fs/promises";
import { splitFilenameExtensions } from "../utils/misc";
/** Load all the events */ /** Load all the events */
export default async (client: Client) => { export default async (client: Client, isDev: boolean) => {
const events_categories = (await readdir(__dirname)).filter( const events_categories = (await readdir(__dirname, { withFileTypes: true }))
(element) => !element.endsWith(".js") && !element.endsWith(".ts"), .filter((element) => element.isDirectory())
); .map((element) => element.name);
const player = useMainPlayer();
if (isDev) {
player.on("debug", async (message) => {
console.log(`General player debug event: ${message}`);
});
player.events.on("debug", async (_, message) => {
console.log(`Player debug event: ${message}`);
});
}
player.events.on("error", (_, error) => {
// Emitted when the player queue encounters error
console.error(`General player error event: ${error.message}`);
console.error(error);
});
player.events.on("playerError", (_, error) => {
// Emitted when the audio player errors while streaming audio track
console.error(`Player error event: ${error.message}`);
console.error(error);
});
events_categories.forEach(async (event_category) => { events_categories.forEach(async (event_category) => {
// Retrieve events // Retrieve events
@ -20,16 +45,12 @@ export default async (client: Client) => {
); );
// Remove extension // Remove extension
// TODO: use utils functions const { file: event_type, ext } = splitFilenameExtensions(event_file)!;
const event_type_ext = event_file.split(".");
const ext = event_type_ext.pop();
if (!(ext === "js" || ext === "ts")) { if (!(ext === "js" || ext === "ts")) {
throw `Unknown file in ${event_category}: ${event_file}`; throw `Unknown file in ${event_category}: ${event_file}`;
} }
const event_type = event_type_ext.join(".");
if (event_category == "player") { if (event_category === "player") {
const player = useMainPlayer() as Player;
if (once) { if (once) {
// eslint-disable-next-line // eslint-disable-next-line
return player.events.once(event_type as keyof PlayerEvents, (...args: any[]) => { return player.events.once(event_type as keyof PlayerEvents, (...args: any[]) => {

View file

@ -1,4 +1,4 @@
import { Client, EmbedBuilder, GuildMember, Message, TextBasedChannel } from "discord.js"; import { Client, EmbedBuilder, Message, TextBasedChannel } from "discord.js";
import { getLocale } from "../../utils/locales"; import { getLocale } from "../../utils/locales";
import { isImage, userWithNickname } from "../../utils/misc"; import { isImage, userWithNickname } from "../../utils/misc";
import { showDate } from "../../utils/time"; import { showDate } from "../../utils/time";
@ -65,12 +65,24 @@ export default async (message: Message, client: Client) => {
[], [],
) )
.map(async ({ message_id, channel }) => { .map(async ({ message_id, channel }) => {
const quoted_message = await channel.messages.fetch(message_id).catch(() => undefined); let quoted_message = await channel.messages.fetch(message_id).catch(() => undefined);
// If it's a reference, we only check for reference once
const message_reference = quoted_message?.reference;
if (message_reference && message_reference.messageId) {
const channel_reference = client.channels.cache.get(
message_reference.channelId,
) as TextBasedChannel;
quoted_message = await channel_reference.messages
.fetch(message_reference.messageId)
.catch(() => undefined);
}
// If message doesn't exist or empty // If message doesn't exist or empty
if ( if (
!quoted_message || !quoted_message ||
(!quoted_message.content && quoted_message.attachments.size == 0) (!quoted_message.content && quoted_message.attachments.size === 0)
) { ) {
return; return;
} }
@ -131,10 +143,10 @@ export default async (message: Message, client: Client) => {
} }
let author = "Auteur"; let author = "Auteur";
if (message.author == quoted_post?.author) { if (message.author === quoted_post?.author) {
author += " & Citateur"; author += " & Citateur";
} else { } else {
footer += `\nCité par ${userWithNickname(message.member as GuildMember) ?? "?"} le ${showDate( footer += `\nCité par ${userWithNickname(message.member!) ?? "?"} le ${showDate(
client.config.default_lang, client.config.default_lang,
loc, loc,
message.createdAt, message.createdAt,

View file

@ -12,7 +12,7 @@ export const run = async (isDev: boolean) => {
// Client Discord.JS // Client Discord.JS
const client_name = "Client"; const client_name = "Client";
await loadClient() await loadClient(isDev)
.then(async (client) => { .then(async (client) => {
if (isDev) { if (isDev) {
// Attach debugging listeners // Attach debugging listeners
@ -21,7 +21,7 @@ export const run = async (isDev: boolean) => {
// Events Discord.JS and Player // Events Discord.JS and Player
const events_name = "Events"; const events_name = "Events";
await loadEvents(client) await loadEvents(client, isDev)
.then(() => console.log(logStart(events_name, true))) .then(() => console.log(logStart(events_name, true)))
.catch((err) => { .catch((err) => {
console.error(err); console.error(err);

View file

@ -77,6 +77,7 @@
"c_reminder15": "Message sent in DM because you have left", "c_reminder15": "Message sent in DM because you have left",
"c_reminder16": "Message sent in DM because the Discord guild is no longer available.", "c_reminder16": "Message sent in DM because the Discord guild is no longer available.",
"c_reminder17": "Message from", "c_reminder17": "Message from",
"c_reminder18": "Invalid time, try again.",
"c_play_name": "play", "c_play_name": "play",
"c_play_desc": "Plays a song/playlist, no query displays the now playing song", "c_play_desc": "Plays a song/playlist, no query displays the now playing song",

View file

@ -77,6 +77,7 @@
"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": "Temps invalide, réessayez.",
"c_play_name": "play", "c_play_name": "play",
"c_play_desc": "Joue une chanson/playlist, pas de requête affiche la chanson en cours actuellement", "c_play_desc": "Joue une chanson/playlist, pas de requête affiche la chanson en cours actuellement",

View file

@ -1,13 +1,14 @@
import { Player } from "discord-player"; import { Player } from "discord-player";
import { Client, Collection, GatewayIntentBits } from "discord.js"; import { ActivityType, Client, Collection, GatewayIntentBits } from "discord.js";
import { readFileSync } from "fs"; import { readFileSync } from "fs";
import { Database } from "sqlite3"; import { Database } from "sqlite3";
import "../modules/client"; import "../modules/client";
import { loadLocales } from "./locales"; import { loadLocales } from "./locales";
import { YoutubeiExtractor } from "discord-player-youtubei";
/** Creation of the client and definition of its properties */ /** Creation of the client and definition of its properties */
export default async () => { export default async (isDev: boolean) => {
const activities = isDev ? [] : [{ name: "/help", type: ActivityType.Watching }];
const client: Client = new Client({ const client: Client = new Client({
shards: "auto", shards: "auto",
intents: [ intents: [
@ -16,6 +17,9 @@ export default async () => {
GatewayIntentBits.MessageContent, GatewayIntentBits.MessageContent,
GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildVoiceStates,
], ],
presence: {
activities,
},
}); });
client.config = { client.config = {
@ -46,8 +50,7 @@ export default async () => {
quality: "highestaudio", quality: "highestaudio",
}, },
}); });
await player.extractors.loadDefault((ext) => ext !== "YouTubeExtractor"); await player.extractors.loadDefault();
await player.extractors.register(YoutubeiExtractor, {});
console.log("Translations progression :"); console.log("Translations progression :");
client.locales = await loadLocales(client.config.default_lang); client.locales = await loadLocales(client.config.default_lang);

View file

@ -28,27 +28,41 @@ export const getFilename = (path: string) => {
return removeExtension(filename_with_ext); return removeExtension(filename_with_ext);
}; };
/**
* Split a filename and his extension
* @param filename string of the filename
* @returns Object with filename and extension splitted
*/
export const splitFilenameExtensions = (filename: string) => {
if (filename.length === 0) {
return undefined;
}
// Check if the filename starts with a dot and has no other dots
if (filename.startsWith(".") && filename.indexOf(".", 1) === -1) {
return { file: filename, ext: undefined };
}
const lastDotIndex = filename.lastIndexOf(".");
// If there's no dot or the dot is at the start, treat the whole string as the filename
if (lastDotIndex <= 0) {
return { file: filename, ext: undefined };
}
const file = filename.slice(0, lastDotIndex);
const ext = filename.slice(lastDotIndex + 1);
return { file, ext };
};
/** /**
* Remove extension from a filename * Remove extension from a filename
* @param filename string of the filename with an extension * @param filename string of the filename with an extension
* @returns string of the filename without an extension * @returns string of the filename without an extension
*/ */
export const removeExtension = (filename: string) => { export const removeExtension = (filename: string) => {
const array = filename.split("."); return splitFilenameExtensions(filename)!.file;
array.pop();
return array.join(".");
};
/**
* Get extension from a filename
* @param filename string of the filename
* @returns string of the extension if it exists
*/
export const getExtension = (filename: string) => {
const array = filename.split(".");
return array.pop();
}; };
/** /**
@ -57,7 +71,11 @@ export const getExtension = (filename: string) => {
* @returns true is file is a media * @returns true is file is a media
*/ */
export const isImage = (filename: string) => { export const isImage = (filename: string) => {
return Boolean(getExtension(filename)?.match(/jpg|jpeg|png|webp|gif/)); return Boolean(
splitFilenameExtensions(filename)
?.ext?.toLowerCase()
.match(/jpg|jpeg|png|webp|gif/),
);
}; };
/** /**
@ -98,7 +116,7 @@ export const cleanCodeBlock = (text: string) => {
); );
// Fix issues // Fix issues
text = text.replace("``", ""); text = text.replaceAll("``", "");
return text; return text;
}; };

View file

@ -1,5 +1,5 @@
import { EmbedBuilder } from "@discordjs/builders"; import { EmbedBuilder } from "@discordjs/builders";
import { GuildQueue, QueueRepeatMode, Track } from "discord-player"; import { GuildQueue, QueueRepeatMode } from "discord-player";
import { Client } from "discord.js"; import { Client } from "discord.js";
import { getLocale } from "./locales"; import { getLocale } from "./locales";
@ -14,7 +14,7 @@ export const embedListQueue = (
const tracks = queue.tracks.toArray(); const tracks = queue.tracks.toArray();
// Add the current song at the top of the list // Add the current song at the top of the list
tracks.unshift(queue.history.currentTrack as Track); tracks.unshift(queue.history.currentTrack!);
// Limit of discord is 25 // Limit of discord is 25
const limit_fields = 25; const limit_fields = 25;
@ -25,10 +25,10 @@ export const embedListQueue = (
embed.setFooter({ text: `${printRepeatMode(queue.repeatMode, loc)}` }); embed.setFooter({ text: `${printRepeatMode(queue.repeatMode, loc)}` });
tracks.slice((page - 1) * limit_fields, page * limit_fields).forEach((t, idx) => { tracks.slice((page - 1) * limit_fields, page * limit_fields).forEach((t, idx) => {
const now_playing = idx == 0 && page == 1; const now_playing = idx === 0 && page === 1;
const name = now_playing const name = now_playing
? loc.get("c_queue10") ? loc.get("c_queue10")
: (idx == 1 && page == 1) || (idx == 0 && page > 1) : (idx === 1 && page === 1) || (idx === 0 && page > 1)
? loc.get("c_queue11") ? loc.get("c_queue11")
: "\u200b"; : "\u200b";
const idx_track = now_playing ? "" : `${idx + limit_fields * (page - 1)}. `; const idx_track = now_playing ? "" : `${idx + limit_fields * (page - 1)}. `;

View file

@ -64,8 +64,15 @@ const splitTime = (time: string) => {
*/ */
export const newReminder = async (client: Client, time: string, info: infoReminder) => export const newReminder = async (client: Client, time: string, info: infoReminder) =>
new Promise((ok, ko) => { new Promise((ok, ko) => {
const loc = getLocale(client, info.locale);
const data = splitTime(time); const data = splitTime(time);
const timeout = strToSeconds(data.time); const timeout = strToSeconds(data.time);
if (timeout < 0) {
ko(loc.get("c_reminder18"));
return;
}
const timeoutId = setTimeoutReminder(client, info, data.option, timeout); const timeoutId = setTimeoutReminder(client, info, data.option, timeout);
// Add the remind to the db // Add the remind to the db
@ -90,7 +97,6 @@ export const newReminder = async (client: Client, time: string, info: infoRemind
} }
// Send confirmation to user // Send confirmation to user
const loc = getLocale(client, info.locale);
ok(`${loc.get("c_reminder1")} ${data.time}.`); ok(`${loc.get("c_reminder1")} ${data.time}.`);
}, },
); );
@ -160,7 +166,7 @@ export const sendReminder = (client: Client, info: infoReminder, option: OptionR
} }
} }
if (option == OptionReminder.DirectMessage || !channelOk || !guildOk) { if (option === OptionReminder.DirectMessage || !channelOk || !guildOk) {
// Direct message // Direct message
const user = client.users.cache.get(info.userId); const user = client.users.cache.get(info.userId);
if (user !== undefined) { if (user !== undefined) {
@ -176,7 +182,7 @@ export const sendReminder = (client: Client, info: infoReminder, option: OptionR
}); });
// Mention everybody if needed // Mention everybody if needed
if (option == OptionReminder.Mention) { if (option === OptionReminder.Mention) {
(info.message?.match(/<@\d+>/g) ?? []).forEach((mention) => { (info.message?.match(/<@\d+>/g) ?? []).forEach((mention) => {
content += " " + mention; content += " " + mention;
}); });
@ -202,8 +208,19 @@ export const setTimeoutReminder = (
option: OptionReminder, option: OptionReminder,
timeout: number, timeout: number,
) => { ) => {
return Number( const setChunkedTimeout = (remainingTime: number) => {
setTimeout(() => { // Maximum for setTimeout is Int32
if (remainingTime > 2147483647) {
// Schedule a 24-hour delay (24 * 60 * 60 * 1000), then check again
const dayChunk = 86400000;
return setTimeout(() => {
setChunkedTimeout(remainingTime - dayChunk);
}, dayChunk);
}
// Final timeout when remaining time is within limit
return setTimeout(() => {
deleteReminder(client, String(info.createdAt), info.userId).then((val) => { deleteReminder(client, String(info.createdAt), info.userId).then((val) => {
if (val != true) { if (val != true) {
throw val; throw val;
@ -211,8 +228,11 @@ export const setTimeoutReminder = (
sendReminder(client, info, option); sendReminder(client, info, option);
}); });
}, timeout * 1000), }, remainingTime);
); };
// Convert to milliseconds
return Number(setChunkedTimeout(timeout * 1000));
}; };
/** /**

View file

@ -24,6 +24,11 @@ enum TimeSecond {
* @returns time in seconds * @returns time in seconds
*/ */
export const strToSeconds = (time: string) => { export const strToSeconds = (time: string) => {
if (time.length > 15) {
// 15 is a magic value, weird to be this long
return -1;
}
const regex = new RegExp( const regex = new RegExp(
`(?<${TimeSecond[TimeSecond.Year]}>[0-9]+(?=[y|a]))|(?<${ `(?<${TimeSecond[TimeSecond.Year]}>[0-9]+(?=[y|a]))|(?<${
TimeSecond[TimeSecond.Week] TimeSecond[TimeSecond.Week]
@ -34,7 +39,12 @@ export const strToSeconds = (time: string) => {
}>[0-9]+(?=[s]?))`, }>[0-9]+(?=[s]?))`,
); );
const data = Object.assign({}, regex.exec(time)?.groups); const data = Object.assign({}, regex.exec(time.toLowerCase())?.groups);
if (Object.keys(data).length === 0) {
// Regex returned an invalid time
return -1;
}
let res = 0; let res = 0;
Object.entries(data).forEach(([key, value]) => { Object.entries(data).forEach(([key, value]) => {