// 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 System.Text; using System.Text.Json; using Catalogger.Backend.Api.Middleware; using Catalogger.Backend.Bot; using Catalogger.Backend.Extensions; using Catalogger.Backend.Services; using Dapper; using Microsoft.AspNetCore.Mvc; using Remora.Discord.API.Abstractions.Rest; using Remora.Discord.Extensions.Embeds; namespace Catalogger.Backend.Api; public partial class GuildsController { [HttpPost("leave")] public async Task LeaveGuildAsync(string id, [FromBody] LeaveGuildRequest req) { var (guildId, guild) = await ParseGuildAsync(id); if (guild.Name != req.Name) { throw new ApiError( HttpStatusCode.BadRequest, ErrorCode.BadRequest, "You must spell the server name correctly." ); } var guildConfig = await guildRepository.GetAsync(guildId); var export = await ToExport(guildConfig); var logChannelId = webhookExecutor.GetLogChannel(guildConfig, LogChannelType.GuildUpdate) ?? webhookExecutor.GetLogChannel(guildConfig, LogChannelType.GuildMemberRemove); if (logChannelId != null) { var currentUser = await discordRequestService.GetMeAsync(CurrentToken); var embed = new EmbedBuilder() .WithTitle("Catalogger is leaving this server") .WithDescription( $""" A moderator in this server ({currentUser.Tag}, <@{currentUser.Id}>) requested that Catalogger leave. All data related to this server will be deleted. A backup of this server's configuration is attached to this message, in case you want to use the bot again later. """ ) .WithColour(DiscordUtils.Red) .WithCurrentTimestamp() .Build() .GetOrThrow(); var exportData = JsonSerializer.Serialize(export, JsonUtils.ApiJsonOptions); var file = new FileData( "config-backup.json", new MemoryStream(Encoding.UTF8.GetBytes(exportData)) ); await webhookExecutor.SendLogAsync(logChannelId.Value, [embed], [file]); } else { _logger.Information( "Can't send guild {GuildId} a heads up when leaving as neither the guild update nor member remove events are logged", guildId ); } var leaveRes = await userApi.LeaveGuildAsync(guildId); if (!leaveRes.IsSuccess) { _logger.Error("Couldn't leave guild {GuildId}: {Error}", guildId, leaveRes.Error); throw new ApiError( HttpStatusCode.InternalServerError, ErrorCode.InternalServerError, "There was an error making Catalogger leave the server." ); } await using var tx = await dbConn.BeginTransactionAsync(); var inviteCount = await dbConn.ExecuteAsync( "delete from invites where guild_id = @GuildId", new { GuildId = guildId.Value }, tx ); var watchlistCount = await dbConn.ExecuteAsync( "delete from watchlists where guild_id = @GuildId", new { GuildId = guildId.Value }, tx ); var messageCount = await dbConn.ExecuteAsync( "delete from messages where guild_id = @GuildId", new { GuildId = guildId.Value }, tx ); await dbConn.ExecuteAsync( "delete from guilds where id = @GuildId", new { GuildId = guildId.Value }, tx ); await tx.CommitAsync(); _logger.Information( "Deleted {InviteCount} invites, {WatchlistCount} watchlist entries, and {MessageCount} messages for guild {GuildId}", inviteCount, watchlistCount, messageCount, guildId ); _logger.Information("Left guild {GuildId} and removed all data for it", guildId); return Ok(export); } public record LeaveGuildRequest(string Name); }