From b8feed971ccbf55a390f3738d602bb26f2afd710 Mon Sep 17 00:00:00 2001 From: Sarah Faey Date: Tue, 15 Nov 2022 19:00:56 +0100 Subject: [PATCH] reworked the commandHandler added Slash Commands --- DiscordBot/CommandHandler.cs | 199 ++---------------- DiscordBot/CommandHandlers/ButtonHandler.cs | 62 ++++++ .../CommandHandlers/HandlerFunctions.cs | 56 +++++ DiscordBot/CommandHandlers/ModalHandler.cs | 72 +++++++ .../CommandHandlers/SelectMenuHandler.cs | 96 +++++++++ .../CommandHandlers/SlashCommandHandler.cs | 154 ++++++++++++++ DiscordBot/Constants.cs | 20 +- DiscordBot/Messages/SignUpMessage.cs | 4 +- DiscordBot/Program.cs | 50 ++++- DiscordBot/Services/HttpService.cs | 16 ++ Lieb/Controllers/DiscordBotController.cs | 64 +++++- Lieb/Data/DbInitializer.cs | 3 +- Lieb/Data/DiscordService.cs | 11 +- Lieb/Data/RaidService.cs | 4 +- SharedClasses/SharedModels/ApiSignUp.cs | 2 + 15 files changed, 599 insertions(+), 214 deletions(-) create mode 100644 DiscordBot/CommandHandlers/ButtonHandler.cs create mode 100644 DiscordBot/CommandHandlers/HandlerFunctions.cs create mode 100644 DiscordBot/CommandHandlers/ModalHandler.cs create mode 100644 DiscordBot/CommandHandlers/SelectMenuHandler.cs create mode 100644 DiscordBot/CommandHandlers/SlashCommandHandler.cs diff --git a/DiscordBot/CommandHandler.cs b/DiscordBot/CommandHandler.cs index f639b35..fe29f7c 100644 --- a/DiscordBot/CommandHandler.cs +++ b/DiscordBot/CommandHandler.cs @@ -5,6 +5,7 @@ using System.Reflection; using DiscordBot.Services; using SharedClasses.SharedModels; using DiscordBot.Messages; +using DiscordBot.CommandHandlers; namespace DiscordBot { @@ -13,6 +14,10 @@ namespace DiscordBot private readonly DiscordSocketClient _client; private readonly CommandService _commands; private readonly HttpService _httpService; + private readonly SlashCommandHandler _slashCommandHandler; + private readonly ButtonHandler _buttonHandler; + private readonly SelectMenuHandler _selectMenuHandler; + private readonly ModalHandler _modalHandler; // Retrieve client and CommandService instance via ctor public CommandHandler(DiscordSocketClient client, CommandService commands, HttpService httpService) @@ -20,196 +25,18 @@ namespace DiscordBot _commands = commands; _client = client; _httpService = httpService; + _slashCommandHandler = new SlashCommandHandler(_client, _httpService); + _buttonHandler = new ButtonHandler(_httpService); + _selectMenuHandler = new SelectMenuHandler(_httpService); + _modalHandler = new ModalHandler(_httpService); } public async Task InstallCommandsAsync() { - _client.SlashCommandExecuted += SlashCommandHandler; - _client.ButtonExecuted += ButtonHandler; - _client.SelectMenuExecuted += SelectMenuHandler; - _client.ModalSubmitted += ModalHandler; - } - - private async Task SlashCommandHandler(SocketSlashCommand command) - { - switch (command.Data.Name) - { - case Constants.SlashCommands.FIRST_COMMAND: - await HandleFirstCommand(command); - break; - } - await command.RespondAsync($"You executed {command.Data.Name}"); - } - - - private async Task HandleFirstCommand(SocketSlashCommand command) - { - // We need to extract the user parameter from the command. since we only have one option and it's required, we can just use the first option. - var guildUser = (SocketGuildUser)command.Data.Options.First().Value; - - // We remove the everyone role and select the mention of each role. - var roleList = string.Join(",\n", guildUser.Roles.Where(x => !x.IsEveryone).Select(x => x.Mention)); - - var embedBuiler = new EmbedBuilder() - .WithAuthor(guildUser.ToString(), guildUser.GetAvatarUrl() ?? guildUser.GetDefaultAvatarUrl()) - .WithTitle("Roles") - .WithDescription(roleList) - .WithColor(Color.Green) - .WithCurrentTimestamp(); - - // Now, Let's respond with the embed. - await command.RespondAsync(embed: embedBuiler.Build()); - } - - public async Task ButtonHandler(SocketMessageComponent component) - { - string[] ids = component.Data.CustomId.Split('-'); - List roles = new List(); - int parsedRaidId = 0; - if(ids.Length > 1) - { - int.TryParse(ids[1],out parsedRaidId); - } - switch(ids[0]) - { - case Constants.ComponentIds.SIGN_UP_BUTTON: - if(await IsRaidSignUpAllowed(component, parsedRaidId, ids[0])) - { - roles = await _httpService.GetRoles(parsedRaidId, component.User.Id); - await component.RespondAsync("Please choose a role.", components: SignUpMessage.buildMessage(roles, parsedRaidId, ids[0], false) , ephemeral: true); - } - break; - case Constants.ComponentIds.MAYBE_BUTTON: - case Constants.ComponentIds.BACKUP_BUTTON: - case Constants.ComponentIds.FLEX_BUTTON: - if(await IsRaidSignUpAllowed(component, parsedRaidId, ids[0])) - { - roles = await _httpService.GetRoles(parsedRaidId, component.User.Id); - await component.RespondAsync("Please choose a role.", components: SignUpMessage.buildMessage(roles, parsedRaidId, ids[0], true) , ephemeral: true); - } - break; - case Constants.ComponentIds.SIGN_OFF_BUTTON: - ApiSignUp signOff = new ApiSignUp() - { - raidId = parsedRaidId, - userId = component.User.Id - }; - await _httpService.SignOff(signOff); - await Respond(component); - break; - } - } - - public async Task IsRaidSignUpAllowed(SocketInteraction component, int raidId, string pressedButtonId) - { - if(! await _httpService.DoesUserExist(component.User.Id)) - { - var mb = new ModalBuilder() - .WithTitle("Create Account") - .WithCustomId($"{Constants.ComponentIds.CREATE_ACCOUNT_MODAL}-{raidId}-{pressedButtonId}") - .AddTextInput("Name", Constants.ComponentIds.NAME_TEXT_BOX, placeholder: component.User.Username, required: true, value: component.User.Username) - .AddTextInput("Guild Wars 2 Account", Constants.ComponentIds.ACCOUNT_TEXT_BOX, placeholder: "Account.1234", required: true); - - await component.RespondWithModalAsync(mb.Build()); - return false; - } - Tuple signUpAllowed = await _httpService.IsSignUpAllowed(raidId, component.User.Id); - if(!signUpAllowed.Item1) - { - await component.RespondAsync(signUpAllowed.Item2, ephemeral: true); - return false; - } - return true; - } - - public async Task SelectMenuHandler(SocketMessageComponent component) - { - string[] ids = component.Data.CustomId.Split('-'); - List roles = new List(); - int parsedRaidId = 0; - if(ids.Length > 1) - { - int.TryParse(ids[1],out parsedRaidId); - } - switch(ids[0]) - { - case Constants.ComponentIds.SIGN_UP_DROP_DOWN: - await ManageSignUp(ids[2], parsedRaidId, component); - await Respond(component); - break; - } - } - - private async Task ManageSignUp(string buttonType, int raidId, SocketMessageComponent component) - { - if(! int.TryParse(component.Data.Values.First(), out int parsedRoleId)) return; - - ApiSignUp signUp = new ApiSignUp() - { - raidId = raidId, - userId = component.User.Id, - roleId = parsedRoleId - }; - - switch(buttonType) - { - case Constants.ComponentIds.SIGN_UP_BUTTON: - await _httpService.SignUp(signUp); - break; - case Constants.ComponentIds.MAYBE_BUTTON: - await _httpService.SignUpMaybe(signUp); - break; - case Constants.ComponentIds.BACKUP_BUTTON: - await _httpService.SignUpBackup(signUp); - break; - case Constants.ComponentIds.FLEX_BUTTON: - await _httpService.SignUpFlex(signUp); - break; - } - } - - public async Task ModalHandler(SocketModal modal) - { - List components = modal.Data.Components.ToList(); - string name = components.First(x => x.CustomId == Constants.ComponentIds.NAME_TEXT_BOX).Value; - string account = components.First(x => x.CustomId == Constants.ComponentIds.ACCOUNT_TEXT_BOX).Value; - - //create Account - ApiRaid.Role.User user = new ApiRaid.Role.User() - { - UserName = name, - AccountName = account, - UserId = modal.User.Id - }; - Tuple createAccountResult = await _httpService.CreateAccount(user); - if(!createAccountResult.Item1) - { - await modal.RespondAsync(createAccountResult.Item2, ephemeral: true); - return; - } - - //sign up - string[] ids = modal.Data.CustomId.Split('-'); - if(ids.Length > 2 && int.TryParse(ids[1], out int parsedRaidId) && await IsRaidSignUpAllowed(modal, parsedRaidId, ids[2])) - { - List roles = await _httpService.GetRoles(parsedRaidId, modal.User.Id); - await modal.RespondAsync("Please choose a role.", components: SignUpMessage.buildMessage(roles, parsedRaidId, ids[2], false) , ephemeral: true); - return; - } - await Respond(modal); - } - - //to avoid error messages because of no response... - private async Task Respond(SocketInteraction component) - { - try - { - await component.RespondAsync(); - } - catch(Discord.Net.HttpException e) - { - - } + _client.SlashCommandExecuted += _slashCommandHandler.Handler; + _client.ButtonExecuted += _buttonHandler.Handler; + _client.SelectMenuExecuted += _selectMenuHandler.Handler; + _client.ModalSubmitted += _modalHandler.Handler; } } } diff --git a/DiscordBot/CommandHandlers/ButtonHandler.cs b/DiscordBot/CommandHandlers/ButtonHandler.cs new file mode 100644 index 0000000..f6776b3 --- /dev/null +++ b/DiscordBot/CommandHandlers/ButtonHandler.cs @@ -0,0 +1,62 @@ +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using System.Reflection; +using DiscordBot.Services; +using SharedClasses.SharedModels; +using DiscordBot.Messages; + +namespace DiscordBot.CommandHandlers +{ + public class ButtonHandler + { + private readonly HttpService _httpService; + private readonly HandlerFunctions _handlerFunctions; + + public ButtonHandler(HttpService httpService) + { + _httpService = httpService; + _handlerFunctions = new HandlerFunctions(_httpService); + } + + public async Task Handler(SocketMessageComponent component) + { + string[] ids = component.Data.CustomId.Split('-'); + List roles = new List(); + int parsedRaidId = 0; + if(ids.Length > 1) + { + int.TryParse(ids[1],out parsedRaidId); + } + switch(ids[0]) + { + case Constants.ComponentIds.SIGN_UP_BUTTON: + if(await _handlerFunctions.IsRaidSignUpAllowed(component, parsedRaidId, ids[0])) + { + roles = await _httpService.GetRoles(parsedRaidId, component.User.Id); + await component.RespondAsync("Please choose a role.", components: SignUpMessage.buildMessage(roles, parsedRaidId, ids[0], false) , ephemeral: true); + } + break; + case Constants.ComponentIds.MAYBE_BUTTON: + case Constants.ComponentIds.BACKUP_BUTTON: + case Constants.ComponentIds.FLEX_BUTTON: + if(await _handlerFunctions.IsRaidSignUpAllowed(component, parsedRaidId, ids[0])) + { + roles = await _httpService.GetRoles(parsedRaidId, component.User.Id); + await component.RespondAsync("Please choose a role.", components: SignUpMessage.buildMessage(roles, parsedRaidId, ids[0], true) , ephemeral: true); + } + break; + case Constants.ComponentIds.SIGN_OFF_BUTTON: + ApiSignUp signOff = new ApiSignUp() + { + raidId = parsedRaidId, + userId = component.User.Id + }; + await _httpService.SignOff(signOff); + await _handlerFunctions.Respond(component); + break; + } + } + + } +} \ No newline at end of file diff --git a/DiscordBot/CommandHandlers/HandlerFunctions.cs b/DiscordBot/CommandHandlers/HandlerFunctions.cs new file mode 100644 index 0000000..5f70ccb --- /dev/null +++ b/DiscordBot/CommandHandlers/HandlerFunctions.cs @@ -0,0 +1,56 @@ +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using System.Reflection; +using DiscordBot.Services; +using SharedClasses.SharedModels; +using DiscordBot.Messages; + +namespace DiscordBot.CommandHandlers +{ + public class HandlerFunctions + { + private readonly HttpService _httpService; + + public HandlerFunctions(HttpService httpService) + { + _httpService = httpService; + } + + + public async Task IsRaidSignUpAllowed(SocketInteraction component, int raidId, string pressedButtonId) + { + if(! await _httpService.DoesUserExist(component.User.Id)) + { + var mb = new ModalBuilder() + .WithTitle("Create Account") + .WithCustomId($"{Constants.ComponentIds.CREATE_ACCOUNT_MODAL}-{raidId}-{pressedButtonId}") + .AddTextInput("Name", Constants.ComponentIds.NAME_TEXT_BOX, placeholder: component.User.Username, required: true, value: component.User.Username) + .AddTextInput("Guild Wars 2 Account", Constants.ComponentIds.ACCOUNT_TEXT_BOX, placeholder: "Account.1234", required: true); + + await component.RespondWithModalAsync(mb.Build()); + return false; + } + Tuple signUpAllowed = await _httpService.IsSignUpAllowed(raidId, component.User.Id); + if(!signUpAllowed.Item1) + { + await component.RespondAsync(signUpAllowed.Item2, ephemeral: true); + return false; + } + return true; + } + + //to avoid error messages because of no response... + public async Task Respond(SocketInteraction component) + { + try + { + await component.RespondAsync(); + } + catch(Discord.Net.HttpException e) + { + + } + } + } +} \ No newline at end of file diff --git a/DiscordBot/CommandHandlers/ModalHandler.cs b/DiscordBot/CommandHandlers/ModalHandler.cs new file mode 100644 index 0000000..bbb384f --- /dev/null +++ b/DiscordBot/CommandHandlers/ModalHandler.cs @@ -0,0 +1,72 @@ +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using System.Reflection; +using DiscordBot.Services; +using SharedClasses.SharedModels; +using DiscordBot.Messages; +using DiscordBot; + +namespace DiscordBot.CommandHandlers +{ + public class ModalHandler + { + private readonly HttpService _httpService; + private readonly HandlerFunctions _handlerFunctions; + + public ModalHandler(HttpService httpService) + { + _httpService = httpService; + _handlerFunctions = new HandlerFunctions(_httpService); + } + + public async Task Handler(SocketModal modal) + { + string[] ids = modal.Data.CustomId.Split('-'); + List components = modal.Data.Components.ToList(); + switch(ids[0]) + { + case Constants.ComponentIds.CREATE_ACCOUNT_MODAL: + string name = components.First(x => x.CustomId == Constants.ComponentIds.NAME_TEXT_BOX).Value; + string account = components.First(x => x.CustomId == Constants.ComponentIds.ACCOUNT_TEXT_BOX).Value; + + //create Account + ApiRaid.Role.User user = new ApiRaid.Role.User() + { + UserName = name, + AccountName = account, + UserId = modal.User.Id + }; + Tuple createAccountResult = await _httpService.CreateAccount(user); + if(!createAccountResult.Item1) + { + await modal.RespondAsync(createAccountResult.Item2, ephemeral: true); + return; + } + + //sign up + if(ids.Length > 2 && int.TryParse(ids[1], out int parsedRaidId) && await _handlerFunctions.IsRaidSignUpAllowed(modal, parsedRaidId, ids[2])) + { + List roles = await _httpService.GetRoles(parsedRaidId, modal.User.Id); + await modal.RespondAsync("Please choose a role.", components: SignUpMessage.buildMessage(roles, parsedRaidId, ids[2], false) , ephemeral: true); + return; + } + await _handlerFunctions.Respond(modal); + break; + case Constants.ComponentIds.SIGN_UP_EXTERNAL_MODAL: + string userName = components.First(x => x.CustomId == Constants.ComponentIds.NAME_TEXT_BOX).Value; + int raidId = int.Parse(ids[1]); + int roleId = int.Parse(ids[2]); + ApiSignUp signUpExternal = new ApiSignUp() + { + raidId = raidId, + userName = userName, + signedUpByUserId = modal.User.Id, + roleId = roleId + }; + await _httpService.SignUp(signUpExternal); + break; + } + } + } +} diff --git a/DiscordBot/CommandHandlers/SelectMenuHandler.cs b/DiscordBot/CommandHandlers/SelectMenuHandler.cs new file mode 100644 index 0000000..b8aa1a8 --- /dev/null +++ b/DiscordBot/CommandHandlers/SelectMenuHandler.cs @@ -0,0 +1,96 @@ +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using System.Reflection; +using DiscordBot.Services; +using SharedClasses.SharedModels; +using DiscordBot.Messages; + +namespace DiscordBot.CommandHandlers +{ + public class SelectMenuHandler + { + private readonly HttpService _httpService; + private readonly HandlerFunctions _handlerFunctions; + + public SelectMenuHandler(HttpService httpService) + { + _httpService = httpService; + _handlerFunctions = new HandlerFunctions(_httpService); + } + + public async Task Handler(SocketMessageComponent component) + { + string[] ids = component.Data.CustomId.Split('-'); + List roles = new List(); + int parsedRaidId = 0; + if(ids.Length > 1) + { + int.TryParse(ids[1],out parsedRaidId); + } + + switch(ids[0]) + { + case Constants.ComponentIds.SIGN_UP_DROP_DOWN: + ulong userId = 0; + if(ids.Length >= 4) + { + ulong.TryParse(ids[3],out userId); + } + await ManageSignUp(ids[2], parsedRaidId, component, userId); + await component.RespondAsync("successfully signed up"); + break; + case Constants.ComponentIds.SIGN_UP_EXTERNAL_DROP_DOWN: + await component.RespondWithModalAsync(CreateUserNameModal(parsedRaidId, int.Parse(component.Data.Values.First()))); + break; + } + } + + private async Task ManageSignUp(string buttonType, int raidId, SocketMessageComponent component, ulong userIdToSignUp) + { + if(! int.TryParse(component.Data.Values.First(), out int parsedRoleId)) return; + + ApiSignUp signUp = new ApiSignUp() + { + raidId = raidId, + roleId = parsedRoleId + }; + + if(userIdToSignUp == 0) + { + signUp.userId = component.User.Id; + } + else + { + signUp.userId = userIdToSignUp; + signUp.signedUpByUserId = component.User.Id; + } + + switch(buttonType) + { + case Constants.ComponentIds.SIGN_UP_BUTTON: + await _httpService.SignUp(signUp); + break; + case Constants.ComponentIds.MAYBE_BUTTON: + await _httpService.SignUpMaybe(signUp); + break; + case Constants.ComponentIds.BACKUP_BUTTON: + await _httpService.SignUpBackup(signUp); + break; + case Constants.ComponentIds.FLEX_BUTTON: + await _httpService.SignUpFlex(signUp); + break; + } + } + + private Modal CreateUserNameModal(int raidId, int roleId) + { + var mb = new ModalBuilder() + .WithTitle("Create Account") + .WithCustomId($"{Constants.ComponentIds.SIGN_UP_EXTERNAL_MODAL}-{raidId}-{roleId}") + .AddTextInput("Name", Constants.ComponentIds.NAME_TEXT_BOX, placeholder: "Name", required: true); + + return mb.Build(); + } + } +} \ No newline at end of file diff --git a/DiscordBot/CommandHandlers/SlashCommandHandler.cs b/DiscordBot/CommandHandlers/SlashCommandHandler.cs new file mode 100644 index 0000000..7abb167 --- /dev/null +++ b/DiscordBot/CommandHandlers/SlashCommandHandler.cs @@ -0,0 +1,154 @@ +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using System.Reflection; +using DiscordBot.Services; +using SharedClasses.SharedModels; +using DiscordBot.Messages; +using DiscordBot; + +namespace DiscordBot.CommandHandlers +{ + public class SlashCommandHandler + { + private readonly DiscordSocketClient _client; + private readonly HttpService _httpService; + private readonly HandlerFunctions _handlerFunctions; + + public SlashCommandHandler(DiscordSocketClient client, HttpService httpService) + { + _client = client; + _httpService = httpService; + _handlerFunctions = new HandlerFunctions(_httpService); + } + + + + public async Task Handler(SocketSlashCommand command) + { + switch (command.Data.Name) + { + case Constants.SlashCommands.RAID: + await HandleRaidCommands(command); + break; + } + } + + private async Task HandleRaidCommands(SocketSlashCommand command) + { + switch (command.Data.Options.First().Name) + { + case Constants.SlashCommands.USER: + await HandleUserCommands(command); + break; + case Constants.SlashCommands.SEND_MESSAGE_COMMAND: + int raidId = (int)(long)command.Data.Options.First().Options?.FirstOrDefault(o => o.Name == Constants.SlashCommands.OptionNames.RAID_ID).Value; + string message = (string)command.Data.Options.First().Options?.FirstOrDefault(o => o.Name == Constants.SlashCommands.OptionNames.MESSAGE).Value; + await SendMessages(message, raidId); + await command.RespondAsync($"message sent", ephemeral:true); + break; + } + } + + private async Task HandleUserCommands(SocketSlashCommand command) + { + int raidId = (int)(long)command.Data.Options.First().Options.First().Options?.FirstOrDefault(o => o.Name == Constants.SlashCommands.OptionNames.RAID_ID).Value; + string userName = string.Empty; + IUser user; + switch (command.Data.Options.First().Options.First().Name) + { + case Constants.SlashCommands.ADD_USER_COMMAND: + user = (IUser)command.Data.Options.First().Options.First().Options?.FirstOrDefault(o => o.Name == Constants.SlashCommands.OptionNames.USER).Value; + await SignUpUser(command, user, raidId); + await command.RespondAsync($"signed up {user.Username}", ephemeral:true); + break; + case Constants.SlashCommands.ADD_EXTERNAL_USER_COMMAND: + await SignUpExternalUser(command, raidId); + break; + case Constants.SlashCommands.REMOVE_USER_COMMAND: + user = (IUser)command.Data.Options.First().Options.First().Options?.FirstOrDefault(o => o.Name == Constants.SlashCommands.OptionNames.USER).Value; + ApiSignUp signOff = new ApiSignUp() + { + raidId = raidId, + userId = user.Id, + signedUpByUserId = command.User.Id + }; + await _httpService.SignOff(signOff); + await command.RespondAsync($"signed off {user.Username}", ephemeral:true); + break; + case Constants.SlashCommands.REMOVE_EXTERNAL_USER_COMMAND: + userName = (string)command.Data.Options.First().Options.First().Options?.FirstOrDefault(o => o.Name == Constants.SlashCommands.OptionNames.USER_NAME).Value; + ApiSignUp signOffExternal = new ApiSignUp() + { + raidId = raidId, + userName = userName, + signedUpByUserId = command.User.Id + }; + await _httpService.SignOff(signOffExternal); + await command.RespondAsync($"signed off {userName}", ephemeral:true); + break; + } + } + + private async Task SignUpUser(SocketSlashCommand command, IUser user, int raidId) + { + if(await _handlerFunctions.IsRaidSignUpAllowed(command, raidId, Constants.ComponentIds.SIGN_UP_BUTTON)) + { + List roles = await _httpService.GetRoles(raidId, user.Id); + if(await _httpService.DoesUserExist(user.Id)) + { + await command.RespondAsync("Please choose a role.", components: SignUpMessage.buildMessage(roles, raidId, Constants.ComponentIds.SIGN_UP_BUTTON, false, user.Id) , ephemeral: true); + } + else + { + await SignUpExternalUser(command, raidId, roles); + } + } + } + + private async Task SignUpExternalUser(SocketSlashCommand command, int raidId) + { + if(await _handlerFunctions.IsRaidSignUpAllowed(command, raidId, Constants.ComponentIds.SIGN_UP_BUTTON)) + { + List roles = await _httpService.GetRoles(raidId, uint.MaxValue); + await SignUpExternalUser(command, raidId, roles); + } + } + + private async Task SignUpExternalUser(SocketSlashCommand command, int raidId, List roles) + { + var signUpSelect = new SelectMenuBuilder() + .WithPlaceholder("Select an option") + .WithCustomId($"{Constants.ComponentIds.SIGN_UP_EXTERNAL_DROP_DOWN}-{raidId}") + .WithMinValues(1) + .WithMaxValues(1); + + foreach(ApiRole role in roles) + { + if(role.IsSignUpAllowed) + signUpSelect.AddOption(role.Name, role.roleId.ToString(), role.Description); + } + + var builder = new ComponentBuilder() + .WithSelectMenu(signUpSelect, 0); + + await command.RespondAsync("Please choose a role.", components: builder.Build() , ephemeral: true); + } + + private async Task SendMessages(string message, int raidId) + { + ApiRaid raid = await _httpService.GetRaid(raidId); + foreach(ApiRaid.Role role in raid.Roles) + { + foreach(var user in role.Users) + { + if(user.UserId > 100) + { + IUser discordUser = await _client.GetUserAsync(user.UserId); + await discordUser.SendMessageAsync($"{raid.Title}: {message}"); + } + } + } + } + } +} \ No newline at end of file diff --git a/DiscordBot/Constants.cs b/DiscordBot/Constants.cs index 4ab1886..beff081 100644 --- a/DiscordBot/Constants.cs +++ b/DiscordBot/Constants.cs @@ -12,15 +12,33 @@ public const string SIGN_OFF_BUTTON = "signOffButton"; public const string SIGN_UP_DROP_DOWN = "signUpDropDown"; + public const string SIGN_UP_EXTERNAL_DROP_DOWN = "signUpExternalDropDown"; public const string NAME_TEXT_BOX = "nameTextbox"; public const string ACCOUNT_TEXT_BOX = "accountTextBox"; public const string CREATE_ACCOUNT_MODAL = "createAccountModal"; + + public const string SIGN_UP_EXTERNAL_MODAL = "signUpExternalModal"; } public class SlashCommands { - public const string FIRST_COMMAND = "first-command"; + public const string RAID = "raid"; + public const string SEND_MESSAGE_COMMAND = "send-message"; + public const string USER = "user"; + + public const string ADD_USER_COMMAND = "add"; + public const string REMOVE_USER_COMMAND = "remove"; + public const string ADD_EXTERNAL_USER_COMMAND = "add-external"; + public const string REMOVE_EXTERNAL_USER_COMMAND = "remove-external"; + + public class OptionNames + { + public const string USER = "user"; + public const string USER_NAME = "user-name"; + public const string MESSAGE = "message"; + public const string RAID_ID = "raid-id"; + } } } } diff --git a/DiscordBot/Messages/SignUpMessage.cs b/DiscordBot/Messages/SignUpMessage.cs index 0821bae..ff55bb5 100644 --- a/DiscordBot/Messages/SignUpMessage.cs +++ b/DiscordBot/Messages/SignUpMessage.cs @@ -8,11 +8,11 @@ namespace DiscordBot.Messages { public class SignUpMessage { - public static MessageComponent buildMessage(List roles, int raidId, string buttonType, bool allRoles) + public static MessageComponent buildMessage(List roles, int raidId, string buttonType, bool allRoles, ulong userIdToSignUp = 0) { var signUpSelect = new SelectMenuBuilder() .WithPlaceholder("Select an option") - .WithCustomId($"{Constants.ComponentIds.SIGN_UP_DROP_DOWN}-{raidId}-{buttonType}") + .WithCustomId($"{Constants.ComponentIds.SIGN_UP_DROP_DOWN}-{raidId}-{buttonType}-{userIdToSignUp}") .WithMinValues(1) .WithMaxValues(1); diff --git a/DiscordBot/Program.cs b/DiscordBot/Program.cs index 67e67ca..5ebc323 100644 --- a/DiscordBot/Program.cs +++ b/DiscordBot/Program.cs @@ -77,7 +77,7 @@ namespace DiscordBot await commandHandler.InstallCommandsAsync(); _client.Log += Log; - //_client.Ready += Client_Ready; + _client.Ready += Client_Ready; // You can assign your bot token to a string, and pass that in to connect. // This is, however, insecure, particularly if you plan to have your code hosted in a public repository. @@ -113,13 +113,49 @@ namespace DiscordBot var guild = _client.GetGuild(666953424734257182); // Next, lets create our slash command builder. This is like the embed builder but for slash commands. - var guildCommand = new SlashCommandBuilder(); + var guildCommand = new SlashCommandBuilder() + .WithName(Constants.SlashCommands.RAID) + .WithDescription("Raid commands") + .AddOption(new SlashCommandOptionBuilder() + .WithName(Constants.SlashCommands.USER) + .WithDescription("Add or remove users") + .WithType(ApplicationCommandOptionType.SubCommandGroup) + .AddOption(new SlashCommandOptionBuilder() + .WithName(Constants.SlashCommands.ADD_USER_COMMAND) + .WithDescription("Sign up existing user") + .WithType(ApplicationCommandOptionType.SubCommand) + .AddOption(Constants.SlashCommands.OptionNames.RAID_ID, ApplicationCommandOptionType.Integer, "The Id of the Raid, found at the bottom of the raid message", isRequired: true) + .AddOption(Constants.SlashCommands.OptionNames.USER, ApplicationCommandOptionType.User, "The user you want to sign up", isRequired: true) + ) - // Note: Names have to be all lowercase and match the regular expression ^[\w-]{3,32}$ - guildCommand.WithName("first-command"); - - // Descriptions can have a max length of 100. - guildCommand.WithDescription("This is my first guild slash command!"); + .AddOption(new SlashCommandOptionBuilder() + .WithName(Constants.SlashCommands.REMOVE_USER_COMMAND) + .WithDescription("Sign off existing user") + .WithType(ApplicationCommandOptionType.SubCommand) + .AddOption(Constants.SlashCommands.OptionNames.RAID_ID, ApplicationCommandOptionType.Integer, "The Id of the Raid, found at the bottom of the raid message", isRequired: true) + .AddOption(Constants.SlashCommands.OptionNames.USER, ApplicationCommandOptionType.User, "The user you want to sign off", isRequired: true) + ) + .AddOption(new SlashCommandOptionBuilder() + .WithName(Constants.SlashCommands.ADD_EXTERNAL_USER_COMMAND) + .WithDescription("Sign up non existing user") + .WithType(ApplicationCommandOptionType.SubCommand) + .AddOption(Constants.SlashCommands.OptionNames.RAID_ID, ApplicationCommandOptionType.Integer, "The Id of the Raid, found at the bottom of the raid message", isRequired: true) + ) + .AddOption(new SlashCommandOptionBuilder() + .WithName(Constants.SlashCommands.REMOVE_EXTERNAL_USER_COMMAND) + .WithDescription("Sign off non existing user") + .WithType(ApplicationCommandOptionType.SubCommand) + .AddOption(Constants.SlashCommands.OptionNames.RAID_ID, ApplicationCommandOptionType.Integer, "The Id of the Raid, found at the bottom of the raid message", isRequired: true) + .AddOption(Constants.SlashCommands.OptionNames.USER_NAME, ApplicationCommandOptionType.String, "The user name you want to sign off", isRequired: true) + ) + ) + .AddOption(new SlashCommandOptionBuilder() + .WithName(Constants.SlashCommands.SEND_MESSAGE_COMMAND) + .WithDescription("Send message to all signed up users") + .WithType(ApplicationCommandOptionType.SubCommand) + .AddOption(Constants.SlashCommands.OptionNames.RAID_ID, ApplicationCommandOptionType.Integer, "The Id of the Raid, found at the bottom of the raid message", isRequired: true) + .AddOption(Constants.SlashCommands.OptionNames.MESSAGE, ApplicationCommandOptionType.String, "The message you want to send", isRequired: true) + ); try diff --git a/DiscordBot/Services/HttpService.cs b/DiscordBot/Services/HttpService.cs index b11108a..cf98c95 100644 --- a/DiscordBot/Services/HttpService.cs +++ b/DiscordBot/Services/HttpService.cs @@ -125,5 +125,21 @@ namespace DiscordBot.Services return new Tuple(true, string.Empty); } + public async Task GetRaid(int raidId) + { + var httpClient = _httpClientFactory.CreateClient(Constants.HTTP_CLIENT_NAME); + + var httpResponseMessage = await httpClient.GetAsync($"DiscordBot/GetRaid/{raidId}"); + + if (httpResponseMessage.IsSuccessStatusCode) + { + using var contentStream = + await httpResponseMessage.Content.ReadAsStreamAsync(); + + return await JsonSerializer.DeserializeAsync(contentStream, _serializerOptions); + } + return new ApiRaid(); + } + } } \ No newline at end of file diff --git a/Lieb/Controllers/DiscordBotController.cs b/Lieb/Controllers/DiscordBotController.cs index 564e2a7..0f93490 100644 --- a/Lieb/Controllers/DiscordBotController.cs +++ b/Lieb/Controllers/DiscordBotController.cs @@ -68,40 +68,75 @@ namespace Lieb.Controllers [Route("[action]")] public async Task SignUp(ApiSignUp signUp) { - int accountId = _userService.GetLiebUserGW2AccountOnly(signUp.userId).GuildWars2Accounts.FirstOrDefault(new GuildWars2Account()).GuildWars2AccountId; - await _raidService.SignUp(signUp.raidId, signUp.userId, accountId, signUp.roleId, SignUpType.SignedUp); + if(signUp.userId != 0) + { + int accountId = _userService.GetLiebUserGW2AccountOnly(signUp.userId).GuildWars2Accounts.FirstOrDefault(new GuildWars2Account()).GuildWars2AccountId; + await _raidService.SignUp(signUp.raidId, signUp.userId, accountId, signUp.roleId, SignUpType.SignedUp, signUp.signedUpByUserId); + } + else + { + await _raidService.SignUpExternalUser(signUp.raidId, signUp.userName, signUp.roleId, SignUpType.SignedUp, signUp.signedUpByUserId); + } } [HttpPost] [Route("[action]")] public async Task SignUpMaybe(ApiSignUp signUp) { - int accountId = _userService.GetLiebUserGW2AccountOnly(signUp.userId).GuildWars2Accounts.FirstOrDefault(new GuildWars2Account()).GuildWars2AccountId; - await _raidService.SignUp(signUp.raidId, signUp.userId, signUp.gw2AccountId, signUp.roleId, SignUpType.Maybe); + if(signUp.userId != 0) + { + int accountId = _userService.GetLiebUserGW2AccountOnly(signUp.userId).GuildWars2Accounts.FirstOrDefault(new GuildWars2Account()).GuildWars2AccountId; + await _raidService.SignUp(signUp.raidId, signUp.userId, accountId, signUp.roleId, SignUpType.Maybe, signUp.signedUpByUserId); + } + else + { + await _raidService.SignUpExternalUser(signUp.raidId, signUp.userName, signUp.roleId, SignUpType.Maybe, signUp.signedUpByUserId); + } } [HttpPost] [Route("[action]")] public async Task SignUpBackup(ApiSignUp signUp) { - int accountId = _userService.GetLiebUserGW2AccountOnly(signUp.userId).GuildWars2Accounts.FirstOrDefault(new GuildWars2Account()).GuildWars2AccountId; - await _raidService.SignUp(signUp.raidId, signUp.userId, signUp.gw2AccountId, signUp.roleId, SignUpType.Backup); + if(signUp.userId != 0) + { + int accountId = _userService.GetLiebUserGW2AccountOnly(signUp.userId).GuildWars2Accounts.FirstOrDefault(new GuildWars2Account()).GuildWars2AccountId; + await _raidService.SignUp(signUp.raidId, signUp.userId, accountId, signUp.roleId, SignUpType.Backup, signUp.signedUpByUserId); + } + else + { + await _raidService.SignUpExternalUser(signUp.raidId, signUp.userName, signUp.roleId, SignUpType.Backup, signUp.signedUpByUserId); + } } [HttpPost] [Route("[action]")] public async Task SignUpFlex(ApiSignUp signUp) { - int accountId = _userService.GetLiebUserGW2AccountOnly(signUp.userId).GuildWars2Accounts.FirstOrDefault(new GuildWars2Account()).GuildWars2AccountId; - await _raidService.SignUp(signUp.raidId, signUp.userId, signUp.gw2AccountId, signUp.roleId, SignUpType.Flex); + if(signUp.userId != 0) + { + int accountId = _userService.GetLiebUserGW2AccountOnly(signUp.userId).GuildWars2Accounts.FirstOrDefault(new GuildWars2Account()).GuildWars2AccountId; + await _raidService.SignUp(signUp.raidId, signUp.userId, accountId, signUp.roleId, SignUpType.Flex, signUp.signedUpByUserId); + } + else + { + await _raidService.SignUpExternalUser(signUp.raidId, signUp.userName, signUp.roleId, SignUpType.Flex, signUp.signedUpByUserId); + } } [HttpPost] [Route("[action]")] public async Task SignOff(ApiSignUp signUp) { - int accountId = _userService.GetLiebUserGW2AccountOnly(signUp.userId).GuildWars2Accounts.FirstOrDefault(new GuildWars2Account()).GuildWars2AccountId; - await _raidService.SignOff(signUp.raidId, signUp.userId); + if(signUp.userId != 0) + { + int accountId = _userService.GetLiebUserGW2AccountOnly(signUp.userId).GuildWars2Accounts.FirstOrDefault(new GuildWars2Account()).GuildWars2AccountId; + await _raidService.SignOff(signUp.raidId, signUp.userId, signUp.signedUpByUserId); + } + else + { + await _raidService.SignOffExternalUser(signUp.raidId, signUp.userName, signUp.signedUpByUserId); + } } [HttpPost] @@ -121,5 +156,14 @@ namespace Lieb.Controllers await _gw2AccountService.AddOrEditAccount(gw2Account, user.UserId); return Ok(); } + + [HttpGet] + [Route("[action]/{raidId}")] + public ActionResult GetRaid(int raidId) + { + Raid raid = _raidService.GetRaid(raidId); + + return DiscordService.ConvertRaid(raid); + } } } \ No newline at end of file diff --git a/Lieb/Data/DbInitializer.cs b/Lieb/Data/DbInitializer.cs index 32174cf..5d9b3ba 100644 --- a/Lieb/Data/DbInitializer.cs +++ b/Lieb/Data/DbInitializer.cs @@ -43,7 +43,8 @@ namespace Lieb.Data //new LiebUser{Id=0, Name="Sarah", Birthday=DateTime.Parse("1992-01-15"), GuildWars2Accounts = new List(){ linaith, sarah} }, new LiebUser{Id=194863625477816321, Name="Sarah", Birthday=DateTime.Parse("1992-01-15"), GuildWars2Accounts = new List(){ linaith, sarah} }, #if DEBUG - new LiebUser{Id=194455125769715713, Name="Lisa", GuildWars2Accounts = new List(){ hierpiepts}}, + //new LiebUser{Id=194455125769715713, Name="Lisa", GuildWars2Accounts = new List(){ hierpiepts}}, + new LiebUser{Id=1, Name="Lisa", GuildWars2Accounts = new List(){ hierpiepts}}, new LiebUser{Id=2, Name="Simon", GuildWars2Accounts = new List(){ bloodseeker}} #endif }; diff --git a/Lieb/Data/DiscordService.cs b/Lieb/Data/DiscordService.cs index e482edf..3808383 100644 --- a/Lieb/Data/DiscordService.cs +++ b/Lieb/Data/DiscordService.cs @@ -196,7 +196,7 @@ namespace Lieb.Data await context.SaveChangesAsync(); } - private ApiRaid ConvertRaid(Raid raid) + public static ApiRaid ConvertRaid(Raid raid) { ApiRaid apiRaid = new ApiRaid(){ Title = raid.Title, @@ -239,7 +239,8 @@ namespace Lieb.Data apiRole.Users.Add(new ApiRaid.Role.User(){ AccountName = signUp.GuildWars2Account.AccountName, Status = status, - UserName = signUp.LiebUser.Name + UserName = signUp.LiebUser.Name, + UserId = signUp.LiebUserId }); } } @@ -249,7 +250,7 @@ namespace Lieb.Data return apiRaid; } - private List ConvertMessages(IEnumerable messages) + public static List ConvertMessages(IEnumerable messages) { List apiMessages = new List(); foreach(DiscordRaidMessage message in messages) @@ -264,7 +265,7 @@ namespace Lieb.Data return apiMessages; } - private ApiUserReminder ConvertUserReminder(string message, Raid raid) + public static ApiUserReminder ConvertUserReminder(string message, Raid raid) { ApiUserReminder apiReminder = new ApiUserReminder() { @@ -281,7 +282,7 @@ namespace Lieb.Data return apiReminder; } - private ApiChannelReminder ConvertChannelReminder(ulong discordServerId, ulong discordChannelId, string message) + public static ApiChannelReminder ConvertChannelReminder(ulong discordServerId, ulong discordChannelId, string message) { return new ApiChannelReminder() { diff --git a/Lieb/Data/RaidService.cs b/Lieb/Data/RaidService.cs index 0b425c8..df568d3 100644 --- a/Lieb/Data/RaidService.cs +++ b/Lieb/Data/RaidService.cs @@ -102,7 +102,7 @@ namespace Lieb.Data } } - public async Task SignUp(int raidId, ulong liebUserId, int guildWars2AccountId, int plannedRoleId, SignUpType signUpType) + public async Task SignUp(int raidId, ulong liebUserId, int guildWars2AccountId, int plannedRoleId, SignUpType signUpType, ulong signedUpByUserId = 0) { if (!IsRoleSignUpAllowed(raidId, liebUserId, plannedRoleId, signUpType, true)) { @@ -127,7 +127,7 @@ namespace Lieb.Data }; context.RaidSignUps.Add(signUp); await context.SaveChangesAsync(); - await LogSignUp(signUp); + await LogSignUp(signUp, signedUpByUserId); } await _discordService.PostRaidMessage(raidId); } diff --git a/SharedClasses/SharedModels/ApiSignUp.cs b/SharedClasses/SharedModels/ApiSignUp.cs index cad4f53..e9078cf 100644 --- a/SharedClasses/SharedModels/ApiSignUp.cs +++ b/SharedClasses/SharedModels/ApiSignUp.cs @@ -7,5 +7,7 @@ namespace SharedClasses.SharedModels public ulong userId {get; set;} public int gw2AccountId {get; set;} public int roleId {get; set;} + public ulong signedUpByUserId {get; set;} + public string userName {get; set;} = string.Empty; } } \ No newline at end of file