mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-11 01:38:27 -04:00
Restructured the project structure back to the way it was, there's no reasonable way to split the modules
This commit is contained in:
@@ -0,0 +1,288 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Common.TypeReaders.Models;
|
||||
using NadekoBot.Modules.Administration.Services;
|
||||
using Nadeko.Bot.Db.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration;
|
||||
|
||||
public partial class Administration
|
||||
{
|
||||
[Group]
|
||||
public partial class ProtectionCommands : NadekoModule<ProtectionService>
|
||||
{
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task AntiAlt()
|
||||
{
|
||||
if (await _service.TryStopAntiAlt(ctx.Guild.Id))
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.prot_disable("Anti-Alt"));
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.protection_not_running("Anti-Alt"));
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task AntiAlt(
|
||||
StoopidTime minAge,
|
||||
PunishmentAction action,
|
||||
[Leftover] StoopidTime punishTime = null)
|
||||
{
|
||||
var minAgeMinutes = (int)minAge.Time.TotalMinutes;
|
||||
var punishTimeMinutes = (int?)punishTime?.Time.TotalMinutes ?? 0;
|
||||
|
||||
if (minAgeMinutes < 1 || punishTimeMinutes < 0)
|
||||
return;
|
||||
|
||||
var minutes = (int?)punishTime?.Time.TotalMinutes ?? 0;
|
||||
if (action is PunishmentAction.TimeOut && minutes < 1)
|
||||
minutes = 1;
|
||||
|
||||
await _service.StartAntiAltAsync(ctx.Guild.Id,
|
||||
minAgeMinutes,
|
||||
action,
|
||||
minutes);
|
||||
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task AntiAlt(StoopidTime minAge, PunishmentAction action, [Leftover] IRole role)
|
||||
{
|
||||
var minAgeMinutes = (int)minAge.Time.TotalMinutes;
|
||||
|
||||
if (minAgeMinutes < 1)
|
||||
return;
|
||||
|
||||
if (action == PunishmentAction.TimeOut)
|
||||
return;
|
||||
|
||||
await _service.StartAntiAltAsync(ctx.Guild.Id, minAgeMinutes, action, roleId: role.Id);
|
||||
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public Task AntiRaid()
|
||||
{
|
||||
if (_service.TryStopAntiRaid(ctx.Guild.Id))
|
||||
return ReplyConfirmLocalizedAsync(strs.prot_disable("Anti-Raid"));
|
||||
return ReplyPendingLocalizedAsync(strs.protection_not_running("Anti-Raid"));
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[Priority(1)]
|
||||
public Task AntiRaid(
|
||||
int userThreshold,
|
||||
int seconds,
|
||||
PunishmentAction action,
|
||||
[Leftover] StoopidTime punishTime)
|
||||
=> InternalAntiRaid(userThreshold, seconds, action, punishTime);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[Priority(2)]
|
||||
public Task AntiRaid(int userThreshold, int seconds, PunishmentAction action)
|
||||
=> InternalAntiRaid(userThreshold, seconds, action);
|
||||
|
||||
private async Task InternalAntiRaid(
|
||||
int userThreshold,
|
||||
int seconds = 10,
|
||||
PunishmentAction action = PunishmentAction.Mute,
|
||||
StoopidTime punishTime = null)
|
||||
{
|
||||
if (action == PunishmentAction.AddRole)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.punishment_unsupported(action));
|
||||
return;
|
||||
}
|
||||
|
||||
if (userThreshold is < 2 or > 30)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.raid_cnt(2, 30));
|
||||
return;
|
||||
}
|
||||
|
||||
if (seconds is < 2 or > 300)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.raid_time(2, 300));
|
||||
return;
|
||||
}
|
||||
|
||||
if (punishTime is not null)
|
||||
{
|
||||
if (!_service.IsDurationAllowed(action))
|
||||
await ReplyErrorLocalizedAsync(strs.prot_cant_use_time);
|
||||
}
|
||||
|
||||
var time = (int?)punishTime?.Time.TotalMinutes ?? 0;
|
||||
if (time is < 0 or > 60 * 24)
|
||||
return;
|
||||
|
||||
if(action is PunishmentAction.TimeOut && time < 1)
|
||||
return;
|
||||
|
||||
var stats = await _service.StartAntiRaidAsync(ctx.Guild.Id, userThreshold, seconds, action, time);
|
||||
|
||||
if (stats is null)
|
||||
return;
|
||||
|
||||
await SendConfirmAsync(GetText(strs.prot_enable("Anti-Raid")),
|
||||
$"{ctx.User.Mention} {GetAntiRaidString(stats)}");
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public Task AntiSpam()
|
||||
{
|
||||
if (_service.TryStopAntiSpam(ctx.Guild.Id))
|
||||
return ReplyConfirmLocalizedAsync(strs.prot_disable("Anti-Spam"));
|
||||
return ReplyPendingLocalizedAsync(strs.protection_not_running("Anti-Spam"));
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[Priority(0)]
|
||||
public Task AntiSpam(int messageCount, PunishmentAction action, [Leftover] IRole role)
|
||||
{
|
||||
if (action != PunishmentAction.AddRole)
|
||||
return Task.CompletedTask;
|
||||
|
||||
return InternalAntiSpam(messageCount, action, null, role);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[Priority(1)]
|
||||
public Task AntiSpam(int messageCount, PunishmentAction action, [Leftover] StoopidTime punishTime)
|
||||
=> InternalAntiSpam(messageCount, action, punishTime);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[Priority(2)]
|
||||
public Task AntiSpam(int messageCount, PunishmentAction action)
|
||||
=> InternalAntiSpam(messageCount, action);
|
||||
|
||||
private async Task InternalAntiSpam(
|
||||
int messageCount,
|
||||
PunishmentAction action,
|
||||
StoopidTime timeData = null,
|
||||
IRole role = null)
|
||||
{
|
||||
if (messageCount is < 2 or > 10)
|
||||
return;
|
||||
|
||||
if (timeData is not null)
|
||||
{
|
||||
if (!_service.IsDurationAllowed(action))
|
||||
await ReplyErrorLocalizedAsync(strs.prot_cant_use_time);
|
||||
}
|
||||
|
||||
var time = (int?)timeData?.Time.TotalMinutes ?? 0;
|
||||
if (time is < 0 or > 60 * 24)
|
||||
return;
|
||||
|
||||
if (action is PunishmentAction.TimeOut && time < 1)
|
||||
return;
|
||||
|
||||
var stats = await _service.StartAntiSpamAsync(ctx.Guild.Id, messageCount, action, time, role?.Id);
|
||||
|
||||
await SendConfirmAsync(GetText(strs.prot_enable("Anti-Spam")),
|
||||
$"{ctx.User.Mention} {GetAntiSpamString(stats)}");
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task AntispamIgnore()
|
||||
{
|
||||
var added = await _service.AntiSpamIgnoreAsync(ctx.Guild.Id, ctx.Channel.Id);
|
||||
|
||||
if (added is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.protection_not_running("Anti-Spam"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (added.Value)
|
||||
await ReplyConfirmLocalizedAsync(strs.spam_ignore("Anti-Spam"));
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.spam_not_ignore("Anti-Spam"));
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task AntiList()
|
||||
{
|
||||
var (spam, raid, alt) = _service.GetAntiStats(ctx.Guild.Id);
|
||||
|
||||
if (spam is null && raid is null && alt is null)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.prot_none);
|
||||
return;
|
||||
}
|
||||
|
||||
var embed = _eb.Create().WithOkColor().WithTitle(GetText(strs.prot_active));
|
||||
|
||||
if (spam is not null)
|
||||
embed.AddField("Anti-Spam", GetAntiSpamString(spam).TrimTo(1024), true);
|
||||
|
||||
if (raid is not null)
|
||||
embed.AddField("Anti-Raid", GetAntiRaidString(raid).TrimTo(1024), true);
|
||||
|
||||
if (alt is not null)
|
||||
embed.AddField("Anti-Alt", GetAntiAltString(alt), true);
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
private string GetAntiAltString(AntiAltStats alt)
|
||||
=> GetText(strs.anti_alt_status(Format.Bold(alt.MinAge.ToString(@"dd\d\ hh\h\ mm\m\ ")),
|
||||
Format.Bold(alt.Action.ToString()),
|
||||
Format.Bold(alt.Counter.ToString())));
|
||||
|
||||
private string GetAntiSpamString(AntiSpamStats stats)
|
||||
{
|
||||
var settings = stats.AntiSpamSettings;
|
||||
var ignoredString = string.Join(", ", settings.IgnoredChannels.Select(c => $"<#{c.ChannelId}>"));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ignoredString))
|
||||
ignoredString = "none";
|
||||
|
||||
var add = string.Empty;
|
||||
if (settings.MuteTime > 0)
|
||||
add = $" ({TimeSpan.FromMinutes(settings.MuteTime):hh\\hmm\\m})";
|
||||
|
||||
return GetText(strs.spam_stats(Format.Bold(settings.MessageThreshold.ToString()),
|
||||
Format.Bold(settings.Action + add),
|
||||
ignoredString));
|
||||
}
|
||||
|
||||
private string GetAntiRaidString(AntiRaidStats stats)
|
||||
{
|
||||
var actionString = Format.Bold(stats.AntiRaidSettings.Action.ToString());
|
||||
|
||||
if (stats.AntiRaidSettings.PunishDuration > 0)
|
||||
actionString += $" **({TimeSpan.FromMinutes(stats.AntiRaidSettings.PunishDuration):hh\\hmm\\m})**";
|
||||
|
||||
return GetText(strs.raid_stats(Format.Bold(stats.AntiRaidSettings.UserThreshold.ToString()),
|
||||
Format.Bold(stats.AntiRaidSettings.Seconds.ToString()),
|
||||
actionString));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,499 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Db;
|
||||
using Nadeko.Bot.Db.Models;
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
|
||||
public class ProtectionService : INService
|
||||
{
|
||||
public event Func<PunishmentAction, ProtectionType, IGuildUser[], Task> OnAntiProtectionTriggered = delegate
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, AntiRaidStats> _antiRaidGuilds = new();
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, AntiSpamStats> _antiSpamGuilds = new();
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, AntiAltStats> _antiAltGuilds = new();
|
||||
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly MuteService _mute;
|
||||
private readonly DbService _db;
|
||||
private readonly UserPunishService _punishService;
|
||||
|
||||
private readonly Channel<PunishQueueItem> _punishUserQueue =
|
||||
Channel.CreateUnbounded<PunishQueueItem>(new()
|
||||
{
|
||||
SingleReader = true,
|
||||
SingleWriter = false
|
||||
});
|
||||
|
||||
public ProtectionService(
|
||||
DiscordSocketClient client,
|
||||
IBot bot,
|
||||
MuteService mute,
|
||||
DbService db,
|
||||
UserPunishService punishService)
|
||||
{
|
||||
_client = client;
|
||||
_mute = mute;
|
||||
_db = db;
|
||||
_punishService = punishService;
|
||||
|
||||
var ids = client.GetGuildIds();
|
||||
using (var uow = db.GetDbContext())
|
||||
{
|
||||
var configs = uow.Set<GuildConfig>()
|
||||
.AsQueryable()
|
||||
.Include(x => x.AntiRaidSetting)
|
||||
.Include(x => x.AntiSpamSetting)
|
||||
.ThenInclude(x => x.IgnoredChannels)
|
||||
.Include(x => x.AntiAltSetting)
|
||||
.Where(x => ids.Contains(x.GuildId))
|
||||
.ToList();
|
||||
|
||||
foreach (var gc in configs)
|
||||
Initialize(gc);
|
||||
}
|
||||
|
||||
_client.MessageReceived += HandleAntiSpam;
|
||||
_client.UserJoined += HandleUserJoined;
|
||||
|
||||
bot.JoinedGuild += _bot_JoinedGuild;
|
||||
_client.LeftGuild += _client_LeftGuild;
|
||||
|
||||
_ = Task.Run(RunQueue);
|
||||
}
|
||||
|
||||
private async Task RunQueue()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var item = await _punishUserQueue.Reader.ReadAsync();
|
||||
|
||||
var muteTime = item.MuteTime;
|
||||
var gu = item.User;
|
||||
try
|
||||
{
|
||||
await _punishService.ApplyPunishment(gu.Guild,
|
||||
gu,
|
||||
_client.CurrentUser,
|
||||
item.Action,
|
||||
muteTime,
|
||||
item.RoleId,
|
||||
$"{item.Type} Protection");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error in punish queue: {Message}", ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Task _client_LeftGuild(SocketGuild guild)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
TryStopAntiRaid(guild.Id);
|
||||
TryStopAntiSpam(guild.Id);
|
||||
await TryStopAntiAlt(guild.Id);
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task _bot_JoinedGuild(GuildConfig gc)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var gcWithData = uow.GuildConfigsForId(gc.GuildId,
|
||||
set => set.Include(x => x.AntiRaidSetting)
|
||||
.Include(x => x.AntiAltSetting)
|
||||
.Include(x => x.AntiSpamSetting)
|
||||
.ThenInclude(x => x.IgnoredChannels));
|
||||
|
||||
Initialize(gcWithData);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void Initialize(GuildConfig gc)
|
||||
{
|
||||
var raid = gc.AntiRaidSetting;
|
||||
var spam = gc.AntiSpamSetting;
|
||||
|
||||
if (raid is not null)
|
||||
{
|
||||
var raidStats = new AntiRaidStats
|
||||
{
|
||||
AntiRaidSettings = raid
|
||||
};
|
||||
_antiRaidGuilds[gc.GuildId] = raidStats;
|
||||
}
|
||||
|
||||
if (spam is not null)
|
||||
{
|
||||
_antiSpamGuilds[gc.GuildId] = new()
|
||||
{
|
||||
AntiSpamSettings = spam
|
||||
};
|
||||
}
|
||||
|
||||
var alt = gc.AntiAltSetting;
|
||||
if (alt is not null)
|
||||
_antiAltGuilds[gc.GuildId] = new(alt);
|
||||
}
|
||||
|
||||
private Task HandleUserJoined(SocketGuildUser user)
|
||||
{
|
||||
if (user.IsBot)
|
||||
return Task.CompletedTask;
|
||||
|
||||
_antiRaidGuilds.TryGetValue(user.Guild.Id, out var maybeStats);
|
||||
_antiAltGuilds.TryGetValue(user.Guild.Id, out var maybeAlts);
|
||||
|
||||
if (maybeStats is null && maybeAlts is null)
|
||||
return Task.CompletedTask;
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
if (maybeAlts is { } alts)
|
||||
{
|
||||
if (user.CreatedAt != default)
|
||||
{
|
||||
var diff = DateTime.UtcNow - user.CreatedAt.UtcDateTime;
|
||||
if (diff < alts.MinAge)
|
||||
{
|
||||
alts.Increment();
|
||||
|
||||
await PunishUsers(alts.Action,
|
||||
ProtectionType.Alting,
|
||||
alts.ActionDurationMinutes,
|
||||
alts.RoleId,
|
||||
user);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (maybeStats is not { } stats || !stats.RaidUsers.Add(user))
|
||||
return;
|
||||
|
||||
++stats.UsersCount;
|
||||
|
||||
if (stats.UsersCount >= stats.AntiRaidSettings.UserThreshold)
|
||||
{
|
||||
var users = stats.RaidUsers.ToArray();
|
||||
stats.RaidUsers.Clear();
|
||||
var settings = stats.AntiRaidSettings;
|
||||
|
||||
await PunishUsers(settings.Action, ProtectionType.Raiding, settings.PunishDuration, null, users);
|
||||
}
|
||||
|
||||
await Task.Delay(1000 * stats.AntiRaidSettings.Seconds);
|
||||
|
||||
stats.RaidUsers.TryRemove(user);
|
||||
--stats.UsersCount;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task HandleAntiSpam(SocketMessage arg)
|
||||
{
|
||||
if (arg is not SocketUserMessage msg || msg.Author.IsBot)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (msg.Channel is not ITextChannel channel)
|
||||
return Task.CompletedTask;
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_antiSpamGuilds.TryGetValue(channel.Guild.Id, out var spamSettings)
|
||||
|| spamSettings.AntiSpamSettings.IgnoredChannels.Contains(new()
|
||||
{
|
||||
ChannelId = channel.Id
|
||||
}))
|
||||
return;
|
||||
|
||||
var stats = spamSettings.UserStats.AddOrUpdate(msg.Author.Id,
|
||||
_ => new(msg),
|
||||
(_, old) =>
|
||||
{
|
||||
old.ApplyNextMessage(msg);
|
||||
return old;
|
||||
});
|
||||
|
||||
if (stats.Count >= spamSettings.AntiSpamSettings.MessageThreshold)
|
||||
{
|
||||
if (spamSettings.UserStats.TryRemove(msg.Author.Id, out stats))
|
||||
{
|
||||
var settings = spamSettings.AntiSpamSettings;
|
||||
await PunishUsers(settings.Action,
|
||||
ProtectionType.Spamming,
|
||||
settings.MuteTime,
|
||||
settings.RoleId,
|
||||
(IGuildUser)msg.Author);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task PunishUsers(
|
||||
PunishmentAction action,
|
||||
ProtectionType pt,
|
||||
int muteTime,
|
||||
ulong? roleId,
|
||||
params IGuildUser[] gus)
|
||||
{
|
||||
Log.Information("[{PunishType}] - Punishing [{Count}] users with [{PunishAction}] in {GuildName} guild",
|
||||
pt,
|
||||
gus.Length,
|
||||
action,
|
||||
gus[0].Guild.Name);
|
||||
|
||||
foreach (var gu in gus)
|
||||
{
|
||||
await _punishUserQueue.Writer.WriteAsync(new()
|
||||
{
|
||||
Action = action,
|
||||
Type = pt,
|
||||
User = gu,
|
||||
MuteTime = muteTime,
|
||||
RoleId = roleId
|
||||
});
|
||||
}
|
||||
|
||||
_ = OnAntiProtectionTriggered(action, pt, gus);
|
||||
}
|
||||
|
||||
public async Task<AntiRaidStats> StartAntiRaidAsync(
|
||||
ulong guildId,
|
||||
int userThreshold,
|
||||
int seconds,
|
||||
PunishmentAction action,
|
||||
int minutesDuration)
|
||||
{
|
||||
var g = _client.GetGuild(guildId);
|
||||
await _mute.GetMuteRole(g);
|
||||
|
||||
if (action == PunishmentAction.AddRole)
|
||||
return null;
|
||||
|
||||
if (!IsDurationAllowed(action))
|
||||
minutesDuration = 0;
|
||||
|
||||
var stats = new AntiRaidStats
|
||||
{
|
||||
AntiRaidSettings = new()
|
||||
{
|
||||
Action = action,
|
||||
Seconds = seconds,
|
||||
UserThreshold = userThreshold,
|
||||
PunishDuration = minutesDuration
|
||||
}
|
||||
};
|
||||
|
||||
_antiRaidGuilds.AddOrUpdate(guildId, stats, (_, _) => stats);
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiRaidSetting));
|
||||
|
||||
gc.AntiRaidSetting = stats.AntiRaidSettings;
|
||||
await uow.SaveChangesAsync();
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public bool TryStopAntiRaid(ulong guildId)
|
||||
{
|
||||
if (_antiRaidGuilds.TryRemove(guildId, out _))
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiRaidSetting));
|
||||
|
||||
gc.AntiRaidSetting = null;
|
||||
uow.SaveChanges();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryStopAntiSpam(ulong guildId)
|
||||
{
|
||||
if (_antiSpamGuilds.TryRemove(guildId, out _))
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigsForId(guildId,
|
||||
set => set.Include(x => x.AntiSpamSetting).ThenInclude(x => x.IgnoredChannels));
|
||||
|
||||
gc.AntiSpamSetting = null;
|
||||
uow.SaveChanges();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<AntiSpamStats> StartAntiSpamAsync(
|
||||
ulong guildId,
|
||||
int messageCount,
|
||||
PunishmentAction action,
|
||||
int punishDurationMinutes,
|
||||
ulong? roleId)
|
||||
{
|
||||
var g = _client.GetGuild(guildId);
|
||||
await _mute.GetMuteRole(g);
|
||||
|
||||
if (!IsDurationAllowed(action))
|
||||
punishDurationMinutes = 0;
|
||||
|
||||
var stats = new AntiSpamStats
|
||||
{
|
||||
AntiSpamSettings = new()
|
||||
{
|
||||
Action = action,
|
||||
MessageThreshold = messageCount,
|
||||
MuteTime = punishDurationMinutes,
|
||||
RoleId = roleId
|
||||
}
|
||||
};
|
||||
|
||||
stats = _antiSpamGuilds.AddOrUpdate(guildId,
|
||||
stats,
|
||||
(_, old) =>
|
||||
{
|
||||
stats.AntiSpamSettings.IgnoredChannels = old.AntiSpamSettings.IgnoredChannels;
|
||||
return stats;
|
||||
});
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiSpamSetting));
|
||||
|
||||
if (gc.AntiSpamSetting is not null)
|
||||
{
|
||||
gc.AntiSpamSetting.Action = stats.AntiSpamSettings.Action;
|
||||
gc.AntiSpamSetting.MessageThreshold = stats.AntiSpamSettings.MessageThreshold;
|
||||
gc.AntiSpamSetting.MuteTime = stats.AntiSpamSettings.MuteTime;
|
||||
gc.AntiSpamSetting.RoleId = stats.AntiSpamSettings.RoleId;
|
||||
}
|
||||
else
|
||||
gc.AntiSpamSetting = stats.AntiSpamSettings;
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
return stats;
|
||||
}
|
||||
|
||||
public async Task<bool?> AntiSpamIgnoreAsync(ulong guildId, ulong channelId)
|
||||
{
|
||||
var obj = new AntiSpamIgnore
|
||||
{
|
||||
ChannelId = channelId
|
||||
};
|
||||
bool added;
|
||||
await using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigsForId(guildId,
|
||||
set => set.Include(x => x.AntiSpamSetting).ThenInclude(x => x.IgnoredChannels));
|
||||
var spam = gc.AntiSpamSetting;
|
||||
if (spam is null)
|
||||
return null;
|
||||
|
||||
if (spam.IgnoredChannels.Add(obj)) // if adding to db is successful
|
||||
{
|
||||
if (_antiSpamGuilds.TryGetValue(guildId, out var temp))
|
||||
temp.AntiSpamSettings.IgnoredChannels.Add(obj); // add to local cache
|
||||
|
||||
added = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var toRemove = spam.IgnoredChannels.First(x => x.ChannelId == channelId);
|
||||
uow.Set<AntiSpamIgnore>().Remove(toRemove); // remove from db
|
||||
if (_antiSpamGuilds.TryGetValue(guildId, out var temp))
|
||||
temp.AntiSpamSettings.IgnoredChannels.Remove(toRemove); // remove from local cache
|
||||
|
||||
added = false;
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
return added;
|
||||
}
|
||||
|
||||
public (AntiSpamStats, AntiRaidStats, AntiAltStats) GetAntiStats(ulong guildId)
|
||||
{
|
||||
_antiRaidGuilds.TryGetValue(guildId, out var antiRaidStats);
|
||||
_antiSpamGuilds.TryGetValue(guildId, out var antiSpamStats);
|
||||
_antiAltGuilds.TryGetValue(guildId, out var antiAltStats);
|
||||
|
||||
return (antiSpamStats, antiRaidStats, antiAltStats);
|
||||
}
|
||||
|
||||
public bool IsDurationAllowed(PunishmentAction action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case PunishmentAction.Ban:
|
||||
case PunishmentAction.Mute:
|
||||
case PunishmentAction.ChatMute:
|
||||
case PunishmentAction.VoiceMute:
|
||||
case PunishmentAction.AddRole:
|
||||
case PunishmentAction.TimeOut:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StartAntiAltAsync(
|
||||
ulong guildId,
|
||||
int minAgeMinutes,
|
||||
PunishmentAction action,
|
||||
int actionDurationMinutes = 0,
|
||||
ulong? roleId = null)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiAltSetting));
|
||||
gc.AntiAltSetting = new()
|
||||
{
|
||||
Action = action,
|
||||
ActionDurationMinutes = actionDurationMinutes,
|
||||
MinAge = TimeSpan.FromMinutes(minAgeMinutes),
|
||||
RoleId = roleId
|
||||
};
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
_antiAltGuilds[guildId] = new(gc.AntiAltSetting);
|
||||
}
|
||||
|
||||
public async Task<bool> TryStopAntiAlt(ulong guildId)
|
||||
{
|
||||
if (!_antiAltGuilds.TryRemove(guildId, out _))
|
||||
return false;
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.AntiAltSetting));
|
||||
gc.AntiAltSetting = null;
|
||||
await uow.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
#nullable disable
|
||||
using Nadeko.Bot.Db.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration;
|
||||
|
||||
public enum ProtectionType
|
||||
{
|
||||
Raiding,
|
||||
Spamming,
|
||||
Alting
|
||||
}
|
||||
|
||||
public class AntiRaidStats
|
||||
{
|
||||
public AntiRaidSetting AntiRaidSettings { get; set; }
|
||||
public int UsersCount { get; set; }
|
||||
public ConcurrentHashSet<IGuildUser> RaidUsers { get; set; } = new();
|
||||
}
|
||||
|
||||
public class AntiSpamStats
|
||||
{
|
||||
public AntiSpamSetting AntiSpamSettings { get; set; }
|
||||
public ConcurrentDictionary<ulong, UserSpamStats> UserStats { get; set; } = new();
|
||||
}
|
||||
|
||||
public class AntiAltStats
|
||||
{
|
||||
public PunishmentAction Action
|
||||
=> _setting.Action;
|
||||
|
||||
public int ActionDurationMinutes
|
||||
=> _setting.ActionDurationMinutes;
|
||||
|
||||
public ulong? RoleId
|
||||
=> _setting.RoleId;
|
||||
|
||||
public TimeSpan MinAge
|
||||
=> _setting.MinAge;
|
||||
|
||||
public int Counter
|
||||
=> counter;
|
||||
|
||||
private readonly AntiAltSetting _setting;
|
||||
|
||||
private int counter;
|
||||
|
||||
public AntiAltStats(AntiAltSetting setting)
|
||||
=> _setting = setting;
|
||||
|
||||
public void Increment()
|
||||
=> Interlocked.Increment(ref counter);
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
#nullable disable
|
||||
using Nadeko.Bot.Db.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration;
|
||||
|
||||
public class PunishQueueItem
|
||||
{
|
||||
public PunishmentAction Action { get; set; }
|
||||
public ProtectionType Type { get; set; }
|
||||
public int MuteTime { get; set; }
|
||||
public ulong? RoleId { get; set; }
|
||||
public IGuildUser User { get; set; }
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Modules.Administration;
|
||||
|
||||
public sealed class UserSpamStats
|
||||
{
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_applyLock)
|
||||
{
|
||||
Cleanup();
|
||||
return _messageTracker.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string lastMessage;
|
||||
|
||||
private readonly Queue<DateTime> _messageTracker;
|
||||
|
||||
private readonly object _applyLock = new();
|
||||
|
||||
private readonly TimeSpan _maxTime = TimeSpan.FromMinutes(30);
|
||||
|
||||
public UserSpamStats(IUserMessage msg)
|
||||
{
|
||||
lastMessage = msg.Content.ToUpperInvariant();
|
||||
_messageTracker = new();
|
||||
|
||||
ApplyNextMessage(msg);
|
||||
}
|
||||
|
||||
public void ApplyNextMessage(IUserMessage message)
|
||||
{
|
||||
var upperMsg = message.Content.ToUpperInvariant();
|
||||
|
||||
lock (_applyLock)
|
||||
{
|
||||
if (upperMsg != lastMessage || (string.IsNullOrWhiteSpace(upperMsg) && message.Attachments.Any()))
|
||||
{
|
||||
// if it's a new message, reset spam counter
|
||||
lastMessage = upperMsg;
|
||||
_messageTracker.Clear();
|
||||
}
|
||||
|
||||
_messageTracker.Enqueue(DateTime.UtcNow);
|
||||
}
|
||||
}
|
||||
|
||||
private void Cleanup()
|
||||
{
|
||||
lock (_applyLock)
|
||||
{
|
||||
while (_messageTracker.TryPeek(out var dateTime))
|
||||
{
|
||||
if (DateTime.UtcNow - dateTime < _maxTime)
|
||||
break;
|
||||
|
||||
_messageTracker.Dequeue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user