From 13f2bf30faf56ccb2f28ed82f68c96ac6f82e3bd Mon Sep 17 00:00:00 2001 From: Michel Fedde Date: Tue, 27 May 2025 20:00:10 +0200 Subject: [PATCH] Adds transfer of leadership of a group --- .../Discord/CommandPartials/GroupSelection.ts | 2 +- source/Discord/Commands/Groups.ts | 52 ++++++++++++++++++- source/Discord/DiscordClient.ts | 8 ++- source/Discord/UserError.ts | 9 +++- 4 files changed, 67 insertions(+), 4 deletions(-) diff --git a/source/Discord/CommandPartials/GroupSelection.ts b/source/Discord/CommandPartials/GroupSelection.ts index 46b8ef1..4804428 100644 --- a/source/Discord/CommandPartials/GroupSelection.ts +++ b/source/Discord/CommandPartials/GroupSelection.ts @@ -14,7 +14,7 @@ export class GroupSelection { public static createOptionSetup(): SlashCommandIntegerOption { return new SlashCommandIntegerOption() .setName("group") - .setDescription("Defines the group you want to manage the playdates for") + .setDescription("Defines the group this action is for") .setRequired(true) .setAutocomplete(true) } diff --git a/source/Discord/Commands/Groups.ts b/source/Discord/Commands/Groups.ts index 0b29d5c..97d165f 100644 --- a/source/Discord/Commands/Groups.ts +++ b/source/Discord/Commands/Groups.ts @@ -9,7 +9,7 @@ import { GuildMember, EmbedBuilder, AutocompleteInteraction, - formatEmoji, roleMention, time + formatEmoji, roleMention, time, userMention } from "discord.js"; import {AutocompleteCommand, ChatInteractionCommand, Command} from "./Command"; import {GroupModel} from "../../Models/GroupModel"; @@ -26,6 +26,7 @@ import {GroupConfigurationRepository} from "../../Repositories/GroupConfiguratio import {IconCache} from "../../Icons/IconCache"; import {PlaydateRepository} from "../../Repositories/PlaydateRepository"; import playdate from "../../Database/tables/Playdate"; +import Commands from "./Commands"; export class GroupCommand implements Command, ChatInteractionCommand, AutocompleteCommand { private static GOODBYE_MESSAGES: string[] = [ @@ -68,6 +69,16 @@ export class GroupCommand implements Command, ChatInteractionCommand, Autocomple .setName("remove") .setDescription("Deletes a group you are the leader for.") .addIntegerOption(GroupSelection.createOptionSetup()) + ) + .addSubcommand(command => command + .setName("transfer") + .setDescription("Transfers leadership of a group to a different person") + .addIntegerOption(GroupSelection.createOptionSetup) + .addUserOption(option => option + .setName("target") + .setDescription("The member, that is the new leader") + .setRequired(true) + ) ); } @@ -85,6 +96,9 @@ export class GroupCommand implements Command, ChatInteractionCommand, Autocomple case "config": await this.runConfigurator(interaction); break; + case "transfer": + await this.transferLeadership(interaction); + break; default: throw new Error("Unsupported command"); } @@ -201,4 +215,40 @@ export class GroupCommand implements Command, ChatInteractionCommand, Autocomple await configurationRenderer.setup(interaction); } + + private async transferLeadership(interaction: ChatInputCommandInteraction) { + const group = GroupSelection.getGroup(interaction); + + const repo = Container.get(GroupRepository.name); + if (group.leader.memberid != interaction.member?.user.id) { + throw new UserError( + "Can't transfer leadership. You are not the leader." + ); + } + + const newLeader = interaction.options.getMember("target", true); + if (!newLeader.roles.cache.has(group.role.roleid)) { + throw new UserError( + "Can't transfer leadership: The target member is not part of your group.", + "Add the user to the role this group is part in or ask your server admin to do it." + ); + } + + group.leader.memberid = newLeader.id + repo.update(group); + + + const embed = new EmbedBuilder() + .setTitle("Leadership transfered") + .setDescription( + `Leadership was successfully transfered to ${userMention(newLeader.user.id)}` + ) + + await interaction.reply({ + embeds: [ + embed + ], + flags: MessageFlags.Ephemeral, + }) + } } \ No newline at end of file diff --git a/source/Discord/DiscordClient.ts b/source/Discord/DiscordClient.ts index f149f90..92c0fc8 100644 --- a/source/Discord/DiscordClient.ts +++ b/source/Discord/DiscordClient.ts @@ -6,7 +6,7 @@ import { ChatInputCommandInteraction, MessageFlags, Activity, - ActivityType, REST + ActivityType, REST, inlineCode } from "discord.js"; import Commands from "./Commands/Commands"; import {Container} from "../Container/Container"; @@ -96,6 +96,12 @@ export class DiscordClient { let userMessage = ":x: There was an error while executing this command!"; if (e.constructor.name === UserError.name) { userMessage = `:x: \`${e.message}\` - Please validate your request!` + if (e.tryInstead) { + userMessage += ` + +You can try the following: +${inlineCode(e.tryInstead)}` + } } if (interaction.replied || interaction.deferred) { await interaction.followUp({ content: userMessage, flags: MessageFlags.Ephemeral }); diff --git a/source/Discord/UserError.ts b/source/Discord/UserError.ts index 4d10465..7eb76cf 100644 --- a/source/Discord/UserError.ts +++ b/source/Discord/UserError.ts @@ -1,3 +1,10 @@ export class UserError extends Error { - + constructor( + message: string, + public readonly tryInstead: string|null = null + ) { + super(message); + + } + } \ No newline at end of file