mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-11 17:58:26 -04:00
Added .imageonlychannel / .imageonly to prevent users from posting anything but images in the channel
This commit is contained in:
@@ -1,9 +1,7 @@
|
||||
using System;
|
||||
using Discord;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Common.Replacements;
|
||||
using NadekoBot.Services;
|
||||
@@ -24,14 +22,11 @@ namespace NadekoBot.Modules.Administration.Services
|
||||
|
||||
private readonly DbService _db;
|
||||
private readonly ILogCommandService _logService;
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
|
||||
public AdministrationService(Bot bot, CommandHandler cmdHandler, DbService db,
|
||||
ILogCommandService logService, IEmbedBuilderService eb)
|
||||
public AdministrationService(Bot bot, CommandHandler cmdHandler, DbService db, ILogCommandService logService)
|
||||
{
|
||||
_db = db;
|
||||
_logService = logService;
|
||||
_eb = eb;
|
||||
|
||||
DeleteMessagesOnCommand = new ConcurrentHashSet<ulong>(bot.AllGuildConfigs
|
||||
.Where(g => g.DeleteMessageOnCommand)
|
||||
|
@@ -0,0 +1,187 @@
|
||||
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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user