323 lines
12 KiB
C#
323 lines
12 KiB
C#
|
|
// 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 <https://www.gnu.org/licenses/>.
|
||
|
|
|
||
|
|
using System.Net;
|
||
|
|
using Catalogger.Backend.Api.Middleware;
|
||
|
|
using Catalogger.Backend.Cache.InMemoryCache;
|
||
|
|
using Catalogger.Backend.Database;
|
||
|
|
using Catalogger.Backend.Database.Queries;
|
||
|
|
using Microsoft.AspNetCore.Mvc;
|
||
|
|
using Remora.Discord.API;
|
||
|
|
using Remora.Discord.API.Abstractions.Objects;
|
||
|
|
using Remora.Rest.Core;
|
||
|
|
|
||
|
|
namespace Catalogger.Backend.Api;
|
||
|
|
|
||
|
|
[Route("/api/guilds/{id}")]
|
||
|
|
public class GuildsController(
|
||
|
|
Config config,
|
||
|
|
DatabaseContext db,
|
||
|
|
ChannelCache channelCache,
|
||
|
|
DiscordRequestService discordRequestService
|
||
|
|
) : ApiControllerBase
|
||
|
|
{
|
||
|
|
public IActionResult AddGuild(ulong id) =>
|
||
|
|
Redirect(
|
||
|
|
$"https://discord.com/oauth2/authorize?client_id={config.Discord.ApplicationId}"
|
||
|
|
+ "&permissions=537250993&scope=bot%20applications.commands"
|
||
|
|
+ $"&guild_id={id}"
|
||
|
|
);
|
||
|
|
|
||
|
|
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<IActionResult> GetGuildAsync(string id)
|
||
|
|
{
|
||
|
|
var (guildId, guild) = await ParseGuildAsync(id);
|
||
|
|
|
||
|
|
var guildConfig = await db.GetGuildAsync(guildId.Value);
|
||
|
|
|
||
|
|
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)
|
||
|
|
));
|
||
|
|
|
||
|
|
return Ok(
|
||
|
|
new GuildResponse(
|
||
|
|
guild.Id,
|
||
|
|
guild.Name,
|
||
|
|
guild.IconUrl,
|
||
|
|
categories,
|
||
|
|
channelsWithoutCategories,
|
||
|
|
guildConfig.Channels
|
||
|
|
)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
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<GuildCategory> Categories,
|
||
|
|
IEnumerable<GuildChannel> ChannelsWithoutCategory,
|
||
|
|
Database.Models.Guild.ChannelConfig Config
|
||
|
|
);
|
||
|
|
|
||
|
|
private record GuildCategory(string Id, string Name, IEnumerable<GuildChannel> Channels);
|
||
|
|
|
||
|
|
private record GuildChannel(string Id, string Name, bool CanLogTo, bool CanRedirectFrom);
|
||
|
|
|
||
|
|
[Authorize]
|
||
|
|
[HttpPatch]
|
||
|
|
[ProducesResponseType<Database.Models.Guild.ChannelConfig>(statusCode: StatusCodes.Status200OK)]
|
||
|
|
public async Task<IActionResult> PatchGuildAsync(string id, [FromBody] ChannelRequest req)
|
||
|
|
{
|
||
|
|
var (guildId, guild) = await ParseGuildAsync(id);
|
||
|
|
var guildChannels = channelCache
|
||
|
|
.GuildChannels(guildId)
|
||
|
|
.Where(c => c.Type is ChannelType.GuildText)
|
||
|
|
.ToList();
|
||
|
|
var guildConfig = await db.GetGuildAsync(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.Value;
|
||
|
|
if (
|
||
|
|
req.GuildEmojisUpdate != null
|
||
|
|
&& (
|
||
|
|
req.GuildEmojisUpdate == 0
|
||
|
|
|| guildChannels.Any(c => c.ID.Value == req.GuildEmojisUpdate)
|
||
|
|
)
|
||
|
|
)
|
||
|
|
guildConfig.Channels.GuildEmojisUpdate = req.GuildEmojisUpdate.Value;
|
||
|
|
if (
|
||
|
|
req.GuildRoleCreate != null
|
||
|
|
&& (
|
||
|
|
req.GuildRoleCreate == 0
|
||
|
|
|| guildChannels.Any(c => c.ID.Value == req.GuildRoleCreate)
|
||
|
|
)
|
||
|
|
)
|
||
|
|
guildConfig.Channels.GuildRoleCreate = req.GuildRoleCreate.Value;
|
||
|
|
if (
|
||
|
|
req.GuildRoleUpdate != null
|
||
|
|
&& (
|
||
|
|
req.GuildRoleUpdate == 0
|
||
|
|
|| guildChannels.Any(c => c.ID.Value == req.GuildRoleUpdate)
|
||
|
|
)
|
||
|
|
)
|
||
|
|
guildConfig.Channels.GuildRoleUpdate = req.GuildRoleUpdate.Value;
|
||
|
|
if (
|
||
|
|
req.GuildRoleDelete != null
|
||
|
|
&& (
|
||
|
|
req.GuildRoleDelete == 0
|
||
|
|
|| guildChannels.Any(c => c.ID.Value == req.GuildRoleDelete)
|
||
|
|
)
|
||
|
|
)
|
||
|
|
guildConfig.Channels.GuildRoleDelete = req.GuildRoleDelete.Value;
|
||
|
|
if (
|
||
|
|
req.ChannelCreate != null
|
||
|
|
&& (req.ChannelCreate == 0 || guildChannels.Any(c => c.ID.Value == req.ChannelCreate))
|
||
|
|
)
|
||
|
|
guildConfig.Channels.ChannelCreate = req.ChannelCreate.Value;
|
||
|
|
if (
|
||
|
|
req.ChannelUpdate != null
|
||
|
|
&& (req.ChannelUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.ChannelUpdate))
|
||
|
|
)
|
||
|
|
guildConfig.Channels.ChannelUpdate = req.ChannelUpdate.Value;
|
||
|
|
if (
|
||
|
|
req.ChannelDelete != null
|
||
|
|
&& (req.ChannelDelete == 0 || guildChannels.Any(c => c.ID.Value == req.ChannelDelete))
|
||
|
|
)
|
||
|
|
guildConfig.Channels.ChannelDelete = req.ChannelDelete.Value;
|
||
|
|
if (
|
||
|
|
req.GuildMemberAdd != null
|
||
|
|
&& (req.GuildMemberAdd == 0 || guildChannels.Any(c => c.ID.Value == req.GuildMemberAdd))
|
||
|
|
)
|
||
|
|
guildConfig.Channels.GuildMemberAdd = req.GuildMemberAdd.Value;
|
||
|
|
if (
|
||
|
|
req.GuildMemberUpdate != null
|
||
|
|
&& (
|
||
|
|
req.GuildMemberUpdate == 0
|
||
|
|
|| guildChannels.Any(c => c.ID.Value == req.GuildMemberUpdate)
|
||
|
|
)
|
||
|
|
)
|
||
|
|
guildConfig.Channels.GuildMemberUpdate = req.GuildMemberUpdate.Value;
|
||
|
|
if (
|
||
|
|
req.GuildKeyRoleUpdate != null
|
||
|
|
&& (
|
||
|
|
req.GuildKeyRoleUpdate == 0
|
||
|
|
|| guildChannels.Any(c => c.ID.Value == req.GuildKeyRoleUpdate)
|
||
|
|
)
|
||
|
|
)
|
||
|
|
guildConfig.Channels.GuildKeyRoleUpdate = req.GuildKeyRoleUpdate.Value;
|
||
|
|
if (
|
||
|
|
req.GuildMemberNickUpdate != null
|
||
|
|
&& (
|
||
|
|
req.GuildMemberNickUpdate == 0
|
||
|
|
|| guildChannels.Any(c => c.ID.Value == req.GuildMemberNickUpdate)
|
||
|
|
)
|
||
|
|
)
|
||
|
|
guildConfig.Channels.GuildMemberNickUpdate = req.GuildMemberNickUpdate.Value;
|
||
|
|
if (
|
||
|
|
req.GuildMemberAvatarUpdate != null
|
||
|
|
&& (
|
||
|
|
req.GuildMemberAvatarUpdate == 0
|
||
|
|
|| guildChannels.Any(c => c.ID.Value == req.GuildMemberAvatarUpdate)
|
||
|
|
)
|
||
|
|
)
|
||
|
|
guildConfig.Channels.GuildMemberAvatarUpdate = req.GuildMemberAvatarUpdate.Value;
|
||
|
|
if (
|
||
|
|
req.GuildMemberTimeout != null
|
||
|
|
&& (
|
||
|
|
req.GuildMemberTimeout == 0
|
||
|
|
|| guildChannels.Any(c => c.ID.Value == req.GuildMemberTimeout)
|
||
|
|
)
|
||
|
|
)
|
||
|
|
guildConfig.Channels.GuildMemberTimeout = req.GuildMemberTimeout.Value;
|
||
|
|
if (
|
||
|
|
req.GuildMemberRemove != null
|
||
|
|
&& (
|
||
|
|
req.GuildMemberRemove == 0
|
||
|
|
|| guildChannels.Any(c => c.ID.Value == req.GuildMemberRemove)
|
||
|
|
)
|
||
|
|
)
|
||
|
|
guildConfig.Channels.GuildMemberRemove = req.GuildMemberRemove.Value;
|
||
|
|
if (
|
||
|
|
req.GuildMemberKick != null
|
||
|
|
&& (
|
||
|
|
req.GuildMemberKick == 0
|
||
|
|
|| guildChannels.Any(c => c.ID.Value == req.GuildMemberKick)
|
||
|
|
)
|
||
|
|
)
|
||
|
|
guildConfig.Channels.GuildMemberKick = req.GuildMemberKick.Value;
|
||
|
|
if (
|
||
|
|
req.GuildBanAdd != null
|
||
|
|
&& (req.GuildBanAdd == 0 || guildChannels.Any(c => c.ID.Value == req.GuildBanAdd))
|
||
|
|
)
|
||
|
|
guildConfig.Channels.GuildBanAdd = req.GuildBanAdd.Value;
|
||
|
|
if (
|
||
|
|
req.GuildBanRemove != null
|
||
|
|
&& (req.GuildBanRemove == 0 || guildChannels.Any(c => c.ID.Value == req.GuildBanRemove))
|
||
|
|
)
|
||
|
|
guildConfig.Channels.GuildBanRemove = req.GuildBanRemove.Value;
|
||
|
|
if (
|
||
|
|
req.InviteCreate != null
|
||
|
|
&& (req.InviteCreate == 0 || guildChannels.Any(c => c.ID.Value == req.InviteCreate))
|
||
|
|
)
|
||
|
|
guildConfig.Channels.InviteCreate = req.InviteCreate.Value;
|
||
|
|
if (
|
||
|
|
req.InviteDelete != null
|
||
|
|
&& (req.InviteDelete == 0 || guildChannels.Any(c => c.ID.Value == req.InviteDelete))
|
||
|
|
)
|
||
|
|
guildConfig.Channels.InviteDelete = req.InviteDelete.Value;
|
||
|
|
if (
|
||
|
|
req.MessageUpdate != null
|
||
|
|
&& (req.MessageUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.MessageUpdate))
|
||
|
|
)
|
||
|
|
guildConfig.Channels.MessageUpdate = req.MessageUpdate.Value;
|
||
|
|
if (
|
||
|
|
req.MessageDelete != null
|
||
|
|
&& (req.MessageDelete == 0 || guildChannels.Any(c => c.ID.Value == req.MessageDelete))
|
||
|
|
)
|
||
|
|
guildConfig.Channels.MessageDelete = req.MessageDelete.Value;
|
||
|
|
if (
|
||
|
|
req.MessageDeleteBulk != null
|
||
|
|
&& (
|
||
|
|
req.MessageDeleteBulk == 0
|
||
|
|
|| guildChannels.Any(c => c.ID.Value == req.MessageDeleteBulk)
|
||
|
|
)
|
||
|
|
)
|
||
|
|
guildConfig.Channels.MessageDeleteBulk = req.MessageDeleteBulk.Value;
|
||
|
|
|
||
|
|
db.Update(guildConfig);
|
||
|
|
await db.SaveChangesAsync();
|
||
|
|
|
||
|
|
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
|
||
|
|
);
|
||
|
|
}
|