feat: quote (#42)
Reviewed-on: https://git.kennel.ml/ConfrerieDuKassoulait/Botanique/pulls/42
This commit is contained in:
parent
06f19c2f84
commit
f6ff50449c
7 changed files with 280 additions and 40 deletions
175
src/events/message/messageCreate.ts
Normal file
175
src/events/message/messageCreate.ts
Normal file
|
@ -0,0 +1,175 @@
|
|||
import { Client, GuildMember, Message, MessageEmbed, TextBasedChannel } from 'discord.js';
|
||||
import { getLocale } from '../../utils/locales';
|
||||
import { isImage, userWithNickname } from '../../utils/misc';
|
||||
import { showDate } from '../../utils/time';
|
||||
|
||||
/** https://discord.js.org/#/docs/discord.js/main/class/Client?scrollTo=e-messageCreate */
|
||||
export default async (message: Message, client: Client) => {
|
||||
// Ignore message if
|
||||
if (
|
||||
// Author is a bot
|
||||
message.author.bot ||
|
||||
// Author is Discord
|
||||
message.author.system ||
|
||||
// Message isn't a message
|
||||
message.system ||
|
||||
// Message is in PM (future-proof if we add Intents.FLAGS.DIRECT_MESSAGES)
|
||||
!message.guild ||
|
||||
// Guild is offline
|
||||
!message.guild.available
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Citation */
|
||||
const regex = 'https://(?:canary\\.|ptb\\.)?discord(?:app)?\\.com/channels/(\\d{17,19})/(\\d{17,19})/(\\d{17,19})';
|
||||
const urls = message.content.match(new RegExp(regex, 'g'));
|
||||
|
||||
// Ignore message if there is no URLs
|
||||
if (!urls) {
|
||||
return;
|
||||
}
|
||||
|
||||
const messages = (
|
||||
await Promise.all(
|
||||
urls.reduce(
|
||||
(data: {
|
||||
message_id: string;
|
||||
channel: TextBasedChannel;
|
||||
}[] = [], match) => {
|
||||
const [,
|
||||
guild_id,
|
||||
channel_id,
|
||||
message_id,
|
||||
] = new RegExp(regex).exec(match) as RegExpExecArray;
|
||||
|
||||
// If message posted in another guild
|
||||
if (guild_id !== message.guild?.id) {
|
||||
return data;
|
||||
}
|
||||
|
||||
const channel =
|
||||
message.guild.channels.cache.get(channel_id) as TextBasedChannel;
|
||||
|
||||
// If channel doesn't exist in the guild and isn't text
|
||||
if (!channel) {
|
||||
return data;
|
||||
}
|
||||
|
||||
data.push({ message_id, channel });
|
||||
|
||||
return data;
|
||||
}, []
|
||||
).map(async ({ message_id, channel }) => {
|
||||
const quoted_message = await channel.messages
|
||||
.fetch(message_id)
|
||||
.catch(() => undefined);
|
||||
|
||||
// If message doesn't exist or empty
|
||||
if (!quoted_message || (
|
||||
!quoted_message.content &&
|
||||
quoted_message.attachments.size == 0)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return quoted_message;
|
||||
})
|
||||
)
|
||||
// Remove undefined elements
|
||||
).filter(Boolean);
|
||||
|
||||
const loc = getLocale(client, client.config.default_lang);
|
||||
|
||||
// Remove duplicates then map the quoted posts
|
||||
[...new Set(messages)].map(quoted_post => {
|
||||
const embed = new MessageEmbed()
|
||||
.setColor('#2f3136')
|
||||
.setAuthor({
|
||||
name: 'Citation',
|
||||
iconURL: quoted_post?.author.displayAvatarURL(),
|
||||
});
|
||||
|
||||
// Handle attachments
|
||||
if (quoted_post?.attachments.size !== 0) {
|
||||
if (quoted_post?.attachments.size === 1 && isImage(
|
||||
quoted_post.attachments.first()?.name as string
|
||||
)) {
|
||||
// Only contains one image
|
||||
embed.setImage(quoted_post.attachments.first()?.url as string);
|
||||
} else {
|
||||
// Contains more than one image and/or other files
|
||||
let files = '';
|
||||
quoted_post?.attachments.forEach(file => files +=
|
||||
`[${file.name}](${file.url}), `
|
||||
);
|
||||
embed.addFields({
|
||||
// TODO: Don't pluralize when there is only one file.
|
||||
name: 'Fichiers joints',
|
||||
// TODO: Check if don't exceed char limit, if yes, split
|
||||
// files into multiples field.
|
||||
value: `${files.slice(0, -2)}.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Description as post content
|
||||
embed.setDescription(quoted_post?.content ?? '');
|
||||
|
||||
// Footer
|
||||
let footer = `Posté le ${showDate(
|
||||
client.config.default_lang,
|
||||
loc,
|
||||
quoted_post?.createdAt as Date
|
||||
)}`;
|
||||
if (quoted_post?.editedAt) {
|
||||
footer += ` et modifié le ${showDate(
|
||||
client.config.default_lang,
|
||||
loc,
|
||||
quoted_post.editedAt
|
||||
)}`;
|
||||
}
|
||||
|
||||
let author = 'Auteur';
|
||||
if (message.author == quoted_post?.author) {
|
||||
author += ' & Citateur';
|
||||
} else {
|
||||
footer += `\nCité par ${userWithNickname(
|
||||
message.member as GuildMember
|
||||
) ?? '?'} le ${showDate(
|
||||
client.config.default_lang,
|
||||
loc,
|
||||
message.createdAt
|
||||
)}`;
|
||||
}
|
||||
|
||||
embed.setFooter({
|
||||
text: footer,
|
||||
iconURL: message.author.avatarURL() ?? undefined,
|
||||
});
|
||||
|
||||
// Location/author of the quoted post
|
||||
embed.addField(author, `${quoted_post?.author}`, true);
|
||||
embed.addField(
|
||||
'Message', `${quoted_post?.channel} - [Lien Message](${quoted_post?.url})`,
|
||||
true
|
||||
);
|
||||
|
||||
// Delete source message if no content when removing links
|
||||
if (
|
||||
!message.content.replace(new RegExp(regex, 'g'), '').trim() &&
|
||||
messages.length === urls.length &&
|
||||
!message.mentions.repliedUser
|
||||
) {
|
||||
message.delete();
|
||||
return message.channel.send({ embeds: [embed] });
|
||||
} else {
|
||||
return message.reply({
|
||||
embeds: [embed],
|
||||
allowedMentions: {
|
||||
repliedUser: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
|
@ -12,5 +12,7 @@
|
|||
"c_help_opt1_desc": "Command wanted in depth.",
|
||||
"c_help1": "List of categories and associated commands",
|
||||
"c_help2": "`/help <command>` to get more information about a command.",
|
||||
"c_help3": "Can't find :"
|
||||
"c_help3": "Can't find :",
|
||||
|
||||
"u_time_at": "at"
|
||||
}
|
||||
|
|
|
@ -12,5 +12,7 @@
|
|||
"c_help_opt1_desc": "Commande voulu en détail.",
|
||||
"c_help1": "Liste des catégories et des commandes associées",
|
||||
"c_help2": "`/help <commande>` pour obtenir plus d'informations sur une commande.",
|
||||
"c_help3": "Impossible de trouver :"
|
||||
"c_help3": "Impossible de trouver :",
|
||||
|
||||
"u_time_at": "à"
|
||||
}
|
||||
|
|
41
src/modules/client.ts
Normal file
41
src/modules/client.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { Collection } from 'discord.js';
|
||||
import { SlashCommandBuilder } from '@discordjs/builders';
|
||||
|
||||
export {};
|
||||
|
||||
declare module 'discord.js' {
|
||||
// eslint-disable-next-line no-shadow
|
||||
export interface Client {
|
||||
/** Store the configuration */
|
||||
config: {
|
||||
/** Bot version */
|
||||
version: string,
|
||||
/** Bot token from env variable */
|
||||
token_discord: string | undefined,
|
||||
/** Default lang used */
|
||||
default_lang: string
|
||||
},
|
||||
/** Store all the slash commands */
|
||||
commands: {
|
||||
categories: Collection<
|
||||
/** Category name */
|
||||
string,
|
||||
/** Name of the commands in the category */
|
||||
string[]
|
||||
>,
|
||||
list: Collection<
|
||||
/** Command name */
|
||||
string,
|
||||
/** Command itself */
|
||||
{
|
||||
/** Data about the command */
|
||||
data: SlashCommandBuilder,
|
||||
/** How the command interact */
|
||||
interaction: (interaction: CommandInteraction, client: Client) => unknown
|
||||
}
|
||||
>,
|
||||
}
|
||||
/** Store all the localizations */
|
||||
locales: Map<string, Map<string, string>>
|
||||
}
|
||||
}
|
|
@ -1,50 +1,14 @@
|
|||
import { Client, Collection, Intents } from 'discord.js';
|
||||
import { readFileSync } from 'fs';
|
||||
import { SlashCommandBuilder } from '@discordjs/builders';
|
||||
import { loadLocales } from './locales';
|
||||
|
||||
declare module 'discord.js' {
|
||||
// eslint-disable-next-line no-shadow
|
||||
export interface Client {
|
||||
/** Store the configuration */
|
||||
config: {
|
||||
/** Bot version */
|
||||
version: string,
|
||||
/** Bot token from env variable */
|
||||
token_discord: string | undefined,
|
||||
/** Default lang used */
|
||||
default_lang: string
|
||||
},
|
||||
/** Store all the slash commands */
|
||||
commands: {
|
||||
categories: Collection<
|
||||
/** Category name */
|
||||
string,
|
||||
/** Name of the commands in the category */
|
||||
string[]
|
||||
>,
|
||||
list: Collection<
|
||||
/** Command name */
|
||||
string,
|
||||
/** Command itself */
|
||||
{
|
||||
/** Data about the command */
|
||||
data: SlashCommandBuilder,
|
||||
/** How the command interact */
|
||||
interaction: (interaction: CommandInteraction, client: Client) => unknown
|
||||
}
|
||||
>,
|
||||
}
|
||||
/** Store all the localizations */
|
||||
locales: Map<string, Map<string, string>>
|
||||
}
|
||||
}
|
||||
import '../modules/client';
|
||||
|
||||
/** Creation of the client and definition of its properties. */
|
||||
export default async () => {
|
||||
const client: Client = new Client({
|
||||
intents: [
|
||||
Intents.FLAGS.GUILDS,
|
||||
Intents.FLAGS.GUILD_MESSAGES,
|
||||
],
|
||||
});
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { GuildMember } from 'discord.js';
|
||||
|
||||
/**
|
||||
* Log module status.
|
||||
* @param {string} name Module name
|
||||
|
@ -36,3 +38,41 @@ export const removeExtension = (filename: string) => {
|
|||
|
||||
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();
|
||||
};
|
||||
|
||||
/**
|
||||
* Define if a media is a media based on file extension.
|
||||
* @param filename string of the filename
|
||||
* @returns true is file is a media
|
||||
*/
|
||||
export const isImage = (filename: string) => {
|
||||
return Boolean(getExtension(filename)?.match(
|
||||
/jpg|jpeg|png|webp|gif/
|
||||
));
|
||||
};
|
||||
|
||||
/**
|
||||
* String with pseudo and nickname if available.
|
||||
* @param member Member
|
||||
* @returns string
|
||||
*/
|
||||
export const userWithNickname = (member: GuildMember) => {
|
||||
if (!member) {
|
||||
return undefined;
|
||||
}
|
||||
if (member.nickname) {
|
||||
return `${member.nickname} (${member.user.tag})`;
|
||||
} else {
|
||||
return member.user.tag;
|
||||
}
|
||||
};
|
||||
|
|
16
src/utils/time.ts
Normal file
16
src/utils/time.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* Parsed string adapted with TZ (locales) and format for the specified lang.
|
||||
* @param tz Lang
|
||||
* @param locale Locales
|
||||
* @param date Date
|
||||
* @returns String
|
||||
*/
|
||||
export const showDate = (
|
||||
tz: string,
|
||||
locale: Map<string, unknown>,
|
||||
date: Date
|
||||
) => {
|
||||
return date.toLocaleString(tz).replace(' ', ` ${
|
||||
locale.get('u_time_at')
|
||||
} `);
|
||||
};
|
Loading…
Reference in a new issue