Anti raid and anti spam commands added O.O

This commit is contained in:
Kwoth 2016-10-18 12:02:15 +02:00
parent 8d3a722d18
commit 30e1521810
4 changed files with 377 additions and 10 deletions

View File

@ -0,0 +1,287 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
public enum PunishmentAction
{
Mute,
Kick,
Ban,
}
private class AntiRaidSetting
{
public int UserThreshold { get; set; }
public int Seconds { get; set; }
public PunishmentAction Action { get; set; }
public IRole MuteRole { get; set; }
public int UsersCount { get; set; }
public ConcurrentHashSet<IGuildUser> RaidUsers { get; set; } = new ConcurrentHashSet<IGuildUser>();
}
private class AntiSpamSetting
{
public PunishmentAction Action { get; set; }
public int MessageThreshold { get; set; } = 3;
public IRole MuteRole { get; set; }
public ConcurrentDictionary<ulong, UserSpamStats> UserStats { get; set; }
= new ConcurrentDictionary<ulong, UserSpamStats>();
}
private class UserSpamStats
{
public int Count { get; set; }
public string LastMessage { get; set; }
public UserSpamStats(string msg)
{
Count = 1;
LastMessage = msg.ToUpperInvariant();
}
public void ApplyNextMessage(string message)
{
var upperMsg = message.ToUpperInvariant();
if (upperMsg == LastMessage)
Count++;
else
{
LastMessage = upperMsg;
Count = 0;
}
}
}
[Group]
public class AntiRaidCommands
{
private static ConcurrentDictionary<ulong, AntiRaidSetting> antiRaidGuilds =
new ConcurrentDictionary<ulong, AntiRaidSetting>();
// guildId | (userId|messages)
private static ConcurrentDictionary<ulong, AntiSpamSetting> antiSpamGuilds =
new ConcurrentDictionary<ulong, AntiSpamSetting>();
private Logger _log { get; }
public AntiRaidCommands(ShardedDiscordClient client)
{
_log = LogManager.GetCurrentClassLogger();
client.MessageReceived += (imsg) =>
{
var msg = imsg as IUserMessage;
if (msg == null)
return Task.CompletedTask;
var channel = msg.Channel as ITextChannel;
if (channel == null)
return Task.CompletedTask;
var t = Task.Run(async () =>
{
AntiSpamSetting spamSettings;
if (!antiSpamGuilds.TryGetValue(channel.Guild.Id, out spamSettings))
return;
var stats = spamSettings.UserStats.AddOrUpdate(msg.Author.Id, new UserSpamStats(msg.Content),
(id, old) => { old.ApplyNextMessage(msg.Content); return old; });
if (stats.Count >= spamSettings.MessageThreshold)
{
if (spamSettings.UserStats.TryRemove(msg.Author.Id, out stats))
{
var log = await PunishUser((IGuildUser)msg.Author, spamSettings.Action, spamSettings.MuteRole)
.ConfigureAwait(false);
try { await channel.Guild.SendMessageToOwnerAsync(log).ConfigureAwait(false); } catch { }
}
}
});
return Task.CompletedTask;
};
client.UserJoined += (usr) =>
{
if (usr.IsBot)
return Task.CompletedTask;
AntiRaidSetting settings;
if (!antiRaidGuilds.TryGetValue(usr.Guild.Id, out settings))
return Task.CompletedTask;
var t = Task.Run(async () =>
{
if (!settings.RaidUsers.Add(usr))
return;
++settings.UsersCount;
if (settings.UsersCount >= settings.UserThreshold)
{
var users = settings.RaidUsers.ToList();
settings.RaidUsers.Clear();
string msg = "";
foreach (var gu in users)
{
msg += await PunishUser(gu, settings.Action, settings.MuteRole).ConfigureAwait(false);
}
try { await usr.Guild.SendMessageToOwnerAsync(msg).ConfigureAwait(false); } catch { }
}
await Task.Delay(1000 * settings.Seconds).ConfigureAwait(false);
settings.RaidUsers.TryRemove(usr);
--settings.UsersCount;
});
return Task.CompletedTask;
};
}
private async Task<string> PunishUser(IGuildUser gu, PunishmentAction action, IRole muteRole)
{
switch (action)
{
case PunishmentAction.Mute:
try
{
await gu.AddRolesAsync(muteRole);
return $"{Format.Bold(gu.ToString())} was muted due to raiding protection.\n";
}
catch (Exception ex) { _log.Warn(ex, "I can't apply punishement"); }
break;
case PunishmentAction.Kick:
try
{
await gu.Guild.AddBanAsync(gu, 7);
try
{
await gu.Guild.RemoveBanAsync(gu);
}
catch
{
await gu.Guild.RemoveBanAsync(gu);
// try it twice, really don't want to ban user if
// only kick has been specified as the punishement
}
return $"{Format.Bold(gu.ToString())} was kicked due to raiding protection.\n";
}
catch (Exception ex) { _log.Warn(ex, "I can't apply punishment"); }
break;
case PunishmentAction.Ban:
try
{
await gu.Guild.AddBanAsync(gu, 7);
return $"{Format.Bold(gu.ToString())} was banned due to raiding protection.\n";
}
catch (Exception ex) { _log.Warn(ex, "I can't apply punishment"); }
break;
default:
break;
}
return String.Empty;
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.Administrator)]
public async Task AntiRaid(IUserMessage imsg, int userThreshold, int seconds, PunishmentAction action)
{
var channel = (ITextChannel)imsg.Channel;
if (userThreshold < 2 || userThreshold > 30)
{
await channel.SendMessageAsync("User threshold must be between 2 and 30").ConfigureAwait(false);
return;
}
if (seconds < 2 || seconds > 300)
{
await channel.SendMessageAsync("Time must be between 2 and 300 seconds.").ConfigureAwait(false);
return;
}
IRole muteRole;
try
{
muteRole = await GetMuteRole(channel.Guild).ConfigureAwait(false);
}
catch (Exception ex)
{
await channel.SendMessageAsync("Failed creating a mute role. Give me ManageRoles permission" +
"or create 'nadeko-mute' role with disabled SendMessages and try again.")
.ConfigureAwait(false);
_log.Warn(ex);
return;
}
var setting = new AntiRaidSetting()
{
Action = action,
Seconds = seconds,
UserThreshold = userThreshold,
MuteRole = muteRole,
};
antiRaidGuilds.AddOrUpdate(channel.Guild.Id, setting, (id, old) => setting);
await channel.SendMessageAsync($"{imsg.Author.Mention} `If {userThreshold} or more users join within {seconds} seconds, I will {action} them.`")
.ConfigureAwait(false);
}
private async Task<IRole> GetMuteRole(IGuild guild)
{
var muteRole = guild.Roles.FirstOrDefault(r => r.Name == "nadeko-mute") ??
await guild.CreateRoleAsync("nadeko-mute", GuildPermissions.None).ConfigureAwait(false);
foreach (var toOverwrite in guild.GetTextChannels())
{
await toOverwrite.AddPermissionOverwriteAsync(muteRole, new OverwritePermissions(sendMessages: PermValue.Deny, attachFiles: PermValue.Deny))
.ConfigureAwait(false);
await Task.Delay(200).ConfigureAwait(false);
}
return muteRole;
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequirePermission(GuildPermission.Administrator)]
public async Task AntiSpam(IUserMessage imsg, PunishmentAction action = PunishmentAction.Mute)
{
var channel = (ITextChannel)imsg.Channel;
AntiSpamSetting throwaway;
if (antiSpamGuilds.TryRemove(channel.Guild.Id, out throwaway))
{
await channel.SendMessageAsync("`Anti-Spam feature disabled on this server.`").ConfigureAwait(false);
}
else
{
if (antiSpamGuilds.TryAdd(channel.Guild.Id, new AntiSpamSetting()
{
Action = action,
MuteRole = await GetMuteRole(channel.Guild).ConfigureAwait(false),
}))
await channel.SendMessageAsync("`Anti-Spam feature enabled on this server.`").ConfigureAwait(false);
}
}
}
}
}

View File

@ -356,6 +356,60 @@ namespace NadekoBot.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to antiraid.
/// </summary>
public static string antiraid_cmd {
get {
return ResourceManager.GetString("antiraid_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Sets an anti-raid protection on the server. First argument is number of people which will trigger the protection. Second one is a time interval in which that number of people needs to join in order to trigger the protection, and third argument is punishment for those people (Kick, Ban, Mute).
/// </summary>
public static string antiraid_desc {
get {
return ResourceManager.GetString("antiraid_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `.antiraid 5 20 Kick`.
/// </summary>
public static string antiraid_usage {
get {
return ResourceManager.GetString("antiraid_usage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to antispam.
/// </summary>
public static string antispam_cmd {
get {
return ResourceManager.GetString("antispam_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Stops people from repeating same message more than 2 times in a row. You can either specify to mute, kick or ban them if that happens..
/// </summary>
public static string antispam_desc {
get {
return ResourceManager.GetString("antispam_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `.antispam Mute` or `.antispam Kick` or `.antispam Ban`.
/// </summary>
public static string antispam_usage {
get {
return ResourceManager.GetString("antispam_usage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to asar.
/// </summary>
@ -528,7 +582,7 @@ namespace NadekoBot.Resources {
}
/// <summary>
/// Looks up a localized string similar to Bans a user by id or name with an optional message..
/// Looks up a localized string similar to Bans a user by ID or name with an optional message..
/// </summary>
public static string ban_desc {
get {
@ -1446,7 +1500,7 @@ namespace NadekoBot.Resources {
}
/// <summary>
/// Looks up a localized string similar to Deletes all text channels ending in `-voice` for which voicechannels are not found. **Use at your own risk. Needs Manage Roles and Manage Channels Permissions.**.
/// Looks up a localized string similar to Deletes all text channels ending in `-voice` for which voicechannels are not found. Use at your own risk..
/// </summary>
public static string cleanvplust_desc {
get {
@ -1932,7 +1986,7 @@ namespace NadekoBot.Resources {
}
/// <summary>
/// Looks up a localized string similar to Toggles the automatic deletion of user&apos;s successful command message to prevent chat flood. **Server Manager Only.**.
/// Looks up a localized string similar to Toggles the automatic deletion of user&apos;s successful command message to prevent chat flood..
/// </summary>
public static string delmsgoncmd_desc {
get {
@ -2040,7 +2094,7 @@ namespace NadekoBot.Resources {
}
/// <summary>
/// Looks up a localized string similar to Works only for the owner. Shuts the bot down..
/// Looks up a localized string similar to Shuts the bot down..
/// </summary>
public static string die_desc {
get {
@ -5631,7 +5685,7 @@ namespace NadekoBot.Resources {
}
/// <summary>
/// Looks up a localized string similar to Toggles rotation of playing status of the dynamic strings you specified earlier..
/// Looks up a localized string similar to Toggles rotation of playing status of the dynamic strings you previously specified..
/// </summary>
public static string rotateplaying_desc {
get {
@ -5982,7 +6036,7 @@ namespace NadekoBot.Resources {
}
/// <summary>
/// Looks up a localized string similar to Changed the name of the current channel. .
/// Looks up a localized string similar to Changes the name of the current channel. .
/// </summary>
public static string setchanlname_desc {
get {
@ -6063,7 +6117,7 @@ namespace NadekoBot.Resources {
}
/// <summary>
/// Looks up a localized string similar to Give the bot a new name. .
/// Looks up a localized string similar to Gives the bot a new name. .
/// </summary>
public static string setname_desc {
get {
@ -6333,7 +6387,7 @@ namespace NadekoBot.Resources {
}
/// <summary>
/// Looks up a localized string similar to Bans and then unbans a user by id or name with an optional message. .
/// Looks up a localized string similar to Bans and then unbans a user by ID or name with an optional message. .
/// </summary>
public static string softban_desc {
get {
@ -7440,7 +7494,7 @@ namespace NadekoBot.Resources {
}
/// <summary>
/// Looks up a localized string similar to Toggles logging to this channel whenever someone joins or leaves a voice channel you are in right now. .
/// Looks up a localized string similar to Toggles logging to this channel whenever someone joins or leaves a voice channel you are currently in. .
/// </summary>
public static string voicepresence_desc {
get {

View File

@ -2646,4 +2646,22 @@
<data name="resetpermissions_usage" xml:space="preserve">
<value>`.resetperms`</value>
</data>
</root>
<data name="antiraid_cmd" xml:space="preserve">
<value>antiraid</value>
</data>
<data name="antiraid_desc" xml:space="preserve">
<value>Sets an anti-raid protection on the server. First argument is number of people which will trigger the protection. Second one is a time interval in which that number of people needs to join in order to trigger the protection, and third argument is punishment for those people (Kick, Ban, Mute)</value>
</data>
<data name="antiraid_usage" xml:space="preserve">
<value>`.antiraid 5 20 Kick`</value>
</data>
<data name="antispam_cmd" xml:space="preserve">
<value>antispam</value>
</data>
<data name="antispam_desc" xml:space="preserve">
<value>Stops people from repeating same message more than 2 times in a row. You can either specify to mute, kick or ban them if that happens.</value>
</data>
<data name="antispam_usage" xml:space="preserve">
<value>`.antispam Mute` or `.antispam Kick` or `.antispam Ban`</value>
</data>
</root>

View File

@ -24,6 +24,14 @@ namespace NadekoBot.Extensions
http.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
}
public static async Task<IMessage> SendMessageToOwnerAsync(this IGuild guild, string message)
{
var ownerPrivate = await (await guild.GetOwnerAsync().ConfigureAwait(false)).CreateDMChannelAsync()
.ConfigureAwait(false);
return await ownerPrivate.SendMessageAsync(message).ConfigureAwait(false);
}
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> elems, Action<T> exec)
{
foreach (var elem in elems)