// Copyright (C) 2021-present sam (starshines.gay) // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published // by the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . using System.Net; using Catalogger.Backend.Api.Middleware; using Catalogger.Backend.Cache; using Catalogger.Backend.Cache.InMemoryCache; using Catalogger.Backend.Database; using Catalogger.Backend.Database.Repositories; using Catalogger.Backend.Extensions; using Catalogger.Backend.Services; using Microsoft.AspNetCore.Mvc; using Remora.Discord.API; using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Rest; using Remora.Rest.Core; namespace Catalogger.Backend.Api; [Route("/api/guilds/{id}")] public partial class GuildsController( ILogger logger, DatabaseConnection dbConn, GuildRepository guildRepository, InviteRepository inviteRepository, WatchlistRepository watchlistRepository, ChannelCache channelCache, RoleCache roleCache, IMemberCache memberCache, IInviteCache inviteCache, UserCache userCache, DiscordRequestService discordRequestService, IDiscordRestUserAPI userApi, WebhookExecutorService webhookExecutor ) : ApiControllerBase { private readonly ILogger _logger = logger.ForContext(); private async Task<(Snowflake GuildId, Guild Guild)> ParseGuildAsync(string id) { var guilds = await discordRequestService.GetGuildsAsync(CurrentToken); var guild = guilds.FirstOrDefault(g => g.CanManage && g.Id == id); if (guild == null) throw new ApiError( HttpStatusCode.NotFound, ErrorCode.UnknownGuild, "Unknown server, or you're not in it, or you can't manage it." ); if (!DiscordSnowflake.TryParse(guild.Id, out var guildId)) throw new CataloggerError("Invalid snowflake passed to GuildsController.ToResponse"); return (guildId.Value, guild); } [Authorize] [HttpGet] public async Task GetGuildAsync(string id) { var (guildId, guild) = await ParseGuildAsync(id); var guildConfig = await guildRepository.GetAsync(guildId); var channels = channelCache .GuildChannels(guildId) .OrderBy(c => c.Position.OrDefault(0)) .ToList(); var channelsWithoutCategories = channels .Where(c => !c.ParentID.IsDefined() && c.Type is not ChannelType.GuildCategory) .Select(ToChannel); var categories = channels .Where(c => c.Type is ChannelType.GuildCategory) .Select(c => new GuildCategory( c.ID.ToString(), c.Name.Value!, channels .Where(c2 => c2.ParentID.IsDefined(out var parentId) && parentId == c.ID) .Select(ToChannel) )); var roles = roleCache .GuildRoles(guildId) .OrderByDescending(r => r.Position) .Select(r => new GuildRole( r.ID.ToString(), r.Name, r.Position, r.Colour.ToPrettyString() )); return Ok( new GuildResponse( guild.Id, guild.Name, guild.IconUrl, categories, channelsWithoutCategories, roles, guildConfig.IgnoredChannels, guildConfig.IgnoredRoles, guildConfig.Messages, guildConfig.Channels, guildConfig.KeyRoles ) ); } private static GuildChannel ToChannel(IChannel channel) => new( channel.ID.ToString(), channel.Name.Value!, channel.Type is ChannelType.GuildText, channel.Type is ChannelType.GuildText or ChannelType.GuildAnnouncement or ChannelType.GuildForum or ChannelType.GuildMedia or ChannelType.GuildVoice ); private record GuildResponse( string Id, string Name, string IconUrl, IEnumerable Categories, IEnumerable ChannelsWithoutCategory, IEnumerable Roles, List IgnoredChannels, List IgnoredRoles, Database.Models.Guild.MessageConfig Messages, Database.Models.Guild.ChannelConfig Channels, List KeyRoles ); private record GuildCategory(string Id, string Name, IEnumerable Channels); private record GuildChannel(string Id, string Name, bool CanLogTo, bool CanRedirectFrom); private record GuildRole(string Id, string Name, int Position, string Colour); [Authorize] [HttpPatch] [ProducesResponseType(statusCode: StatusCodes.Status200OK)] public async Task PatchGuildAsync(string id, [FromBody] ChannelRequest req) { var (guildId, _) = await ParseGuildAsync(id); var guildChannels = channelCache .GuildChannels(guildId) .Where(c => c.Type is ChannelType.GuildText) .ToList(); var guildConfig = await guildRepository.GetAsync(guildId); // i love repeating myself wheeeeee if ( req.GuildUpdate == null || (req.GuildUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.GuildUpdate)) ) guildConfig.Channels.GuildUpdate = req.GuildUpdate ?? 0; if ( req.GuildEmojisUpdate == null || ( req.GuildEmojisUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.GuildEmojisUpdate) ) ) guildConfig.Channels.GuildEmojisUpdate = req.GuildEmojisUpdate ?? 0; if ( req.GuildRoleCreate == null || ( req.GuildRoleCreate == 0 || guildChannels.Any(c => c.ID.Value == req.GuildRoleCreate) ) ) guildConfig.Channels.GuildRoleCreate = req.GuildRoleCreate ?? 0; if ( req.GuildRoleUpdate == null || ( req.GuildRoleUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.GuildRoleUpdate) ) ) guildConfig.Channels.GuildRoleUpdate = req.GuildRoleUpdate ?? 0; if ( req.GuildRoleDelete == null || ( req.GuildRoleDelete == 0 || guildChannels.Any(c => c.ID.Value == req.GuildRoleDelete) ) ) guildConfig.Channels.GuildRoleDelete = req.GuildRoleDelete ?? 0; if ( req.ChannelCreate == null || (req.ChannelCreate == 0 || guildChannels.Any(c => c.ID.Value == req.ChannelCreate)) ) guildConfig.Channels.ChannelCreate = req.ChannelCreate ?? 0; if ( req.ChannelUpdate == null || (req.ChannelUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.ChannelUpdate)) ) guildConfig.Channels.ChannelUpdate = req.ChannelUpdate ?? 0; if ( req.ChannelDelete == null || (req.ChannelDelete == 0 || guildChannels.Any(c => c.ID.Value == req.ChannelDelete)) ) guildConfig.Channels.ChannelDelete = req.ChannelDelete ?? 0; if ( req.GuildMemberAdd == null || (req.GuildMemberAdd == 0 || guildChannels.Any(c => c.ID.Value == req.GuildMemberAdd)) ) guildConfig.Channels.GuildMemberAdd = req.GuildMemberAdd ?? 0; if ( req.GuildMemberUpdate == null || ( req.GuildMemberUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.GuildMemberUpdate) ) ) guildConfig.Channels.GuildMemberUpdate = req.GuildMemberUpdate ?? 0; if ( req.GuildKeyRoleUpdate == null || ( req.GuildKeyRoleUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.GuildKeyRoleUpdate) ) ) guildConfig.Channels.GuildKeyRoleUpdate = req.GuildKeyRoleUpdate ?? 0; if ( req.GuildMemberNickUpdate == null || ( req.GuildMemberNickUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.GuildMemberNickUpdate) ) ) guildConfig.Channels.GuildMemberNickUpdate = req.GuildMemberNickUpdate ?? 0; if ( req.GuildMemberAvatarUpdate == null || ( req.GuildMemberAvatarUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.GuildMemberAvatarUpdate) ) ) guildConfig.Channels.GuildMemberAvatarUpdate = req.GuildMemberAvatarUpdate ?? 0; if ( req.GuildMemberTimeout == null || ( req.GuildMemberTimeout == 0 || guildChannels.Any(c => c.ID.Value == req.GuildMemberTimeout) ) ) guildConfig.Channels.GuildMemberTimeout = req.GuildMemberTimeout ?? 0; if ( req.GuildMemberRemove == null || ( req.GuildMemberRemove == 0 || guildChannels.Any(c => c.ID.Value == req.GuildMemberRemove) ) ) guildConfig.Channels.GuildMemberRemove = req.GuildMemberRemove ?? 0; if ( req.GuildMemberKick == null || ( req.GuildMemberKick == 0 || guildChannels.Any(c => c.ID.Value == req.GuildMemberKick) ) ) guildConfig.Channels.GuildMemberKick = req.GuildMemberKick ?? 0; if ( req.GuildBanAdd == null || (req.GuildBanAdd == 0 || guildChannels.Any(c => c.ID.Value == req.GuildBanAdd)) ) guildConfig.Channels.GuildBanAdd = req.GuildBanAdd ?? 0; if ( req.GuildBanRemove == null || (req.GuildBanRemove == 0 || guildChannels.Any(c => c.ID.Value == req.GuildBanRemove)) ) guildConfig.Channels.GuildBanRemove = req.GuildBanRemove ?? 0; if ( req.InviteCreate == null || (req.InviteCreate == 0 || guildChannels.Any(c => c.ID.Value == req.InviteCreate)) ) guildConfig.Channels.InviteCreate = req.InviteCreate ?? 0; if ( req.InviteDelete == null || (req.InviteDelete == 0 || guildChannels.Any(c => c.ID.Value == req.InviteDelete)) ) guildConfig.Channels.InviteDelete = req.InviteDelete ?? 0; if ( req.MessageUpdate == null || (req.MessageUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.MessageUpdate)) ) guildConfig.Channels.MessageUpdate = req.MessageUpdate ?? 0; if ( req.MessageDelete == null || (req.MessageDelete == 0 || guildChannels.Any(c => c.ID.Value == req.MessageDelete)) ) guildConfig.Channels.MessageDelete = req.MessageDelete ?? 0; if ( req.MessageDeleteBulk == null || ( req.MessageDeleteBulk == 0 || guildChannels.Any(c => c.ID.Value == req.MessageDeleteBulk) ) ) guildConfig.Channels.MessageDeleteBulk = req.MessageDeleteBulk ?? 0; await guildRepository.UpdateConfigAsync(guildId, guildConfig); return Ok(guildConfig.Channels); } public record ChannelRequest( ulong? GuildUpdate = null, ulong? GuildEmojisUpdate = null, ulong? GuildRoleCreate = null, ulong? GuildRoleUpdate = null, ulong? GuildRoleDelete = null, ulong? ChannelCreate = null, ulong? ChannelUpdate = null, ulong? ChannelDelete = null, ulong? GuildMemberAdd = null, ulong? GuildMemberUpdate = null, ulong? GuildKeyRoleUpdate = null, ulong? GuildMemberNickUpdate = null, ulong? GuildMemberAvatarUpdate = null, ulong? GuildMemberTimeout = null, ulong? GuildMemberRemove = null, ulong? GuildMemberKick = null, ulong? GuildBanAdd = null, ulong? GuildBanRemove = null, ulong? InviteCreate = null, ulong? InviteDelete = null, ulong? MessageUpdate = null, ulong? MessageDelete = null, ulong? MessageDeleteBulk = null ); }