diff --git a/DiscordBot/CommandHandler.cs b/DiscordBot/CommandHandler.cs index f93cd8b..f639b35 100644 --- a/DiscordBot/CommandHandler.cs +++ b/DiscordBot/CommandHandler.cs @@ -27,6 +27,7 @@ namespace DiscordBot _client.SlashCommandExecuted += SlashCommandHandler; _client.ButtonExecuted += ButtonHandler; _client.SelectMenuExecuted += SelectMenuHandler; + _client.ModalSubmitted += ModalHandler; } private async Task SlashCommandHandler(SocketSlashCommand command) @@ -72,14 +73,20 @@ namespace DiscordBot switch(ids[0]) { 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); + 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: - 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); + 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() @@ -93,6 +100,28 @@ namespace DiscordBot } } + 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('-'); @@ -139,8 +168,39 @@ namespace DiscordBot } } + 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(SocketMessageComponent component) + private async Task Respond(SocketInteraction component) { try { diff --git a/DiscordBot/Constants.cs b/DiscordBot/Constants.cs index 679177f..4ab1886 100644 --- a/DiscordBot/Constants.cs +++ b/DiscordBot/Constants.cs @@ -12,6 +12,10 @@ public const string SIGN_OFF_BUTTON = "signOffButton"; public const string SIGN_UP_DROP_DOWN = "signUpDropDown"; + + public const string NAME_TEXT_BOX = "nameTextbox"; + public const string ACCOUNT_TEXT_BOX = "accountTextBox"; + public const string CREATE_ACCOUNT_MODAL = "createAccountModal"; } public class SlashCommands diff --git a/DiscordBot/DiscordBot.csproj b/DiscordBot/DiscordBot.csproj index f13098a..c32b481 100644 --- a/DiscordBot/DiscordBot.csproj +++ b/DiscordBot/DiscordBot.csproj @@ -7,7 +7,7 @@ - + diff --git a/DiscordBot/Messages/RaidMessage.cs b/DiscordBot/Messages/RaidMessage.cs index 966061e..39bd072 100644 --- a/DiscordBot/Messages/RaidMessage.cs +++ b/DiscordBot/Messages/RaidMessage.cs @@ -88,8 +88,10 @@ namespace DiscordBot.Messages private void AddMessageDetails(ApiRaid raid, ref EmbedBuilder embed) { - embed.AddField("Date", $"{raid.StartTimeUTC.ToLocalTime().DateTime.ToLongDateString()}"); - embed.AddField("Time", $"from: {raid.StartTimeUTC.ToLocalTime().DateTime.ToShortTimeString()} to: {raid.EndTimeUTC.ToLocalTime().DateTime.ToShortTimeString()}"); + //embed.AddField("Date", $"{raid.StartTimeUTC.ToLocalTime().DateTime.ToLongDateString()}"); + //embed.AddField("Time", $"from: {raid.StartTimeUTC.ToLocalTime().DateTime.ToShortTimeString()} to: {raid.EndTimeUTC.ToLocalTime().DateTime.ToShortTimeString()}"); + embed.AddField("Start ", $""); + embed.AddField("End", $""); embed.AddField("Organisator", raid.Organizer, true); embed.AddField("Guild", raid.Guild, true); embed.AddField("Voice chat", raid.VoiceChat, true); diff --git a/DiscordBot/Messages/SignUpMessage.cs b/DiscordBot/Messages/SignUpMessage.cs index 71c951a..0821bae 100644 --- a/DiscordBot/Messages/SignUpMessage.cs +++ b/DiscordBot/Messages/SignUpMessage.cs @@ -27,42 +27,5 @@ namespace DiscordBot.Messages 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/Services/HttpService.cs b/DiscordBot/Services/HttpService.cs index a9effe3..b11108a 100644 --- a/DiscordBot/Services/HttpService.cs +++ b/DiscordBot/Services/HttpService.cs @@ -1,5 +1,6 @@ using SharedClasses.SharedModels; using System.Net.Http; +using Microsoft.AspNetCore.Mvc; using static System.Net.Mime.MediaTypeNames; using System.Text.Json; using System.Text; @@ -20,6 +21,37 @@ namespace DiscordBot.Services }; } + public async Task DoesUserExist(ulong userId) + { + var httpClient = _httpClientFactory.CreateClient(Constants.HTTP_CLIENT_NAME); + + var httpResponseMessage = await httpClient.GetAsync($"DiscordBot/DoesUserExist/{userId}"); + + if (httpResponseMessage.IsSuccessStatusCode) + { + using var contentStream = + await httpResponseMessage.Content.ReadAsStreamAsync(); + + return await JsonSerializer.DeserializeAsync(contentStream, _serializerOptions); + } + return false; + } + + public async Task> IsSignUpAllowed(int raidId, ulong userId) + { + var httpClient = _httpClientFactory.CreateClient(Constants.HTTP_CLIENT_NAME); + + var httpResponseMessage = await httpClient.GetAsync($"DiscordBot/IsSignUpAllowed/{raidId}/{userId}"); + + if (!httpResponseMessage.IsSuccessStatusCode) + { + ProblemDetails problemDetails = await httpResponseMessage.Content.ReadFromJsonAsync(_serializerOptions) ?? new ProblemDetails(); + string errorMessage = string.IsNullOrEmpty(problemDetails.Detail) ? string.Empty : problemDetails.Detail; + return new Tuple(false, errorMessage); + } + return new Tuple(true, string.Empty); + } + public async Task> GetRoles(int raidId, ulong userId) { var httpClient = _httpClientFactory.CreateClient(Constants.HTTP_CLIENT_NAME); @@ -73,5 +105,25 @@ namespace DiscordBot.Services var httpResponseMessage = await httpClient.PostAsync(requestUri, raidItemJson); } + public async Task> CreateAccount(ApiRaid.Role.User user) + { + var httpClient = _httpClientFactory.CreateClient(Constants.HTTP_CLIENT_NAME); + + var raidItemJson = new StringContent( + JsonSerializer.Serialize(user), + Encoding.UTF8, + Application.Json); + + var httpResponseMessage = await httpClient.PostAsync("DiscordBot/CreateAccount", raidItemJson); + + if (!httpResponseMessage.IsSuccessStatusCode) + { + ProblemDetails problemDetails = await httpResponseMessage.Content.ReadFromJsonAsync(_serializerOptions) ?? new ProblemDetails(); + string errorMessage = string.IsNullOrEmpty(problemDetails.Detail) ? string.Empty : problemDetails.Detail; + return new Tuple(false, errorMessage); + } + return new Tuple(true, string.Empty); + } + } } \ No newline at end of file diff --git a/Lieb/Controllers/DiscordBotController.cs b/Lieb/Controllers/DiscordBotController.cs index 1124cfc..56baa16 100644 --- a/Lieb/Controllers/DiscordBotController.cs +++ b/Lieb/Controllers/DiscordBotController.cs @@ -2,9 +2,11 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Mvc; +using System.Text.RegularExpressions; using Lieb.Data; using Lieb.Models.GuildWars2.Raid; using Lieb.Models.GuildWars2; +using Lieb.Models; using SharedClasses.SharedModels; namespace Lieb.Controllers @@ -15,11 +17,33 @@ namespace Lieb.Controllers { RaidService _raidService; UserService _userService; + GuildWars2AccountService _gw2AccountService; - public DiscordBotController(RaidService raidService, UserService userService) + public DiscordBotController(RaidService raidService, UserService userService, GuildWars2AccountService gw2AccountService) { _raidService = raidService; _userService = userService; + _gw2AccountService = gw2AccountService; + } + + [HttpGet] + [Route("[action]/{userId}")] + public ActionResult DoesUserExist(ulong userId) + { + LiebUser user = _userService.GetLiebUserGW2AccountOnly(userId); + return user != null && user.GuildWars2Accounts.Count() > 0; + } + + [HttpGet] + [Route("[action]/{raidId}/{userId}")] + public ActionResult IsSignUpAllowed(int raidId, ulong userId) + { + Raid raid = _raidService.GetRaid(raidId); + if(!_raidService.IsRaidSignUpAllowed(userId, raidId, out string errorMessage)) + { + return Problem(errorMessage); + } + return Ok(); } [HttpGet] @@ -27,10 +51,6 @@ namespace Lieb.Controllers public ActionResult> GetRoles(int raidId, ulong userId) { Raid raid = _raidService.GetRaid(raidId); - if(!_raidService.IsRaidSignUpAllowed(userId, raidId, out string errorMessage)) - { - return Problem(errorMessage); - } List apiRoles = new List(); foreach(RaidRole role in raid.Roles) @@ -84,5 +104,23 @@ namespace Lieb.Controllers int accountId = _userService.GetLiebUserGW2AccountOnly(signUp.userId).GuildWars2Accounts.FirstOrDefault(new GuildWars2Account()).GuildWars2AccountId; await _raidService.SignOff(signUp.raidId, signUp.userId); } + + [HttpPost] + [Route("[action]")] + public async Task CreateAccount(ApiRaid.Role.User user) + { + if(!Regex.IsMatch(user.AccountName, Constants.GW2_ACCOUNT_REGEX)) + { + return Problem("Invalid Account Name"); + } + + GuildWars2Account gw2Account = new GuildWars2Account() + { + AccountName = user.AccountName + }; + await _userService.CreateUser(user.UserId, user.UserName); + await _gw2AccountService.AddOrEditAccount(gw2Account, user.UserId); + return Ok(); + } } } \ No newline at end of file diff --git a/Lieb/Data/Constants.cs b/Lieb/Data/Constants.cs index c727318..047247e 100644 --- a/Lieb/Data/Constants.cs +++ b/Lieb/Data/Constants.cs @@ -4,6 +4,7 @@ { public const string HttpClientName = "Discord"; public const string ClaimType = "Role"; + public const string GW2_ACCOUNT_REGEX = "^[a-zA-z ]{3,27}\\.[0-9]{4}$"; public static readonly int RaidEditPowerLevel = Roles.Moderator.PowerLevel; public static class Roles diff --git a/Lieb/Data/DbInitializer.cs b/Lieb/Data/DbInitializer.cs index fb5037c..018d9a8 100644 --- a/Lieb/Data/DbInitializer.cs +++ b/Lieb/Data/DbInitializer.cs @@ -40,8 +40,8 @@ namespace Lieb.Data GuildWars2Account bloodseeker = new GuildWars2Account() { AccountName = "Bloodseeker.2043" }; var users = new LiebUser[] { - //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} }, + 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=2, Name="Simon", GuildWars2Accounts = new List(){ bloodseeker}} @@ -195,6 +195,16 @@ namespace Lieb.Data context.Equipped.AddRange(equippedBuilds); context.SaveChanges(); + var discordMessage = new DiscordRaidMessage() + { + DiscordChannelId = 666954070388637697, + DiscordGuildId = 666953424734257182, + DiscordMessageId = 1040355092630614087, + RaidId = raid.RaidId + }; + context.DiscordRaidMessages.Add(discordMessage); + context.SaveChanges(); + #endif } } diff --git a/Lieb/Data/DiscordService.cs b/Lieb/Data/DiscordService.cs index 536a660..84f9b11 100644 --- a/Lieb/Data/DiscordService.cs +++ b/Lieb/Data/DiscordService.cs @@ -204,11 +204,15 @@ namespace Lieb.Data foreach(RaidSignUp signUp in raid.SignUps.Where(x => x.RaidRoleId == role.RaidRoleId)) { - apiRole.Users.Add(new ApiRaid.Role.User(){ - AccountName = signUp.GuildWars2Account.AccountName, - Status = signUp.SignUpType.ToString(), - UserName = signUp.LiebUser.Name - }); + if(signUp.SignUpType != SignUpType.SignedOff) + { + string status = signUp.SignUpType != SignUpType.SignedUp ? signUp.SignUpType.ToString() : string.Empty; + apiRole.Users.Add(new ApiRaid.Role.User(){ + AccountName = signUp.GuildWars2Account.AccountName, + Status = status, + UserName = signUp.LiebUser.Name + }); + } } apiRaid.Roles.Add(apiRole); } diff --git a/Lieb/Data/RaidService.cs b/Lieb/Data/RaidService.cs index 0e995d2..b558991 100644 --- a/Lieb/Data/RaidService.cs +++ b/Lieb/Data/RaidService.cs @@ -333,6 +333,7 @@ namespace Lieb.Data if(raid.EndTimeUTC < DateTimeOffset.UtcNow) { + errorMessage = $"The raid already ended."; return false; } diff --git a/Lieb/Discord/CommandHandler.cs b/Lieb/Discord/CommandHandler.cs deleted file mode 100644 index 8f3c2a3..0000000 --- a/Lieb/Discord/CommandHandler.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Discord.Commands; -using Discord.WebSocket; -using System.Reflection; - -namespace Lieb.Discord -{ - public class CommandHandler - { - private readonly DiscordSocketClient _client; - private readonly CommandService _commands; - - // Retrieve client and CommandService instance via ctor - public CommandHandler(DiscordSocketClient client, CommandService commands) - { - _commands = commands; - _client = client; - } - - public async Task InstallCommandsAsync() - { - // Hook the MessageReceived event into our command handler - _client.MessageReceived += HandleCommandAsync; - - // Here we discover all of the command modules in the entry - // assembly and load them. Starting from Discord.NET 2.0, a - // service provider is required to be passed into the - // module registration method to inject the - // required dependencies. - // - // If you do not use Dependency Injection, pass null. - // See Dependency Injection guide for more information. - await _commands.AddModulesAsync(assembly: Assembly.GetEntryAssembly(), - services: null); - } - - private async Task HandleCommandAsync(SocketMessage messageParam) - { - // Don't process the command if it was a system message - var message = messageParam as SocketUserMessage; - if (message == null) return; - - // Create a number to track where the prefix ends and the command begins - int argPos = 0; - - // Determine if the message is a command based on the prefix and make sure no bots trigger commands - if (!(message.HasCharPrefix('!', ref argPos) || - message.HasMentionPrefix(_client.CurrentUser, ref argPos)) || - message.Author.IsBot) - return; - - // Create a WebSocket-based command context based on the message - var context = new SocketCommandContext(_client, message); - - // Execute the command with the command context we just - // created, along with the service provider for precondition checks. - await _commands.ExecuteAsync( - context: context, - argPos: argPos, - services: null); - } - } -} diff --git a/Lieb/Discord/Messages/RaidMessage.cs b/Lieb/Discord/Messages/RaidMessage.cs deleted file mode 100644 index b209154..0000000 --- a/Lieb/Discord/Messages/RaidMessage.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Lieb.Models.GuildWars2.Raid; - -namespace Lieb.Discord.Messages -{ - public class RaidMessage - { - private Raid _raid; - - public RaidMessage(Raid raid) - { - _raid = raid; - } - - public void PostRaidMessage() - { - - } - - public void UpdateRaidMessage() - { - - } - - } -} diff --git a/Lieb/Lieb.csproj b/Lieb/Lieb.csproj index 311cbe3..4239793 100644 --- a/Lieb/Lieb.csproj +++ b/Lieb/Lieb.csproj @@ -7,7 +7,6 @@ - diff --git a/Lieb/Models/GuildWars2/GuildWars2Account.cs b/Lieb/Models/GuildWars2/GuildWars2Account.cs index 2e75667..5be9ebc 100644 --- a/Lieb/Models/GuildWars2/GuildWars2Account.cs +++ b/Lieb/Models/GuildWars2/GuildWars2Account.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Lieb.Data; namespace Lieb.Models.GuildWars2 { @@ -9,7 +10,7 @@ namespace Lieb.Models.GuildWars2 public string ApiKey { get; set; } = string.Empty; [Required] - [RegularExpression("^[a-zA-z ]{3,27}\\.[0-9]{4}$", ErrorMessage = "Invalid Account Name")] + [RegularExpression(Constants.GW2_ACCOUNT_REGEX, ErrorMessage = "Invalid Account Name")] public string AccountName { get; set; } = string.Empty; public ICollection EquippedBuilds { get; set; } = new List(); diff --git a/Lieb/Pages/Raids/RaidEdit.razor b/Lieb/Pages/Raids/RaidEdit.razor index e7fb75d..0568e02 100644 --- a/Lieb/Pages/Raids/RaidEdit.razor +++ b/Lieb/Pages/Raids/RaidEdit.razor @@ -323,7 +323,7 @@ } _raid.StartTimeUTC = await TimeZoneService.GetUTCDateTime(_raidDate.Date + _startTime.TimeOfDay); - if(_startTime.TimeOfDay > _endTime.TimeOfDay) + if(_startTime.TimeOfDay <= _endTime.TimeOfDay) { _raid.EndTimeUTC = await TimeZoneService.GetUTCDateTime(_raidDate.Date + _endTime.TimeOfDay); } diff --git a/Lieb/Program.cs b/Lieb/Program.cs index 88cf0c3..c3a928a 100644 --- a/Lieb/Program.cs +++ b/Lieb/Program.cs @@ -1,9 +1,5 @@ -using Discord; -using Discord.Commands; -using Discord.OAuth2; -using Discord.WebSocket; +using Discord.OAuth2; using Lieb.Data; -using Lieb.Discord; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.EntityFrameworkCore; using Microsoft.Net.Http.Headers; diff --git a/SharedClasses/SharedModels/ApiRaid.cs b/SharedClasses/SharedModels/ApiRaid.cs index 9a53e38..5938f5a 100644 --- a/SharedClasses/SharedModels/ApiRaid.cs +++ b/SharedClasses/SharedModels/ApiRaid.cs @@ -49,6 +49,8 @@ namespace SharedClasses.SharedModels public class User { + public ulong UserId {get; set;} + public string UserName { get; set; } = string.Empty; public string AccountName { get; set; } = string.Empty;