Added Discord Bot

This commit is contained in:
Sarah Faey 2022-11-06 19:10:58 +01:00
parent e7a0c9ae68
commit e445b2a181
48 changed files with 1255 additions and 157 deletions

View file

@ -1,130 +0,0 @@
using Lieb.Data;
using Lieb.Models.GuildWars2;
using Lieb.Models.GuildWars2.Raid;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Net;
namespace Lieb.Controllers
{
public class GuildWars2RaidController : ControllerBase
{
private readonly RaidService _raidService;
private readonly UserService _userService;
private readonly IDbContextFactory<LiebContext> _contextFactory;
public GuildWars2RaidController(RaidService raidService, UserService userService, IDbContextFactory<LiebContext> contextFactory)
{
_raidService = raidService;
_userService = userService;
_contextFactory = contextFactory;
}
[HttpGet]
public int GetRaidIdByDiscordMessageId(int discordMessageId)
{
using var context = _contextFactory.CreateDbContext();
DiscordRaidMessage discordMessage = context.DiscordRaidMessages.FirstOrDefault(m => m.DiscordRaidMessageId == discordMessageId);
//TODO retrun 404
return discordMessage != null ? discordMessage.RaidId : -1;
}
[HttpGet]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> GetRaidIdByDiscordMessageId([FromRoute] ulong discordMessageId)
{
using var context = _contextFactory.CreateDbContext();
DiscordRaidMessage? discordMessage = await context.DiscordRaidMessages.FirstOrDefaultAsync(m => m.DiscordMessageId == discordMessageId);
if (discordMessage == null)
{
return Problem(statusCode: (int)HttpStatusCode.NotFound);
}
return Ok(discordMessage.RaidId);
}
[HttpGet]
[ProducesResponseType(typeof(List<RaidRole>), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> GetFreeRoles([FromRoute] int raidId)
{
Raid raid = _raidService.GetRaid(raidId);
Dictionary<int, int> roleMap = new Dictionary<int, int>();
foreach(RaidSignUp signUp in raid.SignUps)
{
if (signUp.SignUpType == SignUpType.SignedUp)
{
roleMap[signUp.RaidRoleId] += 1;
}
}
return Ok(raid.Roles.Where(r => roleMap.ContainsKey(r.RaidRoleId) && roleMap[r.RaidRoleId] < r.Spots).ToList());
}
[HttpGet]
[ProducesResponseType(typeof(List<GuildWars2Account>), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> GetGuildwars2Accounts([FromRoute] ulong userId)
{
using var context = _contextFactory.CreateDbContext();
List<GuildWars2Account>? gw2Accounts = (await context.LiebUsers.FirstOrDefaultAsync(u => u.Id == userId))?.GuildWars2Accounts.ToList();
if (gw2Accounts == null)
{
return Problem(statusCode: (int)HttpStatusCode.NotFound);
}
return Ok(gw2Accounts);
}
[HttpPost]
public string SignUp(int raidId, ulong discordUserId, int guildWars2AccountId, int roleId, SignUpType signUpType)
{
int userId = _userService.GetLiebUserId(discordUserId).Result;
_raidService.SignUp(raidId, userId, guildWars2AccountId, roleId, signUpType).Wait();
return String.Empty;
}
[HttpGet("metadata/{fileId}")]
[Consumes("application/json")]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> GetFileMetadata([FromRoute] Guid fileId)
{
using var context = _contextFactory.CreateDbContext();
DiscordRaidMessage discordMessage = context.DiscordRaidMessages.FirstOrDefault(m => m.DiscordRaidMessageId == discordMessageId);
if (discordMessage == null)
{
return Problem(statusCode: (int)HttpStatusCode.NotFound);
}
return Ok(discordMessage.RaidId);
}
[HttpGet("metadata/{fileId}")]
[Authorize(Roles = Roles.TimeBoard)]
[Consumes("application/json")]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> GetFileMetadata([FromRoute] Guid fileId)
{
using var context = _contextFactory.CreateDbContext();
DiscordRaidMessage discordMessage = context.DiscordRaidMessages.FirstOrDefault(m => m.DiscordRaidMessageId == discordMessageId);
if(discordMessage == null)
{
return Problem(statusCode: (int)HttpStatusCode.NotFound);
}
return Ok(discordMessage.RaidId);
}
}
}

View file

@ -2,6 +2,7 @@
{
public static class Constants
{
public const string HttpClientName = "Discord";
public const string ClaimType = "Role";
public static readonly int RaidEditPowerLevel = Roles.Moderator.PowerLevel;

View file

@ -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<GuildWars2Account>(){ linaith, sarah} },
//new LiebUser{Id=194863625477816321, Name="Sarah", Birthday=DateTime.Parse("1992-01-15"), GuildWars2Accounts = new List<GuildWars2Account>(){ linaith, sarah} },
//new LiebUser{Id=0, Name="Sarah", Birthday=DateTime.Parse("1992-01-15"), GuildWars2Accounts = new List<GuildWars2Account>(){ linaith, sarah} },
new LiebUser{Id=194863625477816321, Name="Sarah", Birthday=DateTime.Parse("1992-01-15"), GuildWars2Accounts = new List<GuildWars2Account>(){ linaith, sarah} },
#if DEBUG
new LiebUser{Id=1, Name="Lisa", GuildWars2Accounts = new List<GuildWars2Account>(){ hierpiepts}},
new LiebUser{Id=2, Name="Simon", GuildWars2Accounts = new List<GuildWars2Account>(){ bloodseeker}}

253
Lieb/Data/DiscordService.cs Normal file
View file

@ -0,0 +1,253 @@
using SharedClasses.SharedModels;
using System.Net.Http;
using static System.Net.Mime.MediaTypeNames;
using System.Text.Json;
using System.Text;
using Lieb.Models.GuildWars2.Raid;
using Microsoft.EntityFrameworkCore;
namespace Lieb.Data
{
public class DiscordService
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly IDbContextFactory<LiebContext> _contextFactory;
private readonly JsonSerializerOptions _serializerOptions;
public DiscordService(IHttpClientFactory httpClientFactory, IDbContextFactory<LiebContext> contextFactory)
{
_httpClientFactory = httpClientFactory;
_contextFactory = contextFactory;
_serializerOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
}
public async Task PostRaidMessage(int raidId)
{
using var context = _contextFactory.CreateDbContext();
Raid raid = context.Raids
.Include(r => r.Roles)
.Include(r => r.SignUps)
.ThenInclude(s => s.LiebUser)
.Include(r => r.SignUps)
.ThenInclude(s => s.GuildWars2Account)
.Include(r => r.SignUps)
.ThenInclude(s => s.RaidRole)
.Include(r => r.DiscordRaidMessages)
.FirstOrDefault(r => r.RaidId == raidId);
await PostRaidMessage(raid);
}
public async Task PostRaidMessage(Raid raid)
{
var httpClient = _httpClientFactory.CreateClient(Constants.HttpClientName);
ApiRaid apiRaid = ConvertRaid(raid);
var raidItemJson = new StringContent(
JsonSerializer.Serialize(apiRaid),
Encoding.UTF8,
Application.Json);
string json = JsonSerializer.Serialize(apiRaid);
var httpResponseMessage = await httpClient.PostAsync("raid/PostRaidMessage", raidItemJson);
if (httpResponseMessage.IsSuccessStatusCode)
{
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
ApiRaid returnedRaid = await JsonSerializer.DeserializeAsync<ApiRaid>(contentStream, _serializerOptions);
await UpdateDiscordMessages(returnedRaid.DisocrdMessages, raid);
}
}
public async Task DeleteRaidMessages(Raid raid)
{
await DeleteRaidMessages(raid.DiscordRaidMessages);
}
public async Task DeleteRaidMessages(IEnumerable<DiscordRaidMessage> messages)
{
var httpClient = _httpClientFactory.CreateClient(Constants.HttpClientName);
IEnumerable<ApiRaid.DiscordMessage> apiMessages = ConvertMessages(messages);
var messageItemJson = new StringContent(
JsonSerializer.Serialize(apiMessages),
Encoding.UTF8,
Application.Json);
var httpResponseMessage = await httpClient.PostAsync("raid/DeleteRaidMessage", messageItemJson);
httpResponseMessage.EnsureSuccessStatusCode();
}
public async Task<List<DiscordServer>> GetServers()
{
try
{
var httpClient = _httpClientFactory.CreateClient(Constants.HttpClientName);
var httpResponseMessage = await httpClient.GetAsync("raid/GetServers");
if (httpResponseMessage.IsSuccessStatusCode)
{
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync<List<DiscordServer>>(contentStream, _serializerOptions);
}
}
catch(Exception e)
{
}
return new List<DiscordServer>();
}
public async Task SendUserReminder(RaidReminder reminder)
{
using var context = _contextFactory.CreateDbContext();
var httpClient = _httpClientFactory.CreateClient(Constants.HttpClientName);
Raid raid = context.Raids
.Include(r => r.SignUps)
.ThenInclude(s => s.LiebUser)
.FirstOrDefault(r => r.RaidId == reminder.RaidId);
if(raid == null) return;
ApiUserReminder apiReminder = ConvertUserReminder(reminder, raid);
var raidItemJson = new StringContent(
JsonSerializer.Serialize(apiReminder),
Encoding.UTF8,
Application.Json);
var httpResponseMessage = await httpClient.PostAsync("raid/SendUserReminder", raidItemJson);
if (httpResponseMessage.IsSuccessStatusCode)
{
reminder.Sent = true;
context.Update(reminder);
await context.SaveChangesAsync();
}
}
public async Task SendChannelReminder(RaidReminder reminder)
{
var httpClient = _httpClientFactory.CreateClient(Constants.HttpClientName);
ApiChannelReminder apiReminder = ConvertChannelReminder(reminder);
var raidItemJson = new StringContent(
JsonSerializer.Serialize(apiReminder),
Encoding.UTF8,
Application.Json);
var httpResponseMessage = await httpClient.PostAsync("raid/SendChannelReminder", raidItemJson);
if (httpResponseMessage.IsSuccessStatusCode)
{
reminder.Sent = true;
using var context = _contextFactory.CreateDbContext();
context.Update(reminder);
await context.SaveChangesAsync();
}
}
private async Task UpdateDiscordMessages(IEnumerable<ApiRaid.DiscordMessage> messages, Raid raid)
{
foreach(ApiRaid.DiscordMessage message in messages)
{
if(raid.DiscordRaidMessages.Where(m => m.DiscordRaidMessageId == message.WebsiteDatabaseId).Any())
{
raid.DiscordRaidMessages.FirstOrDefault(m => m.DiscordRaidMessageId == message.WebsiteDatabaseId).DiscordMessageId = message.MessageId;
}
}
using var context = _contextFactory.CreateDbContext();
context.Update(raid.DiscordRaidMessages);
await context.SaveChangesAsync();
}
private ApiRaid ConvertRaid(Raid raid)
{
ApiRaid apiRaid = new ApiRaid(){
Title = raid.Title,
Description = raid.Description,
Guild = raid.Guild,
Organizer = raid.Organizer,
VoiceChat = raid.VoiceChat,
StartTimeUTC = raid.StartTimeUTC,
EndTimeUTC = raid.EndTimeUTC,
RaidId = raid.RaidId
};
apiRaid.DisocrdMessages = ConvertMessages(raid.DiscordRaidMessages);
apiRaid.Roles = new List<ApiRaid.Role>();
foreach(RaidRole role in raid.Roles)
{
ApiRaid.Role apiRole = new ApiRaid.Role(){
Description = role.Description,
Name = role.Name,
Spots = role.Spots
};
apiRole.Users = new List<ApiRaid.Role.User>();
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
});
}
apiRaid.Roles.Add(apiRole);
}
return apiRaid;
}
private List<ApiRaid.DiscordMessage> ConvertMessages(IEnumerable<DiscordRaidMessage> messages)
{
List<ApiRaid.DiscordMessage> apiMessages = new List<ApiRaid.DiscordMessage>();
foreach(DiscordRaidMessage message in messages)
{
apiMessages.Add(new ApiRaid.DiscordMessage(){
ChannelId = message.DiscordChannelId,
GuildId = message.DiscordGuildId,
MessageId = message.DiscordMessageId,
WebsiteDatabaseId = message.DiscordRaidMessageId
});
}
return apiMessages;
}
private ApiUserReminder ConvertUserReminder(RaidReminder reminder, Raid raid)
{
ApiUserReminder apiReminder = new ApiUserReminder()
{
Message = reminder.Message
};
apiReminder.UserIds = new List<ulong>();
foreach(RaidSignUp signUp in raid.SignUps)
{
apiReminder.UserIds.Add(signUp.LiebUserId);
}
return apiReminder;
}
private ApiChannelReminder ConvertChannelReminder(RaidReminder reminder)
{
return new ApiChannelReminder()
{
DiscordServerId = reminder.DiscordServerId,
DiscordChannelId = reminder.DiscordChannelId,
Message = reminder.Message
};
}
}
}

View file

@ -7,10 +7,12 @@ namespace Lieb.Data
public class RaidService
{
private readonly IDbContextFactory<LiebContext> _contextFactory;
private readonly DiscordService _discordService;
public RaidService(IDbContextFactory<LiebContext> contextFactory)
public RaidService(IDbContextFactory<LiebContext> contextFactory, DiscordService discordService)
{
_contextFactory = contextFactory;
_discordService = discordService;
}
public List<Raid> GetRaids()
@ -26,6 +28,7 @@ namespace Lieb.Data
.ThenInclude(s => s.GuildWars2Account)
.Include(r => r.SignUps)
.ThenInclude(s => s.RaidRole)
.Include(r => r.DiscordRaidMessages)
.ToList();
}
@ -42,10 +45,11 @@ namespace Lieb.Data
.ThenInclude(s => s.GuildWars2Account)
.Include(r => r.SignUps)
.ThenInclude(s => s.RaidRole)
.Include(r => r.DiscordRaidMessages)
.FirstOrDefault(r => r.RaidId == raidId);
}
public async Task AddOrEditRaid(Raid raid, List<RaidRole> rolesToDelete, List<RaidReminder> remindersToDelete)
public async Task AddOrEditRaid(Raid raid, List<RaidRole> rolesToDelete, List<RaidReminder> remindersToDelete, List<DiscordRaidMessage> messagesToDelete)
{
if (raid != null)
{
@ -60,6 +64,7 @@ namespace Lieb.Data
context.Update(raid);
context.RaidRoles.RemoveRange(rolesToDelete);
context.RaidReminders.RemoveRange(remindersToDelete);
context.DiscordRaidMessages.RemoveRange(messagesToDelete);
//move users back to "Random" role
if (raid.RaidType != RaidType.Planned)
@ -74,6 +79,7 @@ namespace Lieb.Data
await context.SaveChangesAsync();
}
_discordService.PostRaidMessage(raid.RaidId);
}
}
@ -88,6 +94,7 @@ namespace Lieb.Data
await context.SaveChangesAsync();
context.Raids.Remove(raid);
await context.SaveChangesAsync();
_discordService.DeleteRaidMessages(raid);
}
public async Task SignUp(int raidId, ulong liebUserId, int guildWars2AccountId, int plannedRoleId, SignUpType signUpType)
@ -115,6 +122,7 @@ namespace Lieb.Data
});
await context.SaveChangesAsync();
}
_discordService.PostRaidMessage(raidId);
}
public async Task SignOff(int raidId, ulong liebUserId)
@ -138,8 +146,8 @@ namespace Lieb.Data
signUp.RaidRole = raid.Roles.FirstOrDefault(r => r.IsRandomSignUpRole);
}
}
await context.SaveChangesAsync();
_discordService.PostRaidMessage(raidId);
}
public async Task ChangeAccount(int raidId, ulong liebUserId, int guildWars2AccountId)
@ -151,6 +159,7 @@ namespace Lieb.Data
signUp.GuildWars2AccountId = guildWars2AccountId;
}
await context.SaveChangesAsync();
_discordService.PostRaidMessage(raidId);
}
public void ChangeSignUpType(int raidId, ulong liebUserId, int plannedRoleId, SignUpType signUpType)
@ -180,6 +189,7 @@ namespace Lieb.Data
signUp.SignUpType = signUpType;
}
context.SaveChanges();
_discordService.PostRaidMessage(raidId);
}
public bool IsRoleSignUpAllowed(ulong liebUserId, int plannedRoleId, SignUpType signUpType)
@ -334,9 +344,8 @@ namespace Lieb.Data
using var context = _contextFactory.CreateDbContext();
List<Raid> raids = context.Raids
.Include(r => r.Reminders)
.Where(raid => raid.Reminders.Where(reminder => !reminder.Sent && raid.StartTimeUTC.AddHours(-reminder.HoursBeforeRaid) < DateTime.UtcNow).Any())
.ToList();
foreach(Raid raid in raids)
{
foreach(RaidReminder reminder in raid.Reminders.Where(reminder => !reminder.Sent && raid.StartTimeUTC.AddHours(-reminder.HoursBeforeRaid) < DateTime.UtcNow))

View file

@ -0,0 +1,62 @@
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);
}
}
}

View file

@ -0,0 +1,25 @@
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()
{
}
}
}

View file

@ -7,7 +7,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Discord.Net" Version="3.7.2" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="6.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
@ -18,7 +20,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\Shared.csproj" />
<ProjectReference Include="..\SharedClasses\SharedClasses.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,14 @@
namespace Lieb.Models.GuildWars2
{
public class Equipped
{
public int EquippedId { get; set; }
public bool CanTank { get; set; }
public int GuildWars2AccountId { get; set; }
public int GuildWars2BuildId { get; set; }
public GuildWars2Account GuildWars2Account { get; set; }
public GuildWars2Build GuildWars2Build { get; set; }
}
}

View file

@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
namespace Lieb.Models.GuildWars2
{
public class GuildWars2Account
{
public int GuildWars2AccountId { get; set; }
public string ApiKey { get; set; } = string.Empty;
[Required]
[RegularExpression("^[a-zA-z ]{3,27}\\.[0-9]{4}$", ErrorMessage = "Invalid Account Name")]
public string AccountName { get; set; } = string.Empty;
public ICollection<Equipped> EquippedBuilds { get; set; } = new List<Equipped>();
}
}

View file

@ -0,0 +1,93 @@
using System.ComponentModel.DataAnnotations;
namespace Lieb.Models.GuildWars2
{
public enum Role
{
Might = 0,
Quickness = 1,
Alacrity = 2,
Heal = 3,
Tank = 4,
pDps = 5,
cDps = 6,
}
public enum GuildWars2Class
{
Elementalist = 1,
Engineer = 2,
Thief = 3,
Ranger = 4,
Necromancer = 5,
Mesmer = 6,
Revenant = 7,
Guard = 8,
Warrior = 9,
}
public enum EliteSpecialization
{
Elemantalist = 1,
Tempest = 2,
Weaver = 3,
Catalyst = 4,
Engineer = 5,
Scrapper = 6,
Holosmith = 7,
Mechanist = 8,
Thief = 9,
DareDevil = 10,
Deadeye = 11,
Spectre = 12,
Ranger = 13,
Druid = 14,
Soulbeast = 15,
Untamed = 16,
Necromancer = 17,
Reaper = 18,
Scourge = 19,
Harbinger = 20,
Mesmer = 21,
Chronomancer = 22,
Mirage = 23,
Virtuoso = 24,
Revenant = 25,
Herald = 26,
Renegade = 27,
Vindicator = 28,
Guard = 29,
Dragonhunter = 30,
Firebrand = 31,
Willbender = 32,
Warrior = 33,
Berserker = 34,
Spellbreaker = 35,
Bladesworn = 36,
}
public class GuildWars2Build
{
public int GuildWars2BuildId { get; set; }
[Required]
[StringLength(60, ErrorMessage = "BuildName too long (60 character limit).")]
public string BuildName { get; set; } = String.Empty;
public short Might { get; set; }
public short Quickness { get; set; }
public short Alacrity { get; set; }
public short Heal { get; set; }
[Required]
[Range(1, 9, ErrorMessage = "Please select a class")]
public GuildWars2Class Class { get; set; }
[Required]
[Range(1, 90, ErrorMessage = "Please select an elite specialization")]
public EliteSpecialization EliteSpecialization { get; set; }
public ICollection<Equipped> EquippedRoles { get; set; } = new List<Equipped>();
}
}

View file

@ -0,0 +1,17 @@
namespace Lieb.Models.GuildWars2.Raid
{
public class DiscordRaidMessage
{
public int DiscordRaidMessageId { get; set; }
public int RaidId { get; set; }
public Raid Raid { get; set; }
public ulong DiscordMessageId { get; set; }
public ulong DiscordChannelId { get; set; }
public ulong DiscordGuildId { get; set; }
}
}

View file

@ -0,0 +1,40 @@
using System.ComponentModel.DataAnnotations;
namespace Lieb.Models.GuildWars2.Raid
{
public enum RaidType
{
Planned = 0,
RandomWithBoons = 1,
RandomClasses = 2,
RandomEliteSpecialization = 3,
}
public class Raid : RaidBase
{
public int RaidId { get; private set; }
[Required]
public DateTimeOffset StartTimeUTC { get; set; } = DateTime.Now;
[Required]
public DateTimeOffset EndTimeUTC { get; set; }
public DateTimeOffset FreeForAllTimeUTC { get; set; }
public ICollection<RaidSignUp> SignUps { get; set; } = new HashSet<RaidSignUp>();
public ICollection<RaidSignUpHistory> SignUpHistory { get; set; } = new HashSet<RaidSignUpHistory>();
public Raid() { }
public Raid(RaidTemplate template) : base(template)
{
TimeZoneInfo timeZone = TimeZoneInfo.FindSystemTimeZoneById(template.TimeZone);
StartTimeUTC = TimeZoneInfo.ConvertTimeToUtc(template.StartTime, timeZone);
EndTimeUTC = TimeZoneInfo.ConvertTimeToUtc(template.EndTime, timeZone);
FreeForAllTimeUTC = TimeZoneInfo.ConvertTimeToUtc(template.FreeForAllTime, timeZone);
}
}
}

View file

@ -0,0 +1,90 @@
using System.ComponentModel.DataAnnotations;
namespace Lieb.Models.GuildWars2.Raid
{
public abstract class RaidBase
{
[Required]
[StringLength(100, ErrorMessage = "Title too long (100 character limit).")]
public string Title { get; set; } = String.Empty;
[Required]
[StringLength(1000, ErrorMessage = "Description too long (1000 character limit).")]
public string Description { get; set; } = String.Empty;
[Required]
[StringLength(50, ErrorMessage = "Organizer too long (50 character limit).")]
public string Organizer { get; set; } = String.Empty;
[Required]
[StringLength(50, ErrorMessage = "Guild too long (50 character limit).")]
public string Guild { get; set; } = String.Empty;
[Required]
[StringLength(50, ErrorMessage = "VoiceChat too long (50 character limit).")]
public string VoiceChat { get; set; } = String.Empty;
[Required]
public RaidType RaidType { get; set; }
public string RequiredRole { get; set; } = String.Empty;
public bool MoveFlexUsers { get; set; } = true;
public ulong RaidOwnerId { get; set; }
//role name, number of spots
public ICollection<RaidRole> Roles { get; set; } = new HashSet<RaidRole>();
public ICollection<RaidReminder> Reminders { get; set; } = new List<RaidReminder>();
public ICollection<DiscordRaidMessage> DiscordRaidMessages { get; set; } = new HashSet<DiscordRaidMessage>();
public RaidBase() { }
public RaidBase(RaidBase template)
{
this.Title = template.Title;
this.Description = template.Description;
this.Organizer = template.Organizer;
this.Guild = template.Guild;
this.VoiceChat = template.VoiceChat;
this.RaidType = template.RaidType;
this.RequiredRole = template.RequiredRole;
this.MoveFlexUsers = template.MoveFlexUsers;
this.RaidOwnerId = template.RaidOwnerId;
foreach (RaidRole role in template.Roles)
{
this.Roles.Add(new RaidRole()
{
Description = role.Description,
Name = role.Name,
Spots = role.Spots
});
}
foreach (RaidReminder reminder in template.Reminders)
{
this.Reminders.Add(new RaidReminder()
{
DiscordServerId = reminder.DiscordServerId,
DiscordChannelId = reminder.DiscordChannelId,
HoursBeforeRaid = reminder.HoursBeforeRaid,
Message = reminder.Message,
Sent = reminder.Sent,
Type = reminder.Type
});
}
foreach (DiscordRaidMessage message in template.DiscordRaidMessages)
{
this.DiscordRaidMessages.Add(new DiscordRaidMessage()
{
RaidId = message.RaidId,
DiscordMessageId = message.DiscordMessageId,
DiscordChannelId = message.DiscordChannelId,
DiscordGuildId = message.DiscordGuildId
});
}
}
}
}

View file

@ -0,0 +1,36 @@
using System.ComponentModel.DataAnnotations;
namespace Lieb.Models.GuildWars2.Raid
{
public class RaidReminder
{
public enum ReminderType
{
User = 1,
Channel = 2
}
public int RaidReminderId { get; set; }
[Required]
[Range(1, 2, ErrorMessage = "Please select a reminder type")]
public ReminderType Type { get; set; }
[Required]
[StringLength(1000, ErrorMessage = "Message too long (1000 character limit).")]
public string Message { get; set; }
[Required]
public double HoursBeforeRaid { get; set; }
public ulong DiscordServerId { get; set; }
public ulong DiscordChannelId { get; set; }
public bool Sent { get; set; } = false;
public int RaidId { get; set; }
public Raid Raid { get; set; }
}
}

View file

@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
namespace Lieb.Models.GuildWars2.Raid
{
public class RaidRole
{
public int RaidRoleId { get; set; }
public int Spots { get; set; }
[Required]
[StringLength(40, ErrorMessage = "Name too long (40 character limit).")]
public string Name { get; set; } = String.Empty;
[Required]
[StringLength(200, ErrorMessage = "Description too long (200 character limit).")]
public string Description { get; set; } = String.Empty;
public bool IsRandomSignUpRole { get; set; } = false;
}
}

View file

@ -0,0 +1,28 @@
namespace Lieb.Models.GuildWars2.Raid
{
public enum SignUpType
{
SignedUp = 0,
Maybe = 1,
Backup = 2,
Flex = 3,
SignedOff = 4
}
public class RaidSignUp
{
public int RaidSignUpId { get; set; }
public int RaidId { get; set; }
public ulong LiebUserId { get; set; }
public int GuildWars2AccountId { get; set; }
public int RaidRoleId { get; set; }
public SignUpType SignUpType { get; set; }
public Raid Raid { get; set; }
public LiebUser LiebUser { get; set; }
public GuildWars2Account GuildWars2Account { get; set; }
public RaidRole RaidRole { get; set; }
}
}

View file

@ -0,0 +1,15 @@
namespace Lieb.Models.GuildWars2.Raid
{
public class RaidSignUpHistory
{
public int RaidSignUpHistoryId { get; set; }
public string UserName { get; set; } = string.Empty;
public DateTimeOffset Time { get; set; } = DateTimeOffset.Now;
public SignUpType SignUpType { get; set; }
public Raid Raid { get; set; }
}
}

View file

@ -0,0 +1,23 @@
using System.ComponentModel.DataAnnotations;
namespace Lieb.Models.GuildWars2.Raid
{
public class RaidTemplate : RaidBase
{
public int RaidTemplateId { get; private set; }
[Required]
public DateTime StartTime { get; set; } = DateTime.Now;
[Required]
public DateTime EndTime { get; set; }
public DateTime FreeForAllTime { get; set; }
public string TimeZone { get; set; } = String.Empty;
public int Interval { get; set; }
public int CreateDaysBefore { get; set; }
}
}

28
Lieb/Models/LiebRole.cs Normal file
View file

@ -0,0 +1,28 @@
using System.ComponentModel.DataAnnotations;
namespace Lieb.Models
{
public enum RoleType
{
GuildRole = 1,
SystemRole = 2,
UserDefinedRole = 3
}
public class LiebRole
{
public int LiebRoleId { get; set; }
[Required]
[StringLength(40, ErrorMessage = "RoleName too long (40 character limit).")]
public string RoleName { get; set; } = string.Empty;
public RoleType Type { get; set; }
public int Level { get; set; } = 20;
public int LevelToAssign { get; set; } = 30;
public ICollection<RoleAssignment> RoleAssignments { get; set; } = new List<RoleAssignment>();
}
}

24
Lieb/Models/LiebUser.cs Normal file
View file

@ -0,0 +1,24 @@
using Lieb.Models.GuildWars2;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Lieb.Models
{
public class LiebUser
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public ulong Id { get; set; }
[Required]
[StringLength(40, ErrorMessage = "Name too long (40 character limit).")]
public string Name { get; set; } = string.Empty;
[StringLength(60, ErrorMessage = "Pronouns too long (60 character limit).")]
public string Pronouns { get; set; } = string.Empty;
public DateTime? Birthday { get; set; }
public DateTime? BannedUntil { get; set; }
public ICollection<GuildWars2Account> GuildWars2Accounts { get; set; } = new List<GuildWars2Account>();
public ICollection<RoleAssignment> RoleAssignments { get; set; } = new List<RoleAssignment>();
}
}

View file

@ -0,0 +1,13 @@
namespace Lieb.Models
{
public class RoleAssignment
{
public int RoleAssignmentId { get; set; }
public int LiebRoleId { get; set; }
public ulong LiebUserId { get; set; }
public LiebRole LiebRole { get; set; }
public LiebUser LiebUser { get; set; }
}
}

View file

@ -5,8 +5,10 @@
@using Lieb.Models.GuildWars2.Raid
@using System.ComponentModel.DataAnnotations
@using System.Security.Claims
@using SharedClasses.SharedModels
@inject RaidService RaidService
@inject UserService UserService
@inject DiscordService DiscordService
@inject TimeZoneService TimeZoneService
@inject NavigationManager NavigationManager
@inject AuthenticationStateProvider AuthenticationStateProvider
@ -143,6 +145,46 @@
</p>
}
<p>
<label>
Discord Messages:
</label>
<button type=button @onclick="() => AddDiscordMessageClicked()">Add message</button>
<table>
<tr>
<th>Server</th>
<th>Channel</th>
</tr>
@foreach( DiscordRaidMessage message in _raid.DiscordRaidMessages)
{
bool disableEdit = message.DiscordRaidMessageId != 0;
<tr>
<td>
<InputSelect @bind-Value="message.DiscordGuildId" disabled="@disableEdit">
@foreach(DiscordServer item in _discordServers)
{
<option value="@item.Id">@item.Name</option>
}
</InputSelect>
</td>
<td>
<InputSelect @bind-Value="message.DiscordChannelId" disabled="@disableEdit">
@if(message.DiscordGuildId > 0)
{
List<DiscordChannel> channels = _discordServers.Where(s => s.Id == message.DiscordGuildId).FirstOrDefault(new DiscordServer()).Channels;
@foreach(DiscordChannel item in channels)
{
<option value="@item.Id">@item.Name</option>
}
}
</InputSelect>
</td>
<td><button type=button @onclick="() => DeleteMessageClicked(message)">Delete</button></td>
</tr>
}
</table>
</p>
<ValidationSummary />
<label class="validation-message" >@_errorMessage</label>
<button type="submit">Submit</button>
@ -168,6 +210,10 @@
private DateTimeOffset _freeForAllTime;
private List<RaidRole> _rolesToDelete = new List<RaidRole>();
private List<RaidReminder> _remindersToDelete = new List<RaidReminder>();
private List<DiscordRaidMessage> _messagesToDelete = new List<DiscordRaidMessage>();
private List<DiscordServer> _discordServers = new List<DiscordServer>();
@ -206,6 +252,8 @@
{
_raid = new Raid();
}
_discordServers = await DiscordService.GetServers();
}
async Task AddRoleClicked()
@ -222,6 +270,20 @@
}
_raid.Roles.Remove(role);
}
async Task AddDiscordMessageClicked()
{
_raid.DiscordRaidMessages.Add(new DiscordRaidMessage());
}
async Task DeleteMessageClicked(DiscordRaidMessage message)
{
if(message.DiscordRaidMessageId != 0)
{
_messagesToDelete.Add(message);
}
_raid.DiscordRaidMessages.Remove(message);
}
async Task DeleteRaidClicked()
{
@ -276,7 +338,7 @@
_raid.RaidOwnerId = _user.Id;
}
await RaidService.AddOrEditRaid(_raid, _rolesToDelete, new List<RaidReminder>());
await RaidService.AddOrEditRaid(_raid, _rolesToDelete, _remindersToDelete, _messagesToDelete);
NavigationManager.NavigateTo("raidoverview");
}
}

View file

@ -1,5 +1,5 @@
@page "/useredit"
@page "/useredit/{userId}"
@page "/useredit/{_userId}"
@using Lieb.Data
@using Lieb.Models
@using Lieb.Models.GuildWars2
@ -59,6 +59,7 @@
</tr>
}
</table>
hi
<br />
@code {

View file

@ -1,7 +1,12 @@
using Discord.OAuth2;
using Discord;
using Discord.Commands;
using Discord.OAuth2;
using Discord.WebSocket;
using Lieb.Data;
using Lieb.Discord;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.EntityFrameworkCore;
using Microsoft.Net.Http.Headers;
var builder = WebApplication.CreateBuilder(args);
@ -10,7 +15,8 @@ builder.Services.AddRazorPages();
#if DEBUG
builder.Services.AddDbContextFactory<LiebContext>(opt =>
opt.UseSqlServer(builder.Configuration.GetConnectionString("LiebContext")).EnableSensitiveDataLogging(), ServiceLifetime.Transient);
//opt.UseSqlServer(builder.Configuration.GetConnectionString("LiebContext")).EnableSensitiveDataLogging(), ServiceLifetime.Transient);
opt.UseSqlite(builder.Configuration.GetConnectionString("LiebContext")));
#else
builder.Services.AddDbContextFactory<LiebContext>(opt =>
opt.UseMySql(builder.Configuration.GetConnectionString("LiebContext"), ServerVersion.AutoDetect(builder.Configuration.GetConnectionString("LiebContext"))), ServiceLifetime.Transient);
@ -27,8 +33,21 @@ builder.Services.AddScoped<GuildWars2AccountService>();
builder.Services.AddScoped<GuildWars2BuildService>();
builder.Services.AddScoped<RaidRandomizerService>();
builder.Services.AddScoped<TimeZoneService>();
builder.Services.AddScoped<DiscordService>();
builder.Services.AddHostedService<TimerService>();
/*
#region discord
//DiscordSocketClient client = new DiscordSocketClient();
//client.Log += Log;
var token = File.ReadAllText("token.txt");
await client.LoginAsync(TokenType.Bot, token);
await client.StartAsync();
builder.Services.AddSingleton(client);
builder.Services.AddSingleton<CommandService>();
builder.Services.AddSingleton<CommandHandler>();
#endregion discord*/
builder.Services.AddAuthentication(opt =>
{
@ -60,6 +79,24 @@ builder.Services.AddAuthorization(options =>
Constants.Roles.Admin.Name }));
});
builder.Services.AddHttpClient(Constants.HttpClientName , httpClient =>
{
httpClient.BaseAddress = new Uri("http://localhost:5240/");
// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
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();

View file

@ -8,6 +8,6 @@
},
"AllowedHosts": "*",
"ConnectionStrings": {
"LiebContext": "Server=(localdb)\\mssqllocaldb;Database=LiebContext-f9dae0eb-8d5c-495d-ab99-da7951e0638b;Trusted_Connection=True;MultipleActiveResultSets=true"
"LiebContext": "Data Source=./Database/mydb.sqlite;"
}
}