mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
187 lines
6.4 KiB
C#
187 lines
6.4 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Threading.Channels;
|
|
using System.Threading.Tasks;
|
|
using Discord;
|
|
using Discord.Net;
|
|
using Discord.WebSocket;
|
|
using LinqToDB;
|
|
using Microsoft.Extensions.Caching.Memory;
|
|
using NadekoBot.Common.Collections;
|
|
using NadekoBot.Common.ModuleBehaviors;
|
|
using NadekoBot.Extensions;
|
|
using NadekoBot.Services;
|
|
using Serilog;
|
|
|
|
namespace NadekoBot.Modules.Administration.Services
|
|
{
|
|
public sealed class ImageOnlyChannelService : IEarlyBehavior
|
|
{
|
|
private readonly IMemoryCache _ticketCache;
|
|
private readonly DiscordSocketClient _client;
|
|
private readonly DbService _db;
|
|
private readonly ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _enabledOn;
|
|
|
|
private Channel<IUserMessage> _deleteQueue = Channel.CreateBounded<IUserMessage>(new BoundedChannelOptions(100)
|
|
{
|
|
FullMode = BoundedChannelFullMode.DropOldest,
|
|
SingleReader = true,
|
|
SingleWriter = false,
|
|
});
|
|
|
|
|
|
public ImageOnlyChannelService(IMemoryCache ticketCache, DiscordSocketClient client, DbService db)
|
|
{
|
|
_ticketCache = ticketCache;
|
|
_client = client;
|
|
_db = db;
|
|
|
|
var uow = _db.GetDbContext();
|
|
_enabledOn = uow.ImageOnlyChannels
|
|
.ToList()
|
|
.GroupBy(x => x.GuildId)
|
|
.ToDictionary(x => x.Key, x => new ConcurrentHashSet<ulong>(x.Select(x => x.ChannelId)))
|
|
.ToConcurrent();
|
|
|
|
_ = Task.Run(DeleteQueueRunner);
|
|
|
|
_client.ChannelDestroyed += ClientOnChannelDestroyed;
|
|
}
|
|
|
|
private Task ClientOnChannelDestroyed(SocketChannel ch)
|
|
{
|
|
if (ch is not IGuildChannel gch)
|
|
return Task.CompletedTask;
|
|
|
|
if (_enabledOn.TryGetValue(gch.GuildId, out var channels) && channels.TryRemove(ch.Id))
|
|
ToggleImageOnlyChannel(gch.GuildId, ch.Id, true);
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private async Task DeleteQueueRunner()
|
|
{
|
|
while (true)
|
|
{
|
|
var toDelete = await _deleteQueue.Reader.ReadAsync();
|
|
try
|
|
{
|
|
await toDelete.DeleteAsync();
|
|
await Task.Delay(1000);
|
|
}
|
|
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden)
|
|
{
|
|
// disable if bot can't delete messages in the channel
|
|
ToggleImageOnlyChannel(((ITextChannel)toDelete.Channel).GuildId, toDelete.Channel.Id, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool ToggleImageOnlyChannel(ulong guildId, ulong channelId, bool forceDisable = false)
|
|
{
|
|
var newState = false;
|
|
using var uow = _db.GetDbContext();
|
|
if (forceDisable
|
|
|| (_enabledOn.TryGetValue(guildId, out var channels)
|
|
&& channels.TryRemove(channelId)))
|
|
{
|
|
uow.ImageOnlyChannels.Delete(x => x.ChannelId == channelId);
|
|
}
|
|
else
|
|
{
|
|
uow.ImageOnlyChannels.Add(new()
|
|
{
|
|
GuildId = guildId,
|
|
ChannelId = channelId
|
|
});
|
|
|
|
channels = _enabledOn.GetOrAdd(guildId, new ConcurrentHashSet<ulong>());
|
|
channels.Add(channelId);
|
|
newState = true;
|
|
}
|
|
|
|
uow.SaveChanges();
|
|
return newState;
|
|
}
|
|
|
|
public async Task<bool> RunBehavior(IGuild guild, IUserMessage msg)
|
|
{
|
|
if (msg.Channel is not ITextChannel tch)
|
|
return false;
|
|
|
|
if (msg.Attachments.Any(x => x is { Height: > 0, Width: > 0 }))
|
|
return false;
|
|
|
|
if (!_enabledOn.TryGetValue(tch.GuildId, out var chs)
|
|
|| !chs.Contains(msg.Channel.Id))
|
|
return false;
|
|
|
|
var user = await tch.Guild.GetUserAsync(msg.Author.Id)
|
|
?? await _client.Rest.GetGuildUserAsync(tch.GuildId, msg.Author.Id);
|
|
|
|
if (user is null)
|
|
return false;
|
|
|
|
// ignore owner and admin
|
|
if (user.Id == tch.Guild.OwnerId || user.GuildPermissions.Administrator)
|
|
{
|
|
Log.Information("Image-Only: Ignoring owner od admin ({ChannelId})", msg.Channel.Id);
|
|
return false;
|
|
}
|
|
|
|
// ignore users higher in hierarchy
|
|
var botUser = await tch.Guild.GetCurrentUserAsync();
|
|
if (user.GetRoles().Max(x => x.Position) >= botUser.GetRoles().Max(x => x.Position))
|
|
return false;
|
|
|
|
// can't modify channel perms if not admin apparently
|
|
if (!botUser.GuildPermissions.ManageGuild)
|
|
{
|
|
ToggleImageOnlyChannel( tch.GuildId, tch.Id, true);;
|
|
return false;
|
|
}
|
|
|
|
var shouldLock = AddUserTicket(tch.GuildId, msg.Author.Id);
|
|
if (shouldLock)
|
|
{
|
|
await tch.AddPermissionOverwriteAsync(msg.Author, new(sendMessages: PermValue.Deny));
|
|
Log.Warning("Image-Only: User {User} [{UserId}] has been banned from typing in the channel [{ChannelId}]",
|
|
msg.Author,
|
|
msg.Author.Id,
|
|
msg.Channel.Id);
|
|
}
|
|
|
|
try
|
|
{
|
|
await _deleteQueue.Writer.WriteAsync(msg);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, "Error deleting message {MessageId} in image-only channel {ChannelId}.",
|
|
msg.Id,
|
|
tch.Id);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool AddUserTicket(ulong guildId, ulong userId)
|
|
{
|
|
var old = _ticketCache.GetOrCreate($"{guildId}_{userId}", entry =>
|
|
{
|
|
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1);
|
|
return 0;
|
|
});
|
|
|
|
_ticketCache.Set($"{guildId}_{userId}", ++old);
|
|
|
|
// if this is the third time that the user posts a
|
|
// non image in an image-only channel on this server
|
|
return old > 2;
|
|
}
|
|
|
|
public int Priority { get; } = 0;
|
|
}
|
|
} |