// 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
);
}