diff --git a/src/commands/context-menu/blacklist.ts b/src/commands/context-menu/blacklist.ts index 9b9c6e1d..d105ecd7 100644 --- a/src/commands/context-menu/blacklist.ts +++ b/src/commands/context-menu/blacklist.ts @@ -21,6 +21,7 @@ import { colors, emojis } from '../../utils/Constants.js'; import { CustomID } from '../../utils/CustomID.js'; import { RegisterInteractionHandler } from '../../decorators/Interaction.js'; import { simpleEmbed } from '../../utils/Utils.js'; +import { time } from 'discord.js'; export default class Blacklist extends BaseCommand { data: RESTPostAPIApplicationCommandsJSONBody = { @@ -173,7 +174,7 @@ export default class Blacklist extends BaseCommand { }, { name: 'Expires', - value: expires ? `` : 'Never.', + value: expires ? `${time(Math.round(expires.getTime() / 1000), 'R')}` : 'Never.', inline: true, }, ); @@ -255,9 +256,7 @@ export default class Blacklist extends BaseCommand { if (server) { const hubLogger = await new HubLogsManager(originalMsg.hubId).init(); - await hubLogger - .logBlacklist(server, interaction.user, reason, expires) - .catch(() => null); + await hubLogger.logBlacklist(server, interaction.user, reason, expires).catch(() => null); } await interaction.editReply({ embeds: [successEmbed], components: [] }); diff --git a/src/commands/slash/Main/blacklist/index.ts b/src/commands/slash/Main/blacklist/index.ts index fcdcac47..621bb296 100644 --- a/src/commands/slash/Main/blacklist/index.ts +++ b/src/commands/slash/Main/blacklist/index.ts @@ -5,11 +5,9 @@ import { Collection, RESTPostAPIApplicationCommandsJSONBody, } from 'discord.js'; +import { handleError } from '../../../../utils/Utils.js'; import BaseCommand from '../../../BaseCommand.js'; import db from '../../../../utils/Db.js'; -import Logger from '../../../../utils/Logger.js'; -import { captureException } from '@sentry/node'; -import { replyWithError } from '../../../../utils/Utils.js'; export default class BlacklistCommand extends BaseCommand { // TODO: Put this in readme @@ -171,12 +169,7 @@ export default class BlacklistCommand extends BaseCommand { const subCommandName = interaction.options.getSubcommand(); const subcommand = BlacklistCommand.subcommands.get(subCommandName); - return await subcommand?.execute(interaction).catch((e) => { - Logger.error(e); - captureException(e); - // reply with an error message to the user - replyWithError(interaction, e.message); - }); + return await subcommand?.execute(interaction).catch((e) => handleError(e, interaction)); } async autocomplete(interaction: AutocompleteInteraction) { diff --git a/src/commands/slash/Main/blacklist/list.ts b/src/commands/slash/Main/blacklist/list.ts index eeac9523..da31bf11 100644 --- a/src/commands/slash/Main/blacklist/list.ts +++ b/src/commands/slash/Main/blacklist/list.ts @@ -5,6 +5,7 @@ import { paginate } from '../../../../utils/Pagination.js'; import { colors } from '../../../../utils/Constants.js'; import { simpleEmbed } from '../../../../utils/Utils.js'; import { t } from '../../../../utils/Locale.js'; +import { time } from 'discord.js'; export default class ListBlacklists extends BlacklistCommand { async execute(interaction: ChatInputCommandInteraction) { @@ -24,9 +25,7 @@ export default class ListBlacklists extends BlacklistCommand { if (!hubInDb) { await interaction.editReply({ - embeds: [ - simpleEmbed(t({ phrase: 'hub.notFound_mod', locale: interaction.user.locale })), - ], + embeds: [simpleEmbed(t({ phrase: 'hub.notFound_mod', locale: interaction.user.locale }))], }); return; } @@ -66,7 +65,7 @@ export default class ListBlacklists extends BlacklistCommand { reason: `${hubData?.reason}`, expires: !hubData?.expires ? 'Never.' - : ``, + : `${time(Math.round(hubData.expires.getTime() / 1000), 'R')}`, }, ), }); @@ -110,7 +109,7 @@ export default class ListBlacklists extends BlacklistCommand { reason: `${hubData?.reason}`, expires: !hubData?.expires ? 'Never.' - : ``, + : `${time(Math.round(hubData.expires.getTime() / 1000), 'R')}`, }, ), }); diff --git a/src/commands/slash/Main/blacklist/server.ts b/src/commands/slash/Main/blacklist/server.ts index 0667e4b2..e55b0ef5 100644 --- a/src/commands/slash/Main/blacklist/server.ts +++ b/src/commands/slash/Main/blacklist/server.ts @@ -1,5 +1,5 @@ import { captureException } from '@sentry/node'; -import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; +import { ChatInputCommandInteraction, EmbedBuilder, time } from 'discord.js'; import { simpleEmbed } from '../../../../utils/Utils.js'; import { emojis } from '../../../../utils/Constants.js'; import db from '../../../../utils/Db.js'; @@ -111,7 +111,7 @@ export default class UserBlacklist extends BlacklistCommand { }, { name: 'Expires', - value: expires ? `` : 'Never.', + value: expires ? `${time(Math.round(expires.getTime() / 1000), 'R')}` : 'Never.', inline: true, }, ); diff --git a/src/commands/slash/Main/blacklist/user.ts b/src/commands/slash/Main/blacklist/user.ts index df781532..b4341057 100644 --- a/src/commands/slash/Main/blacklist/user.ts +++ b/src/commands/slash/Main/blacklist/user.ts @@ -1,4 +1,4 @@ -import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; +import { ChatInputCommandInteraction, EmbedBuilder, time } from 'discord.js'; import db from '../../../../utils/Db.js'; import BlacklistCommand from './index.js'; import BlacklistManager from '../../../../managers/BlacklistManager.js'; @@ -100,7 +100,7 @@ export default class Server extends BlacklistCommand { }, { name: 'Expires', - value: expires ? `` : 'Never.', + value: expires ? `${time(Math.round(expires.getTime() / 1000), 'R')}` : 'Never.', inline: true, }, ); diff --git a/src/commands/slash/Main/hub/browse.ts b/src/commands/slash/Main/hub/browse.ts index a1a52f25..1ce173bd 100644 --- a/src/commands/slash/Main/hub/browse.ts +++ b/src/commands/slash/Main/hub/browse.ts @@ -13,6 +13,7 @@ import { TextInputStyle, ModalSubmitInteraction, ChannelSelectMenuInteraction, + time, } from 'discord.js'; import db from '../../../../utils/Db.js'; import Hub from './index.js'; @@ -401,7 +402,10 @@ export default class Browse extends Hub { // log the server join to hub const hubLogger = await new HubLogsManager(hubDetails.id).init(); - await hubLogger.logServerJoin(interaction.guild, { totalConnections, hubName: hubDetails.name }); + await hubLogger.logServerJoin(interaction.guild, { + totalConnections, + hubName: hubDetails.name, + }); } } @@ -456,8 +460,9 @@ export default class Browse extends Hub { const lastMessageTimestamp = lastMessage?.getTime() ?? 0; const lastMessageStr = lastMessageTimestamp - ? `` + ? `${time(Math.round(lastMessageTimestamp / 1000), 'R')}` : '-'; + const hubCreatedAt = time(Math.round(hub.createdAt.getTime() / 1000), 'd'); return new EmbedBuilder() .setTitle(hub.name) @@ -467,7 +472,7 @@ export default class Browse extends Hub { name: 'Information', value: stripIndents` ${emojis.connect_icon} **Servers:** ${connections ?? 'Unknown.'} - ${emojis.clock_icon} **Created At:** + ${emojis.clock_icon} **Created At:** ${hubCreatedAt} ${emojis.chat_icon} **Last Message:** ${lastMessageStr} `, inline: true, diff --git a/src/commands/slash/Main/hub/index.ts b/src/commands/slash/Main/hub/index.ts index a5e586ea..a2953df0 100644 --- a/src/commands/slash/Main/hub/index.ts +++ b/src/commands/slash/Main/hub/index.ts @@ -9,9 +9,7 @@ import { } from 'discord.js'; import BaseCommand from '../../../BaseCommand.js'; import db from '../../../../utils/Db.js'; -import { replyWithError } from '../../../../utils/Utils.js'; -import Logger from '../../../../utils/Logger.js'; -import { captureException } from '@sentry/node'; +import { handleError } from '../../../../utils/Utils.js'; const hubOption: APIApplicationCommandBasicOption = { type: ApplicationCommandOptionType.String, @@ -284,12 +282,7 @@ export default class Hub extends BaseCommand { interaction.options.getSubcommandGroup() || interaction.options.getSubcommand(), ); - return await subcommand?.execute(interaction).catch((e) => { - Logger.error(e); - captureException(e); - - replyWithError(interaction, e.message); - }); + return await subcommand?.execute(interaction).catch((e) => handleError(e, interaction)); } async autocomplete(interaction: AutocompleteInteraction): Promise { diff --git a/src/commands/slash/Staff/find/index.ts b/src/commands/slash/Staff/find/index.ts index 00cdb188..a610bf21 100644 --- a/src/commands/slash/Staff/find/index.ts +++ b/src/commands/slash/Staff/find/index.ts @@ -6,9 +6,7 @@ import { RESTPostAPIApplicationCommandsJSONBody, } from 'discord.js'; import BaseCommand from '../../../BaseCommand.js'; -import Logger from '../../../../utils/Logger.js'; -import { captureException } from '@sentry/node'; -import { replyWithError } from '../../../../utils/Utils.js'; +import { handleError } from '../../../../utils/Utils.js'; export default class Find extends BaseCommand { staffOnly = true; @@ -64,12 +62,7 @@ export default class Find extends BaseCommand { async execute(interaction: ChatInputCommandInteraction) { const subcommand = Find.subcommands?.get(interaction.options.getSubcommand()); - return await subcommand?.execute(interaction).catch((e) => { - Logger.error(e); - captureException(e); - // reply with an error message to the user - replyWithError(interaction, e.message); - }); + return await subcommand?.execute(interaction).catch((e) => handleError(e, interaction)); } async autocomplete(interaction: AutocompleteInteraction) { const subcommand = interaction.options.getSubcommand(); diff --git a/src/commands/slash/Support/support/index.ts b/src/commands/slash/Support/support/index.ts index fab4dcc7..8b24cd66 100644 --- a/src/commands/slash/Support/support/index.ts +++ b/src/commands/slash/Support/support/index.ts @@ -5,10 +5,8 @@ import { Collection, RESTPostAPIApplicationCommandsJSONBody, } from 'discord.js'; +import { handleError } from '../../../../utils/Utils.js'; import BaseCommand from '../../../BaseCommand.js'; -import Logger from '../../../../utils/Logger.js'; -import { captureException } from '@sentry/node'; -import { replyWithError } from '../../../../utils/Utils.js'; export default class Support extends BaseCommand { readonly data: RESTPostAPIApplicationCommandsJSONBody = { @@ -50,11 +48,6 @@ export default class Support extends BaseCommand { const subCommandName = interaction.options.getSubcommand(); const subcommand = Support.subcommands?.get(subCommandName); - await subcommand?.execute(interaction).catch((e) => { - Logger.error(e); - captureException(e); - // reply with an error message to the user - replyWithError(interaction, e.message); - }); + await subcommand?.execute(interaction).catch((e) => handleError(e, interaction)); } } diff --git a/src/managers/CommandManager.ts b/src/managers/CommandManager.ts index 2bc835f8..75400561 100644 --- a/src/managers/CommandManager.ts +++ b/src/managers/CommandManager.ts @@ -1,11 +1,9 @@ import { t } from '../utils/Locale.js'; import { join, dirname } from 'path'; import { CustomID } from '../utils/CustomID.js'; -import { Interaction } from 'discord.js'; -import { captureException } from '@sentry/node'; -import { simpleEmbed, replyWithError } from '../utils/Utils.js'; +import { Interaction, time } from 'discord.js'; +import { simpleEmbed, handleError } from '../utils/Utils.js'; import { access, constants, readdirSync, statSync } from 'fs'; -import Logger from '../utils/Logger.js'; import Factory from '../Factory.js'; import BaseCommand, { commandsMap } from '../commands/BaseCommand.js'; @@ -77,7 +75,7 @@ export default class CommandManager extends Factory { await interaction.reply({ content: t( { phrase: 'errors.cooldown', locale: interaction.user.locale }, - { time: `until ()` }, + { time: `until ${time(waitUntil, 'T')} (${time(waitUntil, 'R')})` }, ), ephemeral: true, }); @@ -85,7 +83,7 @@ export default class CommandManager extends Factory { } // run the command - command?.execute(interaction); + await command?.execute(interaction); } else { const customId = CustomID.parseCustomId(interaction.customId); @@ -114,22 +112,7 @@ export default class CommandManager extends Factory { } } catch (e) { - // log the error to the system - Logger.error(e); - // capture the error to Sentry.io with additional information - captureException(e, { - user: { id: interaction.user.id, username: interaction.user.username }, - extra: { - type: interaction.type, - identifier: - interaction.isCommand() || interaction.isAutocomplete() - ? interaction.commandName - : CustomID.parseCustomId(interaction.customId), - }, - }); - - // reply with an error message to the user - if (interaction.isRepliable()) replyWithError(interaction, e); + handleError(e, interaction); } } diff --git a/src/updater/ReactionUpdater.ts b/src/updater/ReactionUpdater.ts index c47da4bd..2169e2fc 100644 --- a/src/updater/ReactionUpdater.ts +++ b/src/updater/ReactionUpdater.ts @@ -15,6 +15,7 @@ import { StringSelectMenuBuilder, User, WebhookClient, + time, } from 'discord.js'; import { sortReactions } from '../utils/Utils.js'; import { HubSettingsBitField } from '../utils/BitFields.js'; @@ -211,10 +212,9 @@ export default class ReactionUpdater extends Factory { } if (cooldown && cooldown > Date.now()) { + const timeString = time(Math.round(cooldown / 1000), 'R'); return await interaction.followUp({ - content: `A little quick there! You can react again !`, + content: `A little quick there! You can react again ${timeString}!`, ephemeral: true, }); } @@ -317,8 +317,7 @@ export default class ReactionUpdater extends Factory { }) .catch(() => null); - - // FIXME: Fix not being able to react to messages with no reply button + // FIXME: Fix not being able to react to messages with reply button const components = message?.components?.filter((row) => { // filter all buttons that are not reaction buttons row.components = row.components.filter((component) => { diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 7ba312be..d95c567b 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -1,4 +1,5 @@ import db from './Db.js'; +import Logger from './Logger.js'; import toLower from 'lodash/toLower.js'; import Scheduler from '../services/SchedulerService.js'; import startCase from 'lodash/startCase.js'; @@ -9,6 +10,7 @@ import { ColorResolvable, ComponentType, EmbedBuilder, + Interaction, Message, MessageActionRowComponent, NewsChannel, @@ -21,6 +23,8 @@ import { DeveloperIds, REGEX, StaffIds, SupporterIds, LINKS, colors, emojis } fr import { randomBytes } from 'crypto'; import { t } from './Locale.js'; import 'dotenv/config'; +import { captureException } from '@sentry/node'; +import { CustomID } from './CustomID.js'; /** Convert milliseconds to a human readable time (eg: 1d 2h 3m 4s) */ export function msToReadable(milliseconds: number): string { @@ -271,3 +275,28 @@ export function channelMention(channelId: Snowflake | null | undefined) { if (!channelId) return emojis.no; return `<#${channelId}>`; } + +export function handleError(e: Error, interaction?: Interaction) { + // log the error to the system + Logger.error(e); + let extra; + + if (interaction) { + extra = { + user: { id: interaction.user.id, username: interaction.user.username }, + extra: { + type: interaction.type, + identifier: + interaction.isCommand() || interaction.isAutocomplete() + ? interaction.commandName + : CustomID.parseCustomId(interaction.customId), + }, + }; + } + + // capture the error to Sentry.io with additional information + captureException(e, extra); + + // reply with an error message to the user + if (interaction?.isRepliable()) replyWithError(interaction, String(e)); +}