diff --git a/Blink3.API/Blink3.API.csproj b/Blink3.API/Blink3.API.csproj index 91339c1..0ac4e35 100644 --- a/Blink3.API/Blink3.API.csproj +++ b/Blink3.API/Blink3.API.csproj @@ -12,7 +12,6 @@ - diff --git a/Blink3.API/Controllers/ApiControllerBase.cs b/Blink3.API/Controllers/ApiControllerBase.cs index 21b93d1..22407e9 100644 --- a/Blink3.API/Controllers/ApiControllerBase.cs +++ b/Blink3.API/Controllers/ApiControllerBase.cs @@ -4,9 +4,7 @@ using Blink3.Core.DiscordAuth.Extensions; using Blink3.Core.Models; using Discord; -using Discord.Addons.Hosting.Util; using Discord.Rest; -using Discord.WebSocket; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Annotations; @@ -25,10 +23,12 @@ namespace Blink3.API.Controllers; [Route("api/[controller]")] [ApiController] [Authorize] -public abstract class ApiControllerBase(DiscordSocketClient discordSocketClient, ICachingService cachingService, IEncryptionService encryptionService) : ControllerBase +public abstract class ApiControllerBase(DiscordRestClient botClient, + Func userClientFactory, + ICachingService cachingService, + IEncryptionService encryptionService) : ControllerBase { - private DiscordRestClient? _client; - protected readonly DiscordSocketClient DiscordBotClient = discordSocketClient; + private readonly DiscordRestClient _userClient = userClientFactory(); /// /// Represents an Unauthorized Access message. @@ -90,32 +90,26 @@ private ObjectResult ProblemForUnauthorizedAccess() return userId != UserId ? ProblemForUnauthorizedAccess() : null; } - private async Task InitDiscordClientAsync() + private async Task AuthenticateUserClientAsync() { - await DiscordBotClient.WaitForReadyAsync(CancellationToken.None); - if (_client is not null) return; - string? encryptedToken = await cachingService.GetAsync($"token:{UserId}"); string? iv = await cachingService.GetAsync($"token:{UserId}:iv"); if (encryptedToken is null || iv is null) return; string accessToken = encryptionService.Decrypt(encryptedToken, iv); - - _client = new DiscordRestClient(); - await _client.LoginAsync(TokenType.Bearer, accessToken); + await _userClient.LoginAsync(TokenType.Bearer, accessToken); } protected async Task> GetUserGuilds() { - await InitDiscordClientAsync(); + await AuthenticateUserClientAsync(); List managedGuilds = await cachingService.GetOrAddAsync($"discord:guilds:{UserId}", async () => { List manageable = []; - if (_client is null) return manageable; - IAsyncEnumerable> guilds = _client.GetGuildSummariesAsync(); + IAsyncEnumerable> guilds = _userClient.GetGuildSummariesAsync(); await foreach (IReadOnlyCollection guildCollection in guilds) { manageable.AddRange(guildCollection.Where(g => g.Permissions.ManageGuild).Select(g => @@ -130,7 +124,10 @@ protected async Task> GetUserGuilds() return manageable; }, TimeSpan.FromMinutes(5)); - List discordGuildIds = DiscordBotClient.Guilds.Select(b => b.Id).ToList(); + List discordGuildIds = await botClient.GetGuildSummariesAsync() + .SelectMany(guildCollection => guildCollection.ToAsyncEnumerable()) + .Select(guild => guild.Id) + .ToListAsync(); return managedGuilds.Where(g => discordGuildIds.Contains(g.Id)).ToList(); } @@ -149,7 +146,7 @@ protected async Task> GetUserGuilds() ~ApiControllerBase() { - _client?.Dispose(); - _client = null; + _userClient.LogoutAsync().Wait(); + _userClient.Dispose(); } } \ No newline at end of file diff --git a/Blink3.API/Controllers/BlinkGuildsController.cs b/Blink3.API/Controllers/BlinkGuildsController.cs index 4204b41..ac30870 100644 --- a/Blink3.API/Controllers/BlinkGuildsController.cs +++ b/Blink3.API/Controllers/BlinkGuildsController.cs @@ -1,10 +1,9 @@ using Blink3.API.Interfaces; using Blink3.Core.Caching; -using Blink3.Core.DTOs; using Blink3.Core.Entities; using Blink3.Core.Models; using Blink3.Core.Repositories.Interfaces; -using Discord.WebSocket; +using Discord.Rest; using Microsoft.AspNetCore.JsonPatch; using Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Annotations; @@ -15,8 +14,12 @@ namespace Blink3.API.Controllers; /// Controller for performing CRUD operations on BlinkGuild items. /// [SwaggerTag("All CRUD operations for BlinkGuild items")] -public class BlinkGuildsController(DiscordSocketClient discordSocketClient, ICachingService cachingService, IEncryptionService encryptionService, IBlinkGuildRepository blinkGuildRepository) - : ApiControllerBase(discordSocketClient, cachingService, encryptionService) +public class BlinkGuildsController(DiscordRestClient botClient, + Func userClientFactory, + ICachingService cachingService, + IEncryptionService encryptionService, + IBlinkGuildRepository blinkGuildRepository) + : ApiControllerBase(botClient, userClientFactory, cachingService, encryptionService) { /// /// Retrieves all BlinkGuild items that are manageable by the logged in user. @@ -64,6 +67,7 @@ public async Task> GetBlinkGuild(ulong id) /// /// The ID of the BlinkGuild item to update. /// The updated BlinkGuild item data. + /// /// /// No content. /// diff --git a/Blink3.API/Controllers/GuildsController.cs b/Blink3.API/Controllers/GuildsController.cs index 70801f3..11df7f3 100644 --- a/Blink3.API/Controllers/GuildsController.cs +++ b/Blink3.API/Controllers/GuildsController.cs @@ -1,16 +1,22 @@ using Blink3.API.Interfaces; using Blink3.Core.Caching; using Blink3.Core.Models; -using Discord.WebSocket; +using Discord.Rest; using Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Annotations; namespace Blink3.API.Controllers; [SwaggerTag("Endpoints for getting information on discord guilds")] -public class GuildsController(DiscordSocketClient discordSocketClient, ICachingService cachingService, IEncryptionService encryptionService) - : ApiControllerBase(discordSocketClient, cachingService, encryptionService) +public class GuildsController(DiscordRestClient botClient, + Func userClientFactory, + ICachingService cachingService, + IEncryptionService encryptionService) + : ApiControllerBase(botClient, userClientFactory, cachingService, encryptionService) { + private readonly DiscordRestClient _botClient = botClient; + private readonly ICachingService _cachingService = cachingService; + [HttpGet] [SwaggerOperation( Summary = "Returns all Discord guilds", @@ -39,15 +45,23 @@ public async Task>> GetC ObjectResult? accessCheckResult = await CheckGuildAccessAsync(id); if (accessCheckResult is not null) return accessCheckResult; - return DiscordBotClient.GetGuild(id).CategoryChannels - .OrderBy(c => c.Position) - .Select(c => - new DiscordPartialChannel - { - Id = c.Id, - Name = c.Name - }) - .ToList(); + string cacheKey = $"guild_{id}_categories"; + IReadOnlyCollection categories = await _cachingService.GetOrAddAsync(cacheKey, async () => + { + RestGuild? guild = await _botClient.GetGuildAsync(id); + IReadOnlyCollection? categories = await guild.GetCategoryChannelsAsync(); + return categories + .OrderBy(c => c.Position) + .Select(c => + new DiscordPartialChannel + { + Id = c.Id, + Name = c.Name + }) + .ToList(); + }, TimeSpan.FromMinutes(5)); + + return Ok(categories); } [HttpGet("{id}/channels")] @@ -63,14 +77,22 @@ public async Task>> GetC ObjectResult? accessCheckResult = await CheckGuildAccessAsync(id); if (accessCheckResult is not null) return accessCheckResult; - return DiscordBotClient.GetGuild(id).TextChannels - .OrderBy(c => c.Position) - .Select(c => - new DiscordPartialChannel - { - Id = c.Id, - Name = c.Name - }) - .ToList(); + string cacheKey = $"guild_{id}_channels"; + IReadOnlyCollection channels = await _cachingService.GetOrAddAsync(cacheKey, async () => + { + RestGuild? guild = await _botClient.GetGuildAsync(id); + IReadOnlyCollection? channels = await guild.GetTextChannelsAsync(); + return channels + .OrderBy(c => c.Position) + .Select(c => + new DiscordPartialChannel + { + Id = c.Id, + Name = c.Name + }) + .ToList(); + }, TimeSpan.FromMinutes(5)); + + return Ok(channels); } } \ No newline at end of file diff --git a/Blink3.API/Controllers/TodoController.cs b/Blink3.API/Controllers/TodoController.cs index 5278d10..3f995d2 100644 --- a/Blink3.API/Controllers/TodoController.cs +++ b/Blink3.API/Controllers/TodoController.cs @@ -3,7 +3,7 @@ using Blink3.Core.DTOs; using Blink3.Core.Entities; using Blink3.Core.Repositories.Interfaces; -using Discord.WebSocket; +using Discord.Rest; using Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Annotations; @@ -13,8 +13,12 @@ namespace Blink3.API.Controllers; /// Controller for performing CRUD operations on userTodo items. /// [SwaggerTag("All CRUD operations for todo items")] -public class TodoController(DiscordSocketClient discordSocketClient, ICachingService cachingService, IEncryptionService encryptionService, IUserTodoRepository todoRepository) - : ApiControllerBase(discordSocketClient, cachingService, encryptionService) +public class TodoController(DiscordRestClient botClient, + Func userClientFactory, + ICachingService cachingService, + IEncryptionService encryptionService, + IUserTodoRepository todoRepository) + : ApiControllerBase(botClient, userClientFactory, cachingService, encryptionService) { /// /// Retrieves all userTodo items for the current user. diff --git a/Blink3.API/Program.cs b/Blink3.API/Program.cs index 68540cb..cbdb6a1 100644 --- a/Blink3.API/Program.cs +++ b/Blink3.API/Program.cs @@ -7,8 +7,7 @@ using Blink3.Core.Helpers; using Blink3.DataAccess.Extensions; using Discord; -using Discord.Addons.Hosting; -using Discord.WebSocket; +using Discord.Rest; using Microsoft.AspNetCore.HttpOverrides; using Serilog; using Serilog.Events; @@ -44,17 +43,18 @@ builder.Services.AddAppConfiguration(builder.Configuration); BlinkConfiguration appConfig = builder.Services.GetAppConfiguration(); - // Discord socket client - builder.Services.AddDiscordHost((config, _) => + // Discord bot client + builder.Services.AddSingleton(_ => { - config.SocketConfig = new DiscordSocketConfig - { - LogLevel = LogSeverity.Verbose, - MessageCacheSize = 0, - GatewayIntents = GatewayIntents.Guilds - }; - - config.Token = appConfig.Discord.BotToken; + DiscordRestClient client = new(); + client.LoginAsync(TokenType.Bot, appConfig.Discord.BotToken).Wait(); + return client; + }); + + // Discord user client + builder.Services.AddScoped>(_ => + { + return () => new DiscordRestClient(); }); // Add forwarded headers