From 69337e69aec7224a364c89d8b2e9d943b81df3b8 Mon Sep 17 00:00:00 2001 From: Sarah Faey Date: Mon, 7 Nov 2022 00:16:49 +0100 Subject: [PATCH] signing up through the discord bot works now --- DiscordBot/CommandHandler.cs | 109 +++++++++++++++++++---- DiscordBot/Constants.cs | 12 ++- DiscordBot/Messages/RaidMessage.cs | 21 ++--- DiscordBot/Messages/SignUpMessage.cs | 68 ++++++++++++++ DiscordBot/Program.cs | 21 +++++ DiscordBot/Services/HttpService.cs | 77 ++++++++++++++++ Lieb/Controllers/DiscordBotController.cs | 47 ++++++++-- Lieb/Data/DbInitializer.cs | 2 +- Lieb/Data/DiscordService.cs | 6 +- SharedClasses/SharedModels/ApiRaid.cs | 2 + SharedClasses/SharedModels/ApiRole.cs | 4 +- 11 files changed, 327 insertions(+), 42 deletions(-) create mode 100644 DiscordBot/Messages/SignUpMessage.cs create mode 100644 DiscordBot/Services/HttpService.cs diff --git a/DiscordBot/CommandHandler.cs b/DiscordBot/CommandHandler.cs index cf734f9..f93cd8b 100644 --- a/DiscordBot/CommandHandler.cs +++ b/DiscordBot/CommandHandler.cs @@ -2,6 +2,9 @@ using Discord.Commands; using Discord.WebSocket; using System.Reflection; +using DiscordBot.Services; +using SharedClasses.SharedModels; +using DiscordBot.Messages; namespace DiscordBot { @@ -9,18 +12,21 @@ namespace DiscordBot { private readonly DiscordSocketClient _client; private readonly CommandService _commands; + private readonly HttpService _httpService; // Retrieve client and CommandService instance via ctor - public CommandHandler(DiscordSocketClient client, CommandService commands) + public CommandHandler(DiscordSocketClient client, CommandService commands, HttpService httpService) { _commands = commands; _client = client; + _httpService = httpService; } public async Task InstallCommandsAsync() { _client.SlashCommandExecuted += SlashCommandHandler; - _client.ButtonExecuted += MyButtonHandler; + _client.ButtonExecuted += ButtonHandler; + _client.SelectMenuExecuted += SelectMenuHandler; } private async Task SlashCommandHandler(SocketSlashCommand command) @@ -54,28 +60,95 @@ namespace DiscordBot await command.RespondAsync(embed: embedBuiler.Build()); } - public async Task MyButtonHandler(SocketMessageComponent component) + 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: - //await component.RespondAsync($"{component.User.Mention} has clicked the SignUp button!"); - - var mb = new ModalBuilder() - .WithTitle("Fav Food") - .WithCustomId("food_menu") - .AddTextInput("What??", "food_name", placeholder:"Pizza") - .AddTextInput("Why??", "food_reason", TextInputStyle.Paragraph, - "Kus it's so tasty"); - - //await component.RespondWithModalAsync(mb.Build()); - - await component.RespondAsync("hi", ephemeral: true); + case Constants.ComponentIds.SIGN_UP_BUTTON: + 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.SIGN_OFF: - //await component.RespondAsync($"{component.User.Mention} has clicked the SignOff button!"); + case Constants.ComponentIds.MAYBE_BUTTON: + case Constants.ComponentIds.BACKUP_BUTTON: + case Constants.ComponentIds.FLEX_BUTTON: + 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 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; + } + } + + //to avoid error messages because of no response... + private async Task Respond(SocketMessageComponent component) + { + try + { + await component.RespondAsync(); + } + catch(Discord.Net.HttpException e) + { + } } } diff --git a/DiscordBot/Constants.cs b/DiscordBot/Constants.cs index b4160fe..679177f 100644 --- a/DiscordBot/Constants.cs +++ b/DiscordBot/Constants.cs @@ -2,10 +2,16 @@ { public class Constants { + public const string HTTP_CLIENT_NAME = "LiebWebsite"; public class ComponentIds - { - public const string SIGN_UP = "su"; - public const string SIGN_OFF = "so"; + { + public const string SIGN_UP_BUTTON = "signUpButton"; + public const string MAYBE_BUTTON = "maybeButton"; + public const string BACKUP_BUTTON = "backupButton"; + public const string FLEX_BUTTON = "flexButton"; + public const string SIGN_OFF_BUTTON = "signOffButton"; + + public const string SIGN_UP_DROP_DOWN = "signUpDropDown"; } public class SlashCommands diff --git a/DiscordBot/Messages/RaidMessage.cs b/DiscordBot/Messages/RaidMessage.cs index 93b2042..966061e 100644 --- a/DiscordBot/Messages/RaidMessage.cs +++ b/DiscordBot/Messages/RaidMessage.cs @@ -33,18 +33,12 @@ namespace DiscordBot.Messages public async Task PostRaidMessage(ApiRaid raid) { - var menuBuilder = new SelectMenuBuilder() - .WithPlaceholder("Select an option") - .WithCustomId("menu-1") - .WithMinValues(1) - .WithMaxValues(1) - .AddOption("Option A", "opt-a", "Option B is lying!") - .AddOption("Option B", "opt-b", "Option A is telling the truth!"); - var builder = new ComponentBuilder() - .WithButton("SignUp", $"{Constants.ComponentIds.SIGN_UP}-{raid.RaidId.ToString()}", ButtonStyle.Secondary) - .WithButton("SignOff", $"{Constants.ComponentIds.SIGN_OFF}-{raid.RaidId.ToString()}", ButtonStyle.Secondary); - //.WithSelectMenu(menuBuilder); + .WithButton("SignUp", $"{Constants.ComponentIds.SIGN_UP_BUTTON}-{raid.RaidId.ToString()}", ButtonStyle.Success) + .WithButton("Maybe", $"{Constants.ComponentIds.MAYBE_BUTTON}-{raid.RaidId.ToString()}", ButtonStyle.Secondary) + .WithButton("Backup", $"{Constants.ComponentIds.BACKUP_BUTTON}-{raid.RaidId.ToString()}", ButtonStyle.Secondary) + .WithButton("Flex", $"{Constants.ComponentIds.FLEX_BUTTON}-{raid.RaidId.ToString()}", ButtonStyle.Secondary) + .WithButton("SignOff", $"{Constants.ComponentIds.SIGN_OFF_BUTTON}-{raid.RaidId.ToString()}", ButtonStyle.Danger); MessageComponent components = builder.Build(); Embed raidMessage = CreateRaidMessage(raid); @@ -62,7 +56,8 @@ namespace DiscordBot.Messages Embed = raidMessage, Components = components }; - await messageChannel.ModifyMessageAsync(message.MessageId, new Action(x => x = properties)); + IUserMessage discordMessage = (IUserMessage)await messageChannel.GetMessageAsync(message.MessageId); + await discordMessage.ModifyAsync(msg => msg.Embed = raidMessage); } else { @@ -105,7 +100,7 @@ namespace DiscordBot.Messages Dictionary fieldList = new Dictionary(); embed.AddField("Signed up", $"({raid.Roles.Sum(r => r.Users.Count)}/{raid.Roles.Sum(r => r.Spots)}):"); - foreach (ApiRaid.Role role in raid.Roles) + foreach (ApiRaid.Role role in raid.Roles.OrderBy(x => x.RoleId)) { //print signed up users string signedUpUsers = PrintUsers(role); diff --git a/DiscordBot/Messages/SignUpMessage.cs b/DiscordBot/Messages/SignUpMessage.cs new file mode 100644 index 0000000..71c951a --- /dev/null +++ b/DiscordBot/Messages/SignUpMessage.cs @@ -0,0 +1,68 @@ +using Discord; +using Discord.WebSocket; +using System; +using System.ComponentModel.DataAnnotations; +using SharedClasses.SharedModels; + +namespace DiscordBot.Messages +{ + public class SignUpMessage + { + public static MessageComponent buildMessage(List roles, int raidId, string buttonType, bool allRoles) + { + var signUpSelect = new SelectMenuBuilder() + .WithPlaceholder("Select an option") + .WithCustomId($"{Constants.ComponentIds.SIGN_UP_DROP_DOWN}-{raidId}-{buttonType}") + .WithMinValues(1) + .WithMaxValues(1); + + foreach(ApiRole role in roles) + { + if(allRoles || role.IsSignUpAllowed) + signUpSelect.AddOption(role.Name, role.roleId.ToString(), role.Description); + } + + var builder = new ComponentBuilder() + .WithSelectMenu(signUpSelect, 0); + + return builder.Build(); + } + /* + public static MessageComponent buildMessage(List roles, int raidId) + { + var signUpSelect = new SelectMenuBuilder() + .WithPlaceholder("Select an option") + .WithCustomId(Constants.ComponentIds.SIGN_UP_DROP_DOWN) + .WithMinValues(1) + .WithMaxValues(1); + + foreach(ApiRole role in roles) + { + if(role.IsSignUpAllowed) + signUpSelect.AddOption(role.Name, role.roleId.ToString(), role.Description); + } + + var flexSelect = new SelectMenuBuilder() + .WithPlaceholder("Select an option") + .WithCustomId(Constants.ComponentIds.FLEX_DROP_DOWN) + .WithMinValues(1) + .WithMaxValues(1); + + foreach(ApiRole role in roles) + { + flexSelect.AddOption(role.Name, role.roleId.ToString(), role.Description); + } + + var builder = new ComponentBuilder() + .WithSelectMenu(signUpSelect, 0) + .WithButton("SignUp", $"{Constants.ComponentIds.SIGN_UP_BUTTON}-{raidId.ToString()}", ButtonStyle.Success, row: 1) + .WithSelectMenu(flexSelect, 2) + .WithButton("Maybe", $"{Constants.ComponentIds.MAYBE_BUTTON}-{raidId.ToString()}", ButtonStyle.Success, row: 3) + .WithButton("Backup", $"{Constants.ComponentIds.BACKUP_BUTTON}-{raidId.ToString()}", ButtonStyle.Success, row: 3) + .WithButton("Flex", $"{Constants.ComponentIds.FLEX_BUTTON}-{raidId.ToString()}", ButtonStyle.Success, row: 3); + + return builder.Build(); + }*/ + + } +} \ No newline at end of file diff --git a/DiscordBot/Program.cs b/DiscordBot/Program.cs index 563f4a9..67e67ca 100644 --- a/DiscordBot/Program.cs +++ b/DiscordBot/Program.cs @@ -2,9 +2,11 @@ using Discord; using Discord.Commands; using Discord.Net; using Discord.WebSocket; +using Microsoft.Net.Http.Headers; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using System.Reflection; +using DiscordBot.Services; namespace DiscordBot { @@ -28,6 +30,7 @@ namespace DiscordBot builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddControllers(); @@ -35,6 +38,24 @@ namespace DiscordBot builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); + builder.Services.AddHttpClient(Constants.HTTP_CLIENT_NAME , httpClient => + { + httpClient.BaseAddress = new Uri("https://localhost:7216/"); + + httpClient.DefaultRequestHeaders.Add( + HeaderNames.Accept, "application/vnd.github.v3+json"); + httpClient.DefaultRequestHeaders.Add( + HeaderNames.UserAgent, "HttpRequestsSample"); + }).ConfigurePrimaryHttpMessageHandler(() => { + var handler = new HttpClientHandler(); + if (builder.Environment.IsDevelopment()) + { + handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; }; + } + return handler; + }); + + var app = builder.Build(); // Configure the HTTP request pipeline. diff --git a/DiscordBot/Services/HttpService.cs b/DiscordBot/Services/HttpService.cs new file mode 100644 index 0000000..a9effe3 --- /dev/null +++ b/DiscordBot/Services/HttpService.cs @@ -0,0 +1,77 @@ +using SharedClasses.SharedModels; +using System.Net.Http; +using static System.Net.Mime.MediaTypeNames; +using System.Text.Json; +using System.Text; + +namespace DiscordBot.Services +{ + public class HttpService + { + private readonly IHttpClientFactory _httpClientFactory; + private readonly JsonSerializerOptions _serializerOptions; + + public HttpService(IHttpClientFactory httpClientFactory) + { + _httpClientFactory = httpClientFactory; + _serializerOptions = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + } + + public async Task> GetRoles(int raidId, ulong userId) + { + var httpClient = _httpClientFactory.CreateClient(Constants.HTTP_CLIENT_NAME); + + var httpResponseMessage = await httpClient.GetAsync($"DiscordBot/GetRoles/{raidId}/{userId}"); + + if (httpResponseMessage.IsSuccessStatusCode) + { + using var contentStream = + await httpResponseMessage.Content.ReadAsStreamAsync(); + + return await JsonSerializer.DeserializeAsync>(contentStream, _serializerOptions); + } + return new List(); + } + + public async Task SignUp(ApiSignUp signUp) + { + await SendSignUp(signUp, "DiscordBot/SignUp"); + } + + public async Task SignUpMaybe(ApiSignUp signUp) + { + await SendSignUp(signUp, "DiscordBot/SignUpMaybe"); + } + + public async Task SignUpBackup(ApiSignUp signUp) + { + await SendSignUp(signUp, "DiscordBot/SignUpBackup"); + } + + public async Task SignUpFlex(ApiSignUp signUp) + { + await SendSignUp(signUp, "DiscordBot/SignUpFlex"); + } + + public async Task SignOff(ApiSignUp signUp) + { + await SendSignUp(signUp, "DiscordBot/SignOff"); + } + + private async Task SendSignUp(ApiSignUp signUp, string requestUri) + { + var httpClient = _httpClientFactory.CreateClient(Constants.HTTP_CLIENT_NAME); + + var raidItemJson = new StringContent( + JsonSerializer.Serialize(signUp), + Encoding.UTF8, + Application.Json); + + var httpResponseMessage = await httpClient.PostAsync(requestUri, raidItemJson); + } + + } +} \ No newline at end of file diff --git a/Lieb/Controllers/DiscordBotController.cs b/Lieb/Controllers/DiscordBotController.cs index f4baac4..1124cfc 100644 --- a/Lieb/Controllers/DiscordBotController.cs +++ b/Lieb/Controllers/DiscordBotController.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Mvc; using Lieb.Data; using Lieb.Models.GuildWars2.Raid; +using Lieb.Models.GuildWars2; using SharedClasses.SharedModels; namespace Lieb.Controllers @@ -13,20 +14,22 @@ namespace Lieb.Controllers public class DiscordBotController : ControllerBase { RaidService _raidService; + UserService _userService; - public DiscordBotController(RaidService raidService) + public DiscordBotController(RaidService raidService, UserService userService) { _raidService = raidService; + _userService = userService; } [HttpGet] [Route("[action]/{raidId}/{userId}")] - public List GetRoles(int raidId, ulong userId) + public ActionResult> GetRoles(int raidId, ulong userId) { Raid raid = _raidService.GetRaid(raidId); if(!_raidService.IsRaidSignUpAllowed(userId, raidId, out string errorMessage)) { - //TODO: send error message + return Problem(errorMessage); } List apiRoles = new List(); @@ -35,7 +38,8 @@ namespace Lieb.Controllers apiRoles.Add(new ApiRole(){ Name = role.Name, Description = role.Description, - IsSignUpAllowed = _raidService.IsRoleSignUpAllowed(userId, role.RaidRoleId, SignUpType.SignedUp) + IsSignUpAllowed = _raidService.IsRoleSignUpAllowed(userId, role.RaidRoleId, SignUpType.SignedUp), + roleId = role.RaidRoleId }); } return apiRoles; @@ -45,7 +49,40 @@ namespace Lieb.Controllers [Route("[action]")] public async Task SignUp(ApiSignUp signUp) { - _raidService.SignUp(signUp.raidId, signUp.userId, signUp.gw2AccountId, signUp.roleId, ) + int accountId = _userService.GetLiebUserGW2AccountOnly(signUp.userId).GuildWars2Accounts.FirstOrDefault(new GuildWars2Account()).GuildWars2AccountId; + await _raidService.SignUp(signUp.raidId, signUp.userId, accountId, signUp.roleId, SignUpType.SignedUp); + } + + [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); + } + + [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); + } + + [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); + } + + [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); } } } \ No newline at end of file diff --git a/Lieb/Data/DbInitializer.cs b/Lieb/Data/DbInitializer.cs index ec0c645..fb5037c 100644 --- a/Lieb/Data/DbInitializer.cs +++ b/Lieb/Data/DbInitializer.cs @@ -43,7 +43,7 @@ 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=1, Name="Lisa", GuildWars2Accounts = new List(){ hierpiepts}}, + new LiebUser{Id=194455125769715713, 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 5380c43..536a660 100644 --- a/Lieb/Data/DiscordService.cs +++ b/Lieb/Data/DiscordService.cs @@ -171,7 +171,10 @@ namespace Lieb.Data } using var context = _contextFactory.CreateDbContext(); - context.Update(raid.DiscordRaidMessages); + foreach(DiscordRaidMessage message in raid.DiscordRaidMessages) + { + context.Update(message); + } await context.SaveChangesAsync(); } @@ -192,6 +195,7 @@ namespace Lieb.Data foreach(RaidRole role in raid.Roles) { ApiRaid.Role apiRole = new ApiRaid.Role(){ + RoleId = role.RaidRoleId, Description = role.Description, Name = role.Name, Spots = role.Spots diff --git a/SharedClasses/SharedModels/ApiRaid.cs b/SharedClasses/SharedModels/ApiRaid.cs index 57102fd..9a53e38 100644 --- a/SharedClasses/SharedModels/ApiRaid.cs +++ b/SharedClasses/SharedModels/ApiRaid.cs @@ -36,6 +36,8 @@ namespace SharedClasses.SharedModels public class Role { + public int RoleId { get; set; } + public string Name { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; diff --git a/SharedClasses/SharedModels/ApiRole.cs b/SharedClasses/SharedModels/ApiRole.cs index 3db3395..6382af9 100644 --- a/SharedClasses/SharedModels/ApiRole.cs +++ b/SharedClasses/SharedModels/ApiRole.cs @@ -7,6 +7,8 @@ namespace SharedClasses.SharedModels public string Description { get; set; } = String.Empty; - public bool IsSignUpAllowed{ get; set; } = false; + public bool IsSignUpAllowed { get; set; } = false; + + public int roleId {get; set;} } } \ No newline at end of file