diff --git a/package-lock.json b/package-lock.json index 8da6a6e6..79a95125 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@sapphire/discord.js-utilities": "^7.1.6", "@sentry/node": "^7.28.0", "@sentry/tracing": "^7.28.0", - "@wise-old-man/utils": "^3.3.1", + "@wise-old-man/utils": "^3.3.3", "canvas": "^2.6.1", "cors": "^2.8.5", "discord.js": "^14.14.1", @@ -888,9 +888,9 @@ } }, "node_modules/@wise-old-man/utils": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@wise-old-man/utils/-/utils-3.3.1.tgz", - "integrity": "sha512-TdpB48LnIH75DD+zlES2iPjws6+b/d5OsK5KgnmrjZArMtrJIOAGafbMjoUORGf9EfRv58sg0rzrloK14nGVMQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@wise-old-man/utils/-/utils-3.3.3.tgz", + "integrity": "sha512-QR19g8HMuN2bz6p0gmihc6krfTKq9lZ35Dnbux/z6zubxhB8oMnpxNfcG6hMo7Wlcuq3SXjvrcDvEen/R3cxDQ==", "dependencies": { "dayjs": "^1.11.5" } diff --git a/package.json b/package.json index 73fa1ca7..4fc0cf65 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "@sapphire/discord.js-utilities": "^7.1.6", "@sentry/node": "^7.28.0", "@sentry/tracing": "^7.28.0", - "@wise-old-man/utils": "^3.3.1", + "@wise-old-man/utils": "^3.3.3", "canvas": "^2.6.1", "cors": "^2.8.5", "discord.js": "^14.14.1", diff --git a/src/bot.ts b/src/bot.ts index 548e21a9..087a9eaf 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -6,18 +6,13 @@ import { TextChannel, GatewayIntentBits, ChannelType, - PermissionFlagsBits, - Options + Options, + PermissionFlagsBits } from 'discord.js'; import config from './config'; import * as router from './commands/router'; -import { - PATREON_MODAL_ID, - PATREON_TRIGGER_ID, - handlePatreonModalSubmit, - handlePatreonTrigger, - setupPatreonTrigger -} from './patreon-trigger'; +import { PATREON_MODAL_ID, handlePatreonModalSubmit, setupPatreonTrigger } from './patreon-trigger'; +import { handleButtonInteraction } from './utils/buttonInteractions'; const CACHED_ACTIVE_USER_IDS = new Set(config.discord.cache.excludeUsers); const CACHED_ACTIVE_GUILD_IDS = new Set(config.discord.cache.excludeGuilds); @@ -79,9 +74,9 @@ class Bot { this.client.user?.setActivity('bot.wiseoldman.net'); // Send received interaction to the command router - this.client.on('interactionCreate', (interaction: Interaction) => { - if (interaction.isButton() && interaction.customId === PATREON_TRIGGER_ID) { - handlePatreonTrigger(interaction); + this.client.on('interactionCreate', async (interaction: Interaction) => { + if (interaction.isButton()) { + handleButtonInteraction(interaction); return; } diff --git a/src/commands/instances/group/GroupGainedCommand.ts b/src/commands/instances/group/GroupGainedCommand.ts index f0702cf1..37883bc5 100644 --- a/src/commands/instances/group/GroupGainedCommand.ts +++ b/src/commands/instances/group/GroupGainedCommand.ts @@ -3,12 +3,11 @@ import { getMetricName, isMetric, Metric, - parseMetricAbbreviation, parsePeriodExpression, PeriodProps } from '@wise-old-man/utils'; import { ApplicationCommandOptionType, ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; -import womClient from '../../../services/wiseoldman'; +import womClient, { parseMetricAbbreviation } from '../../../services/wiseoldman'; import config from '../../../config'; import { Command, CommandConfig, CommandError, getEmoji, getLinkedGroupId, bold } from '../../../utils'; diff --git a/src/commands/instances/group/GroupHiscoresCommand.ts b/src/commands/instances/group/GroupHiscoresCommand.ts index ddbbcef8..06de1ee4 100644 --- a/src/commands/instances/group/GroupHiscoresCommand.ts +++ b/src/commands/instances/group/GroupHiscoresCommand.ts @@ -1,12 +1,6 @@ -import { - formatNumber, - getMetricName, - GroupHiscoresEntry, - Metric, - parseMetricAbbreviation -} from '@wise-old-man/utils'; +import { formatNumber, getMetricName, GroupHiscoresEntry, Metric } from '@wise-old-man/utils'; import { ApplicationCommandOptionType, ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; -import womClient from '../../../services/wiseoldman'; +import womClient, { parseMetricAbbreviation } from '../../../services/wiseoldman'; import config from '../../../config'; import { bold, Command, CommandConfig, CommandError, getEmoji, getLinkedGroupId } from '../../../utils'; diff --git a/src/commands/instances/group/GroupRecordsCommand.ts b/src/commands/instances/group/GroupRecordsCommand.ts index 90cc6451..4d6390b3 100644 --- a/src/commands/instances/group/GroupRecordsCommand.ts +++ b/src/commands/instances/group/GroupRecordsCommand.ts @@ -4,13 +4,12 @@ import { isMetric, isPeriod, Metric, - parseMetricAbbreviation, Period, PeriodProps, PERIODS } from '@wise-old-man/utils'; import { ApplicationCommandOptionType, ChatInputCommandInteraction, EmbedBuilder } from 'discord.js'; -import womClient from '../../../services/wiseoldman'; +import womClient, { parseMetricAbbreviation } from '../../../services/wiseoldman'; import config from '../../../config'; import { bold, Command, CommandConfig, CommandError, getEmoji, getLinkedGroupId } from '../../../utils'; diff --git a/src/commands/instances/player/PlayerGainedCommand.ts b/src/commands/instances/player/PlayerGainedCommand.ts index 52a8d84c..6b1d5528 100644 --- a/src/commands/instances/player/PlayerGainedCommand.ts +++ b/src/commands/instances/player/PlayerGainedCommand.ts @@ -109,11 +109,7 @@ class PlayerGainedCommand extends Command { } } -function buildPages( - displayName: string, - period: string, - gained: GetPlayerGainsResponse -) { +function buildPages(displayName: string, period: string, gained: GetPlayerGainsResponse) { const gainsList = buildGainsList(displayName, period, gained); const pageCount = Math.ceil(gainsList.length / GAINS_PER_PAGE); @@ -137,11 +133,7 @@ function buildPages( return pages; } -function buildGainsList( - displayName: string, - period: string, - gained: GetPlayerGainsResponse -) { +function buildGainsList(displayName: string, period: string, gained: GetPlayerGainsResponse) { const computedGains = Array.from(Object.entries(gained.data.computed)) .filter(([, e]) => e.value.gained > 0) .map(([key, val]) => ({ metric: key, gained: val.value.gained })) diff --git a/src/config.ts b/src/config.ts index f95b94b4..0d27fe2d 100644 --- a/src/config.ts +++ b/src/config.ts @@ -31,7 +31,8 @@ export default { flags: env.DISCORD_DEV_FLAG_CHANNEL_ID || '802680940835897384', modLogs: env.DISCORD_DEV_MODSLOGS_CHANNEL_ID || '830199626630955039', patreonInfo: env.DISCORD_DEV_PATREON_INFO_CHANNEL_ID || '1173680059526152272', - flaggedPlayerReviews: env.DISCORD_DEV_FLAGGED_REVIEWS_CHANNEL_ID || '1086637095415722169' + flaggedPlayerReviews: env.DISCORD_DEV_FLAGGED_REVIEWS_CHANNEL_ID || '1086637095415722169', + underAttackModeFeed: env.DISCORD_DEV_UNDER_ATTACK_MODE_FEED_CHANNEL_ID || '1263280946753441937' }, cache: { excludeUsers: [ diff --git a/src/events/instances/HiddenCompetitionCreated.ts b/src/events/instances/HiddenCompetitionCreated.ts new file mode 100644 index 00000000..affa0b55 --- /dev/null +++ b/src/events/instances/HiddenCompetitionCreated.ts @@ -0,0 +1,41 @@ +import { ChannelType, Client, EmbedBuilder, TextChannel } from 'discord.js'; +import { encodeURL, Event } from '../../utils'; +import { CompetitionListItem } from '@wise-old-man/utils'; +import { createModerationButtons, ModerationType } from '../../utils/buttonInteractions'; +import config from '../../config'; + +class HiddenCompetitionCreated implements Event { + type: string; + + constructor() { + this.type = 'HIDDEN_COMPETITION_CREATED'; + } + + async execute(data: CompetitionListItem, client: Client) { + const { id, title, groupId, participantCount } = data['competition']; + + const actions = createModerationButtons(ModerationType.COMPETITION, id); + + const message = new EmbedBuilder() + .setColor(config.visuals.blue) + .setTitle(`A hidden competition was created`) + .setDescription( + `Id: ${id}\nTitle: ${title}\nParticipants: ${participantCount}\nGroup Id: ${ + groupId ? '[' + groupId + '](https://wiseoldman.net/groups/' + groupId + ')' : groupId + }` + ) + .setURL(encodeURL(`https://wiseoldman.net/competitions/${id}`)); + + const reviewChannel = client.channels?.cache.get(config.discord.channels.underAttackModeFeed); + if (!reviewChannel) return; + if (!((channel): channel is TextChannel => channel.type === ChannelType.GuildText)(reviewChannel)) + return; + + await reviewChannel.send({ + embeds: [message], + components: [actions] + }); + } +} + +export default new HiddenCompetitionCreated(); diff --git a/src/events/instances/HiddenGroupCreated.ts b/src/events/instances/HiddenGroupCreated.ts new file mode 100644 index 00000000..5468e8de --- /dev/null +++ b/src/events/instances/HiddenGroupCreated.ts @@ -0,0 +1,38 @@ +import { ChannelType, Client, EmbedBuilder, TextChannel } from 'discord.js'; +import { Event } from '../../utils/events'; +import { GroupListItem } from '@wise-old-man/utils'; +import config from '../../config'; +import { encodeURL } from '../../utils'; +import { createModerationButtons, ModerationType } from '../../utils/buttonInteractions'; + +class HiddenGroupCreated implements Event { + type: string; + + constructor() { + this.type = 'HIDDEN_GROUP_CREATED'; + } + + async execute(data: GroupListItem, client: Client) { + const { id, name, description, memberCount } = data['group']; + + const actions = createModerationButtons(ModerationType.GROUP, id); + + const message = new EmbedBuilder() + .setColor(config.visuals.blue) + .setTitle(`A hidden group was created`) + .setDescription(`Id: ${id}\nName: ${name}\nDescription: ${description}\nMembers: ${memberCount}`) + .setURL(encodeURL(`https://wiseoldman.net/groups/${id}`)); + + const reviewChannel = client.channels?.cache.get(config.discord.channels.underAttackModeFeed); + if (!reviewChannel) return; + if (!((channel): channel is TextChannel => channel.type === ChannelType.GuildText)(reviewChannel)) + return; + + await reviewChannel.send({ + embeds: [message], + components: [actions] + }); + } +} + +export default new HiddenGroupCreated(); diff --git a/src/events/router.ts b/src/events/router.ts index b1226393..6ddcbb51 100644 --- a/src/events/router.ts +++ b/src/events/router.ts @@ -13,6 +13,8 @@ import MembersJoined from './instances/MembersJoined'; import MembersLeft from './instances/MembersLeft'; import PlayerFlaggedReview from './instances/PlayerFlaggedReview'; import MembersRolesChanged from './instances/MembersRolesChanged'; +import HiddenGroupCreated from './instances/HiddenGroupCreated'; +import HiddenCompetitionCreated from './instances/HiddenCompetitionCreated'; const EVENTS: Event[] = [ CompetitionCreated, @@ -26,7 +28,9 @@ const EVENTS: Event[] = [ MemberHardcoreDied, MemberAchievements, PlayerFlaggedReview, - MembersRolesChanged + MembersRolesChanged, + HiddenGroupCreated, + HiddenCompetitionCreated ]; function onEventReceived(client: Client, payload: { type: string; data: unknown }): void { diff --git a/src/services/wiseoldman.ts b/src/services/wiseoldman.ts index e4a1980b..caabf012 100644 --- a/src/services/wiseoldman.ts +++ b/src/services/wiseoldman.ts @@ -6,7 +6,9 @@ import { GroupListItem, NameChange, Player, - NameChangeDetails + NameChangeDetails, + isMetric, + Metric } from '@wise-old-man/utils'; import env from '../env'; import { durationBetween } from '../utils/dates'; @@ -112,6 +114,16 @@ export async function deleteGroup(groupId: number): Promise<{ message: string }> }); } +/** + * Send an API request to set visibility of a group to true. + */ +export async function setGroupVisible(groupId: number): Promise<{ message: string }> { + return womClient.groups.putRequest(`/groups/${groupId}/visibility`, { + visible: true, + adminPassword: env.ADMIN_PASSWORD + }); +} + /** * Send an API request to delete a competition. */ @@ -121,6 +133,16 @@ export async function deleteCompetition(competitionId: number): Promise<{ messag }); } +/** + * Send an API request to set visibility of a group to true. + */ +export async function setCompetitionVisible(competitionId: number): Promise<{ message: string }> { + return womClient.competitions.putRequest(`/competitions/${competitionId}/visibility`, { + visible: true, + adminPassword: env.ADMIN_PASSWORD + }); +} + export async function approveNameChange(id: number): Promise { return womClient.nameChanges.postRequest(`/names/${id}/approve`, { adminPassword: env.ADMIN_PASSWORD @@ -214,4 +236,330 @@ export async function forceUpdate(username: string) { }); } +export function parseMetricAbbreviation(abbreviation: string): Metric | null { + if (!abbreviation || abbreviation.length === 0) { + return null; + } + + const fixedAbbreviation = abbreviation.toLowerCase(); + + if (isMetric(fixedAbbreviation)) { + return fixedAbbreviation; + } + + switch (fixedAbbreviation) { + // Bosses + case 'sire': + return Metric.ABYSSAL_SIRE; + + case 'hydra': + return Metric.ALCHEMICAL_HYDRA; + + case 'barrows': + return Metric.BARROWS_CHESTS; + + case 'bryo': + return Metric.BRYOPHYTA; + + case 'cerb': + return Metric.CERBERUS; + + case 'cox': + case 'xeric': + case 'chambers': + case 'olm': + case 'raids': + return Metric.CHAMBERS_OF_XERIC; + + case 'cox-cm': + case 'xeric-cm': + case 'chambers-cm': + case 'olm-cm': + case 'raids-cm': + return Metric.CHAMBERS_OF_XERIC_CM; + + case 'chaos-ele': + return Metric.CHAOS_ELEMENTAL; + + case 'fanatic': + return Metric.CHAOS_FANATIC; + + case 'sara': + case 'saradomin': + case 'zilyana': + case 'zily': + return Metric.COMMANDER_ZILYANA; + + case 'corp': + return Metric.CORPOREAL_BEAST; + + case 'crazy-arch': + return Metric.CRAZY_ARCHAEOLOGIST; + + case 'prime': + return Metric.DAGANNOTH_PRIME; + case 'rex': + return Metric.DAGANNOTH_REX; + case 'supreme': + return Metric.DAGANNOTH_SUPREME; + + case 'deranged-arch': + return Metric.DERANGED_ARCHAEOLOGIST; + + case 'bandos': + case 'graardor': + return Metric.GENERAL_GRAARDOR; + + case 'mole': + return Metric.GIANT_MOLE; + + case 'dusk': + case 'dawn': + case 'gargs': + case 'guardians': + case 'ggs': + return Metric.GROTESQUE_GUARDIANS; + + case 'phantom': + case 'muspah': + return Metric.PHANTOM_MUSPAH; + + case 'kq': + return Metric.KALPHITE_QUEEN; + + case 'kbd': + return Metric.KING_BLACK_DRAGON; + + case 'kree': + case 'kreearra': + case 'armadyl': + case 'arma': + return Metric.KREEARRA; + + case 'zammy': + case 'zamorak': + case 'kril': + case 'kril-tsutsaroth': + return Metric.KRIL_TSUTSAROTH; + + case 'gaunt': + case 'gauntlet': + case 'the-gauntlet': + return Metric.THE_GAUNTLET; + + case 'cgaunt': + case 'cgauntlet': + case 'corrupted': + case 'corrupted-gauntlet': + case 'the-corrupted-gauntlet': + return Metric.THE_CORRUPTED_GAUNTLET; + + case 'tob': + case 'theatre': + case 'verzik': + case 'tob-normal': + return Metric.THEATRE_OF_BLOOD; + + case 'tob-hm': + case 'tob-cm': + case 'tob-hard-mode': + case 'tob-hard': + return Metric.THEATRE_OF_BLOOD_HARD_MODE; + + case 'toa': + case 'tombs': + case 'amascut': + return Metric.TOMBS_OF_AMASCUT; + + case 'toa-expert': + case 'toa-hm': + case 'tombs-expert': + case 'tombs-hm': + case 'amascut-expert': + case 'amascut-hm': + return Metric.TOMBS_OF_AMASCUT_EXPERT; + + case 'nm': + case 'tnm': + case 'nmare': + case 'the-nightmare': + return Metric.NIGHTMARE; + + case 'pnm': + case 'phosani': + case 'phosanis': + case 'phosani-nm': + case 'phosani-nightmare': + case 'phosanis nightmare': + return Metric.PHOSANIS_NIGHTMARE; + + case 'thermy': + case 'smoke-devil': + return Metric.THERMONUCLEAR_SMOKE_DEVIL; + + case 'zuk': + case 'inferno': + return Metric.TZKAL_ZUK; + + case 'jad': + case 'fight-caves': + case 'fc': + return Metric.TZTOK_JAD; + + case 'vork': + case 'vorky': + return Metric.VORKATH; + + case 'wt': + return Metric.WINTERTODT; + + case 'snek': + case 'zul': + return Metric.ZULRAH; + + // Minigames and others + + case 'all-clues': + case 'clues': + return Metric.CLUE_SCROLLS_ALL; + + case 'beginner': + case 'beginner-clues': + case 'beg-clues': + case 'beginners': + return Metric.CLUE_SCROLLS_BEGINNER; + + case 'easy': + case 'easy-clues': + case 'easies': + return Metric.CLUE_SCROLLS_EASY; + + case 'medium': + case 'med': + case 'meds': + case 'medium-clues': + case 'med-clues': + case 'mediums': + return Metric.CLUE_SCROLLS_MEDIUM; + + case 'hard': + case 'hard-clues': + case 'hards': + return Metric.CLUE_SCROLLS_HARD; + + case 'elite': + case 'elite-clues': + case 'elites': + return Metric.CLUE_SCROLLS_ELITE; + + case 'master': + case 'master-clues': + case 'masters': + return Metric.CLUE_SCROLLS_MASTER; + + case 'lms': + return Metric.LAST_MAN_STANDING; + + case 'league': + case 'lp': + case 'lps': + return Metric.LEAGUE_POINTS; + + case 'sw': + case 'zeal': + case 'soul-wars': + return Metric.SOUL_WARS_ZEAL; + + case 'rifts-closed': + case 'gotr': + case 'rifts': + return Metric.GUARDIANS_OF_THE_RIFT; + + // Skills + + case 'runecraft': + case 'rc': + return Metric.RUNECRAFTING; + + case 'att': + case 'atk': + case 'attk': + return Metric.ATTACK; + + case 'def': + case 'defense': + return Metric.DEFENCE; + + case 'str': + return Metric.STRENGTH; + + case 'hp': + return Metric.HITPOINTS; + + case 'range': + return Metric.RANGED; + + case 'pray': + return Metric.PRAYER; + + case 'mage': + return Metric.MAGIC; + + case 'cook': + return Metric.COOKING; + + case 'wc': + return Metric.WOODCUTTING; + + case 'fletch': + return Metric.FLETCHING; + + case 'fish': + return Metric.FISHING; + + case 'fm': + case 'burning': + return Metric.FIREMAKING; + + case 'craft': + return Metric.CRAFTING; + + case 'sm': + case 'smith': + return Metric.SMITHING; + + case 'mine': + case 'smash': + return Metric.MINING; + + case 'herb': + return Metric.HERBLORE; + + case 'agi': + case 'agil': + return Metric.AGILITY; + + case 'thief': + return Metric.THIEVING; + + case 'slay': + return Metric.SLAYER; + + case 'farm': + return Metric.FARMING; + + case 'hunt': + case 'hunting': + return Metric.HUNTER; + + case 'con': + case 'cons': + case 'const': + return Metric.CONSTRUCTION; + + default: + return null; + } +} + export default womClient; diff --git a/src/utils/buttonInteractions.ts b/src/utils/buttonInteractions.ts new file mode 100644 index 00000000..3a7980a8 --- /dev/null +++ b/src/utils/buttonInteractions.ts @@ -0,0 +1,145 @@ +import { + ActionRowBuilder, + ButtonBuilder, + ButtonInteraction, + ButtonStyle, + EmbedBuilder +} from 'discord.js'; +import config from '../config'; +import { handlePatreonTrigger, PATREON_TRIGGER_ID } from '../patreon-trigger'; +import { + deleteCompetition, + deleteGroup, + setCompetitionVisible, + setGroupVisible +} from '../services/wiseoldman'; +import { CommandError } from './commands'; + +enum Actions { + DELETE = 'delete', + APPROVE = 'approve', + CONFIRM = 'confirm', + CANCEL = 'cancel' +} + +export enum ModerationType { + GROUP = 'group', + COMPETITION = 'competition' +} + +export async function handleButtonInteraction(interaction: ButtonInteraction): Promise { + const [action, type, id, confirmation] = interaction.customId.split('/'); + console.log(type, action, id, confirmation); + + if (action === PATREON_TRIGGER_ID) { + handlePatreonTrigger(interaction); + } else if (action === Actions.DELETE || action === Actions.APPROVE) { + await interaction.update({ + components: [createConfirmationButtons(action, type as ModerationType, id)] + }); + } else if (action === Actions.CONFIRM) { + if (type === ModerationType.GROUP) { + if (confirmation === Actions.DELETE) { + try { + await deleteGroup(parseInt(id)).catch(e => { + if (e.statusCode === 404) throw new CommandError('Group not found.'); + throw e; + }); + } catch (error) { + await interaction.reply({ ephemeral: true, content: `${error}` }); + return; + } + } else if (confirmation === Actions.APPROVE) { + try { + await setGroupVisible(parseInt(id)).catch(e => { + if (e.statusCode === 404) throw new CommandError('Group not found.'); + throw e; + }); + } catch (error) { + await interaction.reply({ ephemeral: true, content: `${error}` }); + return; + } + } + } else if (type === ModerationType.COMPETITION) { + if (confirmation === Actions.DELETE) { + try { + await deleteCompetition(parseInt(id)).catch(e => { + if (e.statusCode === 404) throw new CommandError('Competition not found.'); + throw e; + }); + } catch (error) { + await interaction.reply({ ephemeral: true, content: `${error}` }); + return; + } + } else if (confirmation === Actions.APPROVE) { + try { + await setCompetitionVisible(parseInt(id)).catch(e => { + if (e.statusCode === 404) throw new CommandError('Competition not found.'); + throw e; + }); + } catch (error) { + await interaction.reply({ ephemeral: true, content: `${error}` }); + return; + } + } + } + + const message = interaction.message; + const oldEmbed = message.embeds[0]; + + const editedEmbed = new EmbedBuilder() + .setTitle(oldEmbed.title) + .setDescription(oldEmbed.description) + .setURL(oldEmbed.url) + .setFooter({ + text: `${confirmation == Actions.DELETE ? 'Deleted ' : 'Approved '} by ${ + interaction.user.username + }` + }) + .setColor(confirmation == Actions.DELETE ? config.visuals.red : config.visuals.green); + + interaction.update({ embeds: [editedEmbed], components: [] }); + } else if (action === Actions.CANCEL) { + await interaction.update({ + components: [createModerationButtons(type as ModerationType, parseInt(id))] + }); + } +} + +export function createModerationButtons(type: ModerationType, id: number) { + const actions = new ActionRowBuilder(); + + actions.addComponents( + new ButtonBuilder() + .setCustomId(`${Actions.APPROVE}/${type}/${id}`) + .setLabel('Approve') + .setStyle(ButtonStyle.Primary), + new ButtonBuilder() + .setCustomId(`${Actions.DELETE}/${type}/${id}`) + .setLabel('Delete') + .setStyle(ButtonStyle.Danger) + ); + + return actions; +} + +function createConfirmationButtons( + action: Actions, + type: ModerationType, + id: string +): ActionRowBuilder { + const actions = new ActionRowBuilder(); + + actions.addComponents( + new ButtonBuilder() + .setCustomId(`${Actions.CONFIRM}/${type}/${id}/${action}`) + .setLabel(`Confirm ${action === Actions.DELETE ? 'deletion' : 'approval'}`) + .setStyle(action === Actions.DELETE ? ButtonStyle.Danger : ButtonStyle.Success), + new ButtonBuilder() + .setCustomId(`${Actions.CANCEL}/${type}/${id}`) + .setLabel('Cancel') + .setStyle(ButtonStyle.Secondary) + ); + + return actions; +} diff --git a/src/utils/discord.ts b/src/utils/discord.ts index 6414fd38..426bdcac 100644 --- a/src/utils/discord.ts +++ b/src/utils/discord.ts @@ -1,4 +1,4 @@ -import { GroupRole, Metric, parseMetricAbbreviation } from '@wise-old-man/utils'; +import { GroupRole, Metric } from '@wise-old-man/utils'; import { Channel, DMChannel, @@ -13,6 +13,7 @@ import { PermissionFlagsBits } from 'discord.js'; import config from '../config'; +import { parseMetricAbbreviation } from '../services/wiseoldman'; export const MAX_FIELD_SIZE = 25;