Administration almost done, logcommands left

This commit is contained in:
Master Kwoth 2017-05-27 19:42:23 +02:00
parent 355425bf80
commit a4973ffbb3
26 changed files with 809 additions and 608 deletions

View File

@ -3,9 +3,7 @@ using Discord.Commands;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using NLog; using NadekoBot.Services.Administration;
using System;
using System.Collections.Concurrent;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -16,31 +14,13 @@ namespace NadekoBot.Modules.Administration
[Group] [Group]
public class AutoAssignRoleCommands : NadekoSubmodule public class AutoAssignRoleCommands : NadekoSubmodule
{ {
//guildid/roleid private readonly DbHandler _db;
private static ConcurrentDictionary<ulong, ulong> AutoAssignedRoles { get; } private readonly AutoAssignRoleService _service;
static AutoAssignRoleCommands() public AutoAssignRoleCommands(AutoAssignRoleService service, DbHandler db)
{ {
var log = LogManager.GetCurrentClassLogger(); _db = db;
_service = service;
AutoAssignedRoles = new ConcurrentDictionary<ulong, ulong>(NadekoBot.AllGuildConfigs.Where(x => x.AutoAssignRoleId != 0)
.ToDictionary(k => k.GuildId, v => v.AutoAssignRoleId));
NadekoBot.Client.UserJoined += async (user) =>
{
try
{
AutoAssignedRoles.TryGetValue(user.Guild.Id, out ulong roleId);
if (roleId == 0)
return;
var role = user.Guild.Roles.FirstOrDefault(r => r.Id == roleId);
if (role != null)
await user.AddRoleAsync(role).ConfigureAwait(false);
}
catch (Exception ex) { log.Warn(ex); }
};
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -53,18 +33,18 @@ namespace NadekoBot.Modules.Administration
if (Context.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= role.Position) if (Context.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= role.Position)
return; return;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var conf = uow.GuildConfigs.For(Context.Guild.Id, set => set); var conf = uow.GuildConfigs.For(Context.Guild.Id, set => set);
if (role == null) if (role == null)
{ {
conf.AutoAssignRoleId = 0; conf.AutoAssignRoleId = 0;
AutoAssignedRoles.TryRemove(Context.Guild.Id, out ulong throwaway); _service.AutoAssignedRoles.TryRemove(Context.Guild.Id, out ulong throwaway);
} }
else else
{ {
conf.AutoAssignRoleId = role.Id; conf.AutoAssignRoleId = role.Id;
AutoAssignedRoles.AddOrUpdate(Context.Guild.Id, role.Id, (key, val) => role.Id); _service.AutoAssignedRoles.AddOrUpdate(Context.Guild.Id, role.Id, (key, val) => role.Id);
} }
await uow.CompleteAsync().ConfigureAwait(false); await uow.CompleteAsync().ConfigureAwait(false);

View File

@ -1,19 +1,9 @@
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Database;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Discord.WebSocket; using NadekoBot.Services.Administration;
using NLog;
using NadekoBot.Extensions;
namespace NadekoBot.Modules.Administration namespace NadekoBot.Modules.Administration
{ {
@ -22,64 +12,13 @@ namespace NadekoBot.Modules.Administration
[Group] [Group]
public class GameChannelCommands : NadekoSubmodule public class GameChannelCommands : NadekoSubmodule
{ {
//private static readonly Timer _t; private readonly DbHandler _db;
private readonly GameVoiceChannelService _service;
private static readonly ConcurrentHashSet<ulong> gameVoiceChannels = new ConcurrentHashSet<ulong>(); public GameChannelCommands(GameVoiceChannelService service, DbHandler db)
private static new readonly Logger _log;
static GameChannelCommands()
{ {
//_t = new Timer(_ => { _db = db;
_service = service;
//}, null, );
_log = LogManager.GetCurrentClassLogger();
gameVoiceChannels = new ConcurrentHashSet<ulong>(
NadekoBot.AllGuildConfigs.Where(gc => gc.GameVoiceChannel != null)
.Select(gc => gc.GameVoiceChannel.Value));
NadekoBot.Client.UserVoiceStateUpdated += Client_UserVoiceStateUpdated;
}
private static Task Client_UserVoiceStateUpdated(SocketUser usr, SocketVoiceState oldState, SocketVoiceState newState)
{
var _ = Task.Run(async () =>
{
try
{
var gUser = usr as SocketGuildUser;
if (gUser == null)
return;
var game = gUser.Game?.Name.TrimTo(50).ToLowerInvariant();
if (oldState.VoiceChannel == newState.VoiceChannel ||
newState.VoiceChannel == null)
return;
if (!gameVoiceChannels.Contains(newState.VoiceChannel.Id) ||
string.IsNullOrWhiteSpace(game))
return;
var vch = gUser.Guild.VoiceChannels
.FirstOrDefault(x => x.Name.ToLowerInvariant() == game);
if (vch == null)
return;
await Task.Delay(1000).ConfigureAwait(false);
await gUser.ModifyAsync(gu => gu.Channel = vch).ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
}
});
return Task.CompletedTask;
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -96,20 +35,20 @@ namespace NadekoBot.Modules.Administration
return; return;
} }
ulong? id; ulong? id;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set); var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set);
if (gc.GameVoiceChannel == vch.Id) if (gc.GameVoiceChannel == vch.Id)
{ {
gameVoiceChannels.TryRemove(vch.Id); _service.GameVoiceChannels.TryRemove(vch.Id);
id = gc.GameVoiceChannel = null; id = gc.GameVoiceChannel = null;
} }
else else
{ {
if(gc.GameVoiceChannel != null) if(gc.GameVoiceChannel != null)
gameVoiceChannels.TryRemove(gc.GameVoiceChannel.Value); _service.GameVoiceChannels.TryRemove(gc.GameVoiceChannel.Value);
gameVoiceChannels.Add(vch.Id); _service.GameVoiceChannels.Add(vch.Id);
id = gc.GameVoiceChannel = vch.Id; id = gc.GameVoiceChannel = vch.Id;
} }
@ -122,7 +61,7 @@ namespace NadekoBot.Modules.Administration
} }
else else
{ {
gameVoiceChannels.Add(vch.Id); _service.GameVoiceChannels.Add(vch.Id);
await ReplyConfirmLocalized("gvc_enabled", Format.Bold(vch.Name)).ConfigureAwait(false); await ReplyConfirmLocalized("gvc_enabled", Format.Bold(vch.Name)).ConfigureAwait(false);
} }
} }

View File

@ -18,7 +18,7 @@ namespace NadekoBot.Modules.Administration
{ {
//Română, România //Română, România
//Bahasa Indonesia, Indonesia //Bahasa Indonesia, Indonesia
private ImmutableDictionary<string, string> supportedLocales { get; } = new Dictionary<string, string>() private static ImmutableDictionary<string, string> supportedLocales { get; } = new Dictionary<string, string>()
{ {
//{"ar", "العربية" }, //{"ar", "العربية" },
{"zh-TW", "繁體中文, 台灣" }, {"zh-TW", "繁體中文, 台灣" },
@ -46,7 +46,7 @@ namespace NadekoBot.Modules.Administration
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task LanguageSet() public async Task LanguageSet()
{ {
var cul = NadekoBot.Localization.GetCultureInfo(Context.Guild); var cul = _localization.GetCultureInfo(Context.Guild);
await ReplyConfirmLocalized("lang_set_show", Format.Bold(cul.ToString()), Format.Bold(cul.NativeName)) await ReplyConfirmLocalized("lang_set_show", Format.Bold(cul.ToString()), Format.Bold(cul.NativeName))
.ConfigureAwait(false); .ConfigureAwait(false);
} }
@ -61,13 +61,13 @@ namespace NadekoBot.Modules.Administration
CultureInfo ci; CultureInfo ci;
if (name.Trim().ToLowerInvariant() == "default") if (name.Trim().ToLowerInvariant() == "default")
{ {
NadekoBot.Localization.RemoveGuildCulture(Context.Guild); _localization.RemoveGuildCulture(Context.Guild);
ci = NadekoBot.Localization.DefaultCultureInfo; ci = _localization.DefaultCultureInfo;
} }
else else
{ {
ci = new CultureInfo(name); ci = new CultureInfo(name);
NadekoBot.Localization.SetGuildCulture(Context.Guild, ci); _localization.SetGuildCulture(Context.Guild, ci);
} }
await ReplyConfirmLocalized("lang_set", Format.Bold(ci.ToString()), Format.Bold(ci.NativeName)).ConfigureAwait(false); await ReplyConfirmLocalized("lang_set", Format.Bold(ci.ToString()), Format.Bold(ci.NativeName)).ConfigureAwait(false);
@ -81,7 +81,7 @@ namespace NadekoBot.Modules.Administration
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task LanguageSetDefault() public async Task LanguageSetDefault()
{ {
var cul = NadekoBot.Localization.DefaultCultureInfo; var cul = _localization.DefaultCultureInfo;
await ReplyConfirmLocalized("lang_set_bot_show", cul, cul.NativeName).ConfigureAwait(false); await ReplyConfirmLocalized("lang_set_bot_show", cul, cul.NativeName).ConfigureAwait(false);
} }
@ -94,13 +94,13 @@ namespace NadekoBot.Modules.Administration
CultureInfo ci; CultureInfo ci;
if (name.Trim().ToLowerInvariant() == "default") if (name.Trim().ToLowerInvariant() == "default")
{ {
NadekoBot.Localization.ResetDefaultCulture(); _localization.ResetDefaultCulture();
ci = NadekoBot.Localization.DefaultCultureInfo; ci = _localization.DefaultCultureInfo;
} }
else else
{ {
ci = new CultureInfo(name); ci = new CultureInfo(name);
NadekoBot.Localization.SetDefaultCulture(ci); _localization.SetDefaultCulture(ci);
} }
await ReplyConfirmLocalized("lang_set_bot", Format.Bold(ci.ToString()), Format.Bold(ci.NativeName)).ConfigureAwait(false); await ReplyConfirmLocalized("lang_set_bot", Format.Bold(ci.ToString()), Format.Bold(ci.NativeName)).ConfigureAwait(false);
} }

View File

@ -35,11 +35,11 @@ namespace NadekoBot.Modules.Administration
static LogCommands() static LogCommands()
{ {
Client = NadekoBot.Client; Client = _client;
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
var sw = Stopwatch.StartNew(); var sw = Stopwatch.StartNew();
GuildLogSettings = new ConcurrentDictionary<ulong, LogSetting>(NadekoBot.AllGuildConfigs GuildLogSettings = new ConcurrentDictionary<ulong, LogSetting>(gcs
.ToDictionary(g => g.GuildId, g => g.LogSetting)); .ToDictionary(g => g.GuildId, g => g.LogSetting));
_timerReference = new Timer(async (state) => _timerReference = new Timer(async (state) =>
@ -136,7 +136,7 @@ namespace NadekoBot.Modules.Administration
await logChannel.EmbedAsync(embed).ConfigureAwait(false); await logChannel.EmbedAsync(embed).ConfigureAwait(false);
//var guildsMemberOf = NadekoBot.Client.GetGuilds().Where(g => g.Users.Select(u => u.Id).Contains(before.Id)).ToList(); //var guildsMemberOf = _client.GetGuilds().Where(g => g.Users.Select(u => u.Id).Contains(before.Id)).ToList();
//foreach (var g in guildsMemberOf) //foreach (var g in guildsMemberOf)
//{ //{
// LogSetting logSetting; // LogSetting logSetting;
@ -840,7 +840,7 @@ namespace NadekoBot.Modules.Administration
private static void UnsetLogSetting(ulong guildId, LogType logChannelType) private static void UnsetLogSetting(ulong guildId, LogType logChannelType)
{ {
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var newLogSetting = uow.GuildConfigs.LogSettingsFor(guildId).LogSetting; var newLogSetting = uow.GuildConfigs.LogSettingsFor(guildId).LogSetting;
switch (logChannelType) switch (logChannelType)
@ -910,7 +910,7 @@ namespace NadekoBot.Modules.Administration
{ {
var channel = (ITextChannel)Context.Channel; var channel = (ITextChannel)Context.Channel;
LogSetting logSetting; LogSetting logSetting;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
logSetting = uow.GuildConfigs.LogSettingsFor(channel.Guild.Id).LogSetting; logSetting = uow.GuildConfigs.LogSettingsFor(channel.Guild.Id).LogSetting;
GuildLogSettings.AddOrUpdate(channel.Guild.Id, (id) => logSetting, (id, old) => logSetting); GuildLogSettings.AddOrUpdate(channel.Guild.Id, (id) => logSetting, (id, old) => logSetting);
@ -946,7 +946,7 @@ namespace NadekoBot.Modules.Administration
{ {
var channel = (ITextChannel)Context.Channel; var channel = (ITextChannel)Context.Channel;
int removed; int removed;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var config = uow.GuildConfigs.LogSettingsFor(channel.Guild.Id); var config = uow.GuildConfigs.LogSettingsFor(channel.Guild.Id);
LogSetting logSetting = GuildLogSettings.GetOrAdd(channel.Guild.Id, (id) => config.LogSetting); LogSetting logSetting = GuildLogSettings.GetOrAdd(channel.Guild.Id, (id) => config.LogSetting);
@ -986,7 +986,7 @@ namespace NadekoBot.Modules.Administration
{ {
var channel = (ITextChannel)Context.Channel; var channel = (ITextChannel)Context.Channel;
ulong? channelId = null; ulong? channelId = null;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var logSetting = uow.GuildConfigs.LogSettingsFor(channel.Guild.Id).LogSetting; var logSetting = uow.GuildConfigs.LogSettingsFor(channel.Guild.Id).LogSetting;
GuildLogSettings.AddOrUpdate(channel.Guild.Id, (id) => logSetting, (id, old) => logSetting); GuildLogSettings.AddOrUpdate(channel.Guild.Id, (id) => logSetting, (id, old) => logSetting);

View File

@ -8,7 +8,6 @@ using NadekoBot.Attributes;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using Newtonsoft.Json; using Newtonsoft.Json;
using NLog;
using NadekoBot.Modules.Administration.Commands.Migration; using NadekoBot.Modules.Administration.Commands.Migration;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using NadekoBot.Extensions; using NadekoBot.Extensions;
@ -23,12 +22,11 @@ namespace NadekoBot.Modules.Administration
public class Migration : NadekoSubmodule public class Migration : NadekoSubmodule
{ {
private const int CURRENT_VERSION = 1; private const int CURRENT_VERSION = 1;
private readonly DbHandler _db;
private new static readonly Logger _log; public Migration(DbHandler db)
static Migration()
{ {
_log = LogManager.GetCurrentClassLogger(); _db = db;
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -36,7 +34,7 @@ namespace NadekoBot.Modules.Administration
public async Task MigrateData() public async Task MigrateData()
{ {
var version = 0; var version = 0;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
version = uow.BotConfig.GetOrCreate().MigrationVersion; version = uow.BotConfig.GetOrCreate().MigrationVersion;
} }
@ -62,7 +60,7 @@ namespace NadekoBot.Modules.Administration
private void Migrate0_9To1_0() private void Migrate0_9To1_0()
{ {
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var botConfig = uow.BotConfig.GetOrCreate(); var botConfig = uow.BotConfig.GetOrCreate();
MigrateConfig0_9(uow, botConfig); MigrateConfig0_9(uow, botConfig);

View File

@ -1,14 +1,9 @@
using Discord.Commands; using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Administration;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration namespace NadekoBot.Modules.Administration
@ -18,101 +13,31 @@ namespace NadekoBot.Modules.Administration
[Group] [Group]
public class PlayingRotateCommands : NadekoSubmodule public class PlayingRotateCommands : NadekoSubmodule
{ {
public static List<PlayingStatus> RotatingStatusMessages { get; } private static readonly object _locker = new object();
public static volatile bool RotatingStatuses; private readonly DbHandler _db;
private readonly object _locker = new object(); private readonly PlayingRotateService _service;
private new static Logger _log { get; }
private static readonly Timer _t;
private class TimerState public PlayingRotateCommands(PlayingRotateService service, DbHandler db)
{ {
public int Index { get; set; } _db = db;
_service = service;
} }
static PlayingRotateCommands()
{
_log = LogManager.GetCurrentClassLogger();
RotatingStatusMessages = NadekoBot.BotConfig.RotatingStatusMessages;
RotatingStatuses = NadekoBot.BotConfig.RotatingStatuses;
_t = new Timer(async (objState) =>
{
try
{
var state = (TimerState)objState;
if (!RotatingStatuses)
return;
if (state.Index >= RotatingStatusMessages.Count)
state.Index = 0;
if (!RotatingStatusMessages.Any())
return;
var status = RotatingStatusMessages[state.Index++].Status;
if (string.IsNullOrWhiteSpace(status))
return;
PlayingPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value()));
var shards = NadekoBot.Client.Shards;
for (int i = 0; i < shards.Count; i++)
{
var curShard = shards.ElementAt(i);
ShardSpecificPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value(curShard)));
try { await shards.ElementAt(i).SetGameAsync(status).ConfigureAwait(false); }
catch (Exception ex)
{
_log.Warn(ex);
}
}
}
catch (Exception ex)
{
_log.Warn("Rotating playing status errored.\n" + ex);
}
}, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
}
public static Dictionary<string, Func<string>> PlayingPlaceholders { get; } =
new Dictionary<string, Func<string>> {
{ "%servers%", () => NadekoBot.Client.Guilds.Count.ToString()},
{ "%users%", () => NadekoBot.Client.Guilds.Sum(s => s.Users.Count).ToString()},
{ "%playing%", () => {
var cnt = NadekoBot.MusicService.MusicPlayers.Count(kvp => kvp.Value.CurrentSong != null);
if (cnt != 1) return cnt.ToString();
try {
var mp = NadekoBot.MusicService.MusicPlayers.FirstOrDefault();
return mp.Value.CurrentSong.SongInfo.Title;
}
catch {
return "No songs";
}
}
},
{ "%queued%", () => NadekoBot.MusicService.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()},
{ "%time%", () => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()) },
{ "%shardcount%", () => NadekoBot.Client.Shards.Count.ToString() },
};
public static Dictionary<string, Func<DiscordSocketClient, string>> ShardSpecificPlaceholders { get; } =
new Dictionary<string, Func<DiscordSocketClient, string>> {
{ "%shardid%", (client) => client.ShardId.ToString()},
{ "%shardguilds%", (client) => client.Guilds.Count.ToString()},
};
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[OwnerOnly] [OwnerOnly]
public async Task RotatePlaying() public async Task RotatePlaying()
{ {
lock (_locker) lock (_locker)
{ {
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var config = uow.BotConfig.GetOrCreate(); var config = uow.BotConfig.GetOrCreate();
RotatingStatuses = config.RotatingStatuses = !config.RotatingStatuses; _service.RotatingStatuses = config.RotatingStatuses = !config.RotatingStatuses;
uow.Complete(); uow.Complete();
} }
} }
if (RotatingStatuses) if (_service.RotatingStatuses)
await ReplyConfirmLocalized("ropl_enabled").ConfigureAwait(false); await ReplyConfirmLocalized("ropl_enabled").ConfigureAwait(false);
else else
await ReplyConfirmLocalized("ropl_disabled").ConfigureAwait(false); await ReplyConfirmLocalized("ropl_disabled").ConfigureAwait(false);
@ -122,12 +47,12 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly] [OwnerOnly]
public async Task AddPlaying([Remainder] string status) public async Task AddPlaying([Remainder] string status)
{ {
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var config = uow.BotConfig.GetOrCreate(); var config = uow.BotConfig.GetOrCreate();
var toAdd = new PlayingStatus { Status = status }; var toAdd = new PlayingStatus { Status = status };
config.RotatingStatusMessages.Add(toAdd); config.RotatingStatusMessages.Add(toAdd);
RotatingStatusMessages.Add(toAdd); _service.RotatingStatusMessages.Add(toAdd);
await uow.CompleteAsync(); await uow.CompleteAsync();
} }
@ -138,13 +63,13 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly] [OwnerOnly]
public async Task ListPlaying() public async Task ListPlaying()
{ {
if (!RotatingStatusMessages.Any()) if (!_service.RotatingStatusMessages.Any())
await ReplyErrorLocalized("ropl_not_set").ConfigureAwait(false); await ReplyErrorLocalized("ropl_not_set").ConfigureAwait(false);
else else
{ {
var i = 1; var i = 1;
await ReplyConfirmLocalized("ropl_list", await ReplyConfirmLocalized("ropl_list",
string.Join("\n\t", RotatingStatusMessages.Select(rs => $"`{i++}.` {rs.Status}"))) string.Join("\n\t", _service.RotatingStatusMessages.Select(rs => $"`{i++}.` {rs.Status}")))
.ConfigureAwait(false); .ConfigureAwait(false);
} }
@ -157,7 +82,7 @@ namespace NadekoBot.Modules.Administration
index -= 1; index -= 1;
string msg; string msg;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var config = uow.BotConfig.GetOrCreate(); var config = uow.BotConfig.GetOrCreate();
@ -165,7 +90,7 @@ namespace NadekoBot.Modules.Administration
return; return;
msg = config.RotatingStatusMessages[index].Status; msg = config.RotatingStatusMessages[index].Status;
config.RotatingStatusMessages.RemoveAt(index); config.RotatingStatusMessages.RemoveAt(index);
RotatingStatusMessages.RemoveAt(index); _service.RotatingStatusMessages.RemoveAt(index);
await uow.CompleteAsync(); await uow.CompleteAsync();
} }
await ReplyConfirmLocalized("reprm", msg).ConfigureAwait(false); await ReplyConfirmLocalized("reprm", msg).ConfigureAwait(false);

View File

@ -5,246 +5,27 @@ using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using NLog;
using System; using System;
using System.Collections.Concurrent;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Threading; using NadekoBot.Services.Administration;
using System.Collections.Generic;
namespace NadekoBot.Modules.Administration namespace NadekoBot.Modules.Administration
{ {
public partial class Administration public partial class Administration
{ {
public enum ProtectionType
{
Raiding,
Spamming,
}
public class AntiRaidStats
{
public AntiRaidSetting AntiRaidSettings { get; set; }
public int UsersCount { get; set; }
public ConcurrentHashSet<IGuildUser> RaidUsers { get; set; } = new ConcurrentHashSet<IGuildUser>();
}
public class AntiSpamStats
{
public AntiSpamSetting AntiSpamSettings { get; set; }
public ConcurrentDictionary<ulong, UserSpamStats> UserStats { get; set; }
= new ConcurrentDictionary<ulong, UserSpamStats>();
}
public class UserSpamStats : IDisposable
{
public int Count => timers.Count;
public string LastMessage { get; set; }
private ConcurrentQueue<Timer> timers { get; }
public UserSpamStats(IUserMessage msg)
{
LastMessage = msg.Content.ToUpperInvariant();
timers = new ConcurrentQueue<Timer>();
ApplyNextMessage(msg);
}
private readonly object applyLock = new object();
public void ApplyNextMessage(IUserMessage message)
{
lock(applyLock){
var upperMsg = message.Content.ToUpperInvariant();
if (upperMsg != LastMessage || (string.IsNullOrWhiteSpace(upperMsg) && message.Attachments.Any()))
{
LastMessage = upperMsg;
//todo c#7
Timer old;
while(timers.TryDequeue(out old))
old.Change(Timeout.Infinite, Timeout.Infinite);
}
var t = new Timer((_) => {
//todo c#7
Timer __;
if(timers.TryDequeue(out __))
__.Change(Timeout.Infinite, Timeout.Infinite);
}, null, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(30));
timers.Enqueue(t);
}
}
public void Dispose()
{
//todo c#7
Timer old;
while(timers.TryDequeue(out old))
old.Change(Timeout.Infinite, Timeout.Infinite);
}
}
[Group] [Group]
public class ProtectionCommands : NadekoSubmodule public class ProtectionCommands : NadekoSubmodule
{ {
private static readonly ConcurrentDictionary<ulong, AntiRaidStats> _antiRaidGuilds = private readonly ProtectionService _service;
new ConcurrentDictionary<ulong, AntiRaidStats>(); private readonly MuteService _mute;
// guildId | (userId|messages) private readonly DbHandler _db;
private static readonly ConcurrentDictionary<ulong, AntiSpamStats> _antiSpamGuilds =
new ConcurrentDictionary<ulong, AntiSpamStats>();
private new static readonly Logger _log; public ProtectionCommands(ProtectionService service, MuteService mute, DbHandler db)
static ProtectionCommands()
{ {
_log = LogManager.GetCurrentClassLogger(); _service = service;
_mute = mute;
foreach (var gc in NadekoBot.AllGuildConfigs) _db = db;
{
var raid = gc.AntiRaidSetting;
var spam = gc.AntiSpamSetting;
if (raid != null)
{
var raidStats = new AntiRaidStats() { AntiRaidSettings = raid };
_antiRaidGuilds.TryAdd(gc.GuildId, raidStats);
}
if (spam != null)
_antiSpamGuilds.TryAdd(gc.GuildId, new AntiSpamStats() { AntiSpamSettings = spam });
}
NadekoBot.Client.MessageReceived += (imsg) =>
{
var msg = imsg as IUserMessage;
if (msg == null || msg.Author.IsBot)
return Task.CompletedTask;
var channel = msg.Channel as ITextChannel;
if (channel == null)
return Task.CompletedTask;
var _ = Task.Run(async () =>
{
try
{
AntiSpamStats spamSettings;
if (!_antiSpamGuilds.TryGetValue(channel.Guild.Id, out spamSettings) ||
spamSettings.AntiSpamSettings.IgnoredChannels.Contains(new AntiSpamIgnore()
{
ChannelId = channel.Id
}))
return;
var stats = spamSettings.UserStats.AddOrUpdate(msg.Author.Id, (id) => new UserSpamStats(msg),
(id, old) =>
{
old.ApplyNextMessage(msg); return old;
});
if (stats.Count >= spamSettings.AntiSpamSettings.MessageThreshold)
{
if (spamSettings.UserStats.TryRemove(msg.Author.Id, out stats))
{
stats.Dispose();
await PunishUsers(spamSettings.AntiSpamSettings.Action, ProtectionType.Spamming, (IGuildUser)msg.Author)
.ConfigureAwait(false);
}
}
}
catch
{
// ignored
}
});
return Task.CompletedTask;
};
NadekoBot.Client.UserJoined += (usr) =>
{
if (usr.IsBot)
return Task.CompletedTask;
AntiRaidStats settings;
if (!_antiRaidGuilds.TryGetValue(usr.Guild.Id, out settings))
return Task.CompletedTask;
if (!settings.RaidUsers.Add(usr))
return Task.CompletedTask;
var _ = Task.Run(async () =>
{
try
{
++settings.UsersCount;
if (settings.UsersCount >= settings.AntiRaidSettings.UserThreshold)
{
var users = settings.RaidUsers.ToArray();
settings.RaidUsers.Clear();
await PunishUsers(settings.AntiRaidSettings.Action, ProtectionType.Raiding, users).ConfigureAwait(false);
}
await Task.Delay(1000 * settings.AntiRaidSettings.Seconds).ConfigureAwait(false);
settings.RaidUsers.TryRemove(usr);
--settings.UsersCount;
}
catch
{
// ignored
}
});
return Task.CompletedTask;
};
}
private static async Task PunishUsers(PunishmentAction action, ProtectionType pt, params IGuildUser[] gus)
{
_log.Info($"[{pt}] - Punishing [{gus.Length}] users with [{action}] in {gus[0].Guild.Name} guild");
foreach (var gu in gus)
{
switch (action)
{
case PunishmentAction.Mute:
try
{
await MuteCommands.MuteUser(gu).ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex, "I can't apply punishement"); }
break;
case PunishmentAction.Kick:
try
{
await gu.KickAsync().ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex, "I can't apply punishement"); }
break;
case PunishmentAction.Softban:
try
{
await gu.Guild.AddBanAsync(gu, 7).ConfigureAwait(false);
try
{
await gu.Guild.RemoveBanAsync(gu).ConfigureAwait(false);
}
catch
{
await gu.Guild.RemoveBanAsync(gu).ConfigureAwait(false);
// try it twice, really don't want to ban user if
// only kick has been specified as the punishement
}
}
catch (Exception ex) { _log.Warn(ex, "I can't apply punishment"); }
break;
case PunishmentAction.Ban:
try
{
await gu.Guild.AddBanAsync(gu, 7).ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex, "I can't apply punishment"); }
break;
}
}
await LogCommands.TriggeredAntiProtection(gus, action, pt).ConfigureAwait(false);
} }
private string GetAntiSpamString(AntiSpamStats stats) private string GetAntiSpamString(AntiSpamStats stats)
@ -282,9 +63,9 @@ namespace NadekoBot.Modules.Administration
} }
AntiRaidStats throwaway; AntiRaidStats throwaway;
if (_antiRaidGuilds.TryRemove(Context.Guild.Id, out throwaway)) if (_service.AntiRaidGuilds.TryRemove(Context.Guild.Id, out throwaway))
{ {
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.AntiRaidSetting)); var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.AntiRaidSetting));
@ -297,7 +78,7 @@ namespace NadekoBot.Modules.Administration
try try
{ {
await MuteCommands.GetMuteRole(Context.Guild).ConfigureAwait(false); await _mute.GetMuteRole(Context.Guild).ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -316,9 +97,9 @@ namespace NadekoBot.Modules.Administration
} }
}; };
_antiRaidGuilds.AddOrUpdate(Context.Guild.Id, stats, (key, old) => stats); _service.AntiRaidGuilds.AddOrUpdate(Context.Guild.Id, stats, (key, old) => stats);
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.AntiRaidSetting)); var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.AntiRaidSetting));
@ -339,10 +120,10 @@ namespace NadekoBot.Modules.Administration
return; return;
AntiSpamStats throwaway; AntiSpamStats throwaway;
if (_antiSpamGuilds.TryRemove(Context.Guild.Id, out throwaway)) if (_service.AntiSpamGuilds.TryRemove(Context.Guild.Id, out throwaway))
{ {
throwaway.UserStats.ForEach(x => x.Value.Dispose()); throwaway.UserStats.ForEach(x => x.Value.Dispose());
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.AntiSpamSetting) var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.AntiSpamSetting)
.ThenInclude(x => x.IgnoredChannels)); .ThenInclude(x => x.IgnoredChannels));
@ -356,7 +137,7 @@ namespace NadekoBot.Modules.Administration
try try
{ {
await MuteCommands.GetMuteRole(Context.Guild).ConfigureAwait(false); await _mute.GetMuteRole(Context.Guild).ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -374,9 +155,9 @@ namespace NadekoBot.Modules.Administration
} }
}; };
_antiSpamGuilds.AddOrUpdate(Context.Guild.Id, stats, (key, old) => stats); _service.AntiSpamGuilds.AddOrUpdate(Context.Guild.Id, stats, (key, old) => stats);
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.AntiSpamSetting)); var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.AntiSpamSetting));
@ -398,7 +179,7 @@ namespace NadekoBot.Modules.Administration
ChannelId = channel.Id ChannelId = channel.Id
}; };
bool added; bool added;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.AntiSpamSetting).ThenInclude(x => x.IgnoredChannels)); var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.AntiSpamSetting).ThenInclude(x => x.IgnoredChannels));
var spam = gc.AntiSpamSetting; var spam = gc.AntiSpamSetting;
@ -410,7 +191,7 @@ namespace NadekoBot.Modules.Administration
if (spam.IgnoredChannels.Add(obj)) if (spam.IgnoredChannels.Add(obj))
{ {
AntiSpamStats temp; AntiSpamStats temp;
if (_antiSpamGuilds.TryGetValue(Context.Guild.Id, out temp)) if (_service.AntiSpamGuilds.TryGetValue(Context.Guild.Id, out temp))
temp.AntiSpamSettings.IgnoredChannels.Add(obj); temp.AntiSpamSettings.IgnoredChannels.Add(obj);
added = true; added = true;
} }
@ -418,7 +199,7 @@ namespace NadekoBot.Modules.Administration
{ {
spam.IgnoredChannels.Remove(obj); spam.IgnoredChannels.Remove(obj);
AntiSpamStats temp; AntiSpamStats temp;
if (_antiSpamGuilds.TryGetValue(Context.Guild.Id, out temp)) if (_service.AntiSpamGuilds.TryGetValue(Context.Guild.Id, out temp))
temp.AntiSpamSettings.IgnoredChannels.Remove(obj); temp.AntiSpamSettings.IgnoredChannels.Remove(obj);
added = false; added = false;
} }
@ -437,10 +218,10 @@ namespace NadekoBot.Modules.Administration
public async Task AntiList() public async Task AntiList()
{ {
AntiSpamStats spam; AntiSpamStats spam;
_antiSpamGuilds.TryGetValue(Context.Guild.Id, out spam); _service.AntiSpamGuilds.TryGetValue(Context.Guild.Id, out spam);
AntiRaidStats raid; AntiRaidStats raid;
_antiRaidGuilds.TryGetValue(Context.Guild.Id, out raid); _service.AntiRaidGuilds.TryGetValue(Context.Guild.Id, out raid);
if (spam == null && raid == null) if (spam == null && raid == null)
{ {

View File

@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Administration;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using NLog; using NLog;
using System; using System;
@ -21,85 +22,13 @@ namespace NadekoBot.Modules.Administration
[Group] [Group]
public class RatelimitCommands : NadekoSubmodule public class RatelimitCommands : NadekoSubmodule
{ {
public static ConcurrentDictionary<ulong, Ratelimiter> RatelimitingChannels = new ConcurrentDictionary<ulong, Ratelimiter>(); private readonly RatelimitService _service;
public static ConcurrentDictionary<ulong, HashSet<ulong>> IgnoredRoles = new ConcurrentDictionary<ulong, HashSet<ulong>>(); private readonly DbHandler _db;
public static ConcurrentDictionary<ulong, HashSet<ulong>> IgnoredUsers = new ConcurrentDictionary<ulong, HashSet<ulong>>();
private new static readonly Logger _log; public RatelimitCommands(RatelimitService service, DbHandler db)
public class Ratelimiter
{ {
public class RatelimitedUser _service = service;
{ _db = db;
public ulong UserId { get; set; }
public int MessageCount { get; set; } = 0;
}
public ulong ChannelId { get; set; }
public int MaxMessages { get; set; }
public int PerSeconds { get; set; }
public CancellationTokenSource CancelSource { get; set; } = new CancellationTokenSource();
public ConcurrentDictionary<ulong, RatelimitedUser> Users { get; set; } = new ConcurrentDictionary<ulong, RatelimitedUser>();
public bool CheckUserRatelimit(ulong id, ulong guildId, SocketGuildUser optUser)
{
if ((IgnoredUsers.TryGetValue(guildId, out HashSet<ulong> ignoreUsers) && ignoreUsers.Contains(id)) ||
(optUser != null && IgnoredRoles.TryGetValue(guildId, out HashSet<ulong> ignoreRoles) && optUser.Roles.Any(x => ignoreRoles.Contains(x.Id))))
return false;
var usr = Users.GetOrAdd(id, (key) => new RatelimitedUser() { UserId = id });
if (usr.MessageCount >= MaxMessages)
{
return true;
}
usr.MessageCount++;
var _ = Task.Run(async () =>
{
try
{
await Task.Delay(PerSeconds * 1000, CancelSource.Token);
}
catch (OperationCanceledException) { }
usr.MessageCount--;
});
return false;
}
}
static RatelimitCommands()
{
_log = LogManager.GetCurrentClassLogger();
IgnoredRoles = new ConcurrentDictionary<ulong, HashSet<ulong>>(
NadekoBot.AllGuildConfigs
.ToDictionary(x => x.GuildId,
x => new HashSet<ulong>(x.SlowmodeIgnoredRoles.Select(y => y.RoleId))));
IgnoredUsers = new ConcurrentDictionary<ulong, HashSet<ulong>>(
NadekoBot.AllGuildConfigs
.ToDictionary(x => x.GuildId,
x => new HashSet<ulong>(x.SlowmodeIgnoredUsers.Select(y => y.UserId))));
NadekoBot.Client.MessageReceived += async (umsg) =>
{
try
{
var usrMsg = umsg as SocketUserMessage;
var channel = usrMsg?.Channel as SocketTextChannel;
if (channel == null || usrMsg.IsAuthor())
return;
if (!RatelimitingChannels.TryGetValue(channel.Id, out Ratelimiter limiter))
return;
if (limiter.CheckUserRatelimit(usrMsg.Author.Id, channel.Guild.Id, usrMsg.Author as SocketGuildUser))
await usrMsg.DeleteAsync();
}
catch (Exception ex) { _log.Warn(ex); }
};
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -107,7 +36,7 @@ namespace NadekoBot.Modules.Administration
[RequireUserPermission(GuildPermission.ManageMessages)] [RequireUserPermission(GuildPermission.ManageMessages)]
public async Task Slowmode() public async Task Slowmode()
{ {
if (RatelimitingChannels.TryRemove(Context.Channel.Id, out Ratelimiter throwaway)) if (_service.RatelimitingChannels.TryRemove(Context.Channel.Id, out Ratelimiter throwaway))
{ {
throwaway.CancelSource.Cancel(); throwaway.CancelSource.Cancel();
await ReplyConfirmLocalized("slowmode_disabled").ConfigureAwait(false); await ReplyConfirmLocalized("slowmode_disabled").ConfigureAwait(false);
@ -126,13 +55,13 @@ namespace NadekoBot.Modules.Administration
await ReplyErrorLocalized("invalid_params").ConfigureAwait(false); await ReplyErrorLocalized("invalid_params").ConfigureAwait(false);
return; return;
} }
var toAdd = new Ratelimiter() var toAdd = new Ratelimiter(_service)
{ {
ChannelId = Context.Channel.Id, ChannelId = Context.Channel.Id,
MaxMessages = msg, MaxMessages = msg,
PerSeconds = perSec, PerSeconds = perSec,
}; };
if(RatelimitingChannels.TryAdd(Context.Channel.Id, toAdd)) if(_service.RatelimitingChannels.TryAdd(Context.Channel.Id, toAdd))
{ {
await Context.Channel.SendConfirmAsync(GetText("slowmode_init"), await Context.Channel.SendConfirmAsync(GetText("slowmode_init"),
GetText("slowmode_desc", Format.Bold(toAdd.MaxMessages.ToString()), Format.Bold(toAdd.PerSeconds.ToString()))) GetText("slowmode_desc", Format.Bold(toAdd.MaxMessages.ToString()), Format.Bold(toAdd.PerSeconds.ToString())))
@ -153,7 +82,7 @@ namespace NadekoBot.Modules.Administration
HashSet<SlowmodeIgnoredUser> usrs; HashSet<SlowmodeIgnoredUser> usrs;
bool removed; bool removed;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
usrs = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.SlowmodeIgnoredUsers)) usrs = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.SlowmodeIgnoredUsers))
.SlowmodeIgnoredUsers; .SlowmodeIgnoredUsers;
@ -164,7 +93,7 @@ namespace NadekoBot.Modules.Administration
await uow.CompleteAsync().ConfigureAwait(false); await uow.CompleteAsync().ConfigureAwait(false);
} }
IgnoredUsers.AddOrUpdate(Context.Guild.Id, new HashSet<ulong>(usrs.Select(x => x.UserId)), (key, old) => new HashSet<ulong>(usrs.Select(x => x.UserId))); _service.IgnoredUsers.AddOrUpdate(Context.Guild.Id, new HashSet<ulong>(usrs.Select(x => x.UserId)), (key, old) => new HashSet<ulong>(usrs.Select(x => x.UserId)));
if(removed) if(removed)
await ReplyConfirmLocalized("slowmodewl_user_stop", Format.Bold(user.ToString())).ConfigureAwait(false); await ReplyConfirmLocalized("slowmodewl_user_stop", Format.Bold(user.ToString())).ConfigureAwait(false);
@ -185,7 +114,7 @@ namespace NadekoBot.Modules.Administration
HashSet<SlowmodeIgnoredRole> roles; HashSet<SlowmodeIgnoredRole> roles;
bool removed; bool removed;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
roles = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.SlowmodeIgnoredRoles)) roles = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.SlowmodeIgnoredRoles))
.SlowmodeIgnoredRoles; .SlowmodeIgnoredRoles;
@ -196,7 +125,7 @@ namespace NadekoBot.Modules.Administration
await uow.CompleteAsync().ConfigureAwait(false); await uow.CompleteAsync().ConfigureAwait(false);
} }
IgnoredRoles.AddOrUpdate(Context.Guild.Id, new HashSet<ulong>(roles.Select(x => x.RoleId)), (key, old) => new HashSet<ulong>(roles.Select(x => x.RoleId))); _service.IgnoredRoles.AddOrUpdate(Context.Guild.Id, new HashSet<ulong>(roles.Select(x => x.RoleId)), (key, old) => new HashSet<ulong>(roles.Select(x => x.RoleId)));
if (removed) if (removed)
await ReplyConfirmLocalized("slowmodewl_role_stop", Format.Bold(role.ToString())).ConfigureAwait(false); await ReplyConfirmLocalized("slowmodewl_role_stop", Format.Bold(role.ToString())).ConfigureAwait(false);

View File

@ -18,6 +18,12 @@ namespace NadekoBot.Modules.Administration
[Group] [Group]
public class SelfAssignedRolesCommands : NadekoSubmodule public class SelfAssignedRolesCommands : NadekoSubmodule
{ {
private readonly DbHandler _db;
public SelfAssignedRolesCommands(DbHandler db)
{
_db = db;
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
@ -25,7 +31,7 @@ namespace NadekoBot.Modules.Administration
public async Task AdSarm() public async Task AdSarm()
{ {
bool newval; bool newval;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var config = uow.GuildConfigs.For(Context.Guild.Id, set => set); var config = uow.GuildConfigs.For(Context.Guild.Id, set => set);
newval = config.AutoDeleteSelfAssignedRoleMessages = !config.AutoDeleteSelfAssignedRoleMessages; newval = config.AutoDeleteSelfAssignedRoleMessages = !config.AutoDeleteSelfAssignedRoleMessages;
@ -49,7 +55,7 @@ namespace NadekoBot.Modules.Administration
string msg; string msg;
var error = false; var error = false;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
roles = uow.SelfAssignedRoles.GetFromGuild(Context.Guild.Id); roles = uow.SelfAssignedRoles.GetFromGuild(Context.Guild.Id);
if (roles.Any(s => s.RoleId == role.Id && s.GuildId == role.Guild.Id)) if (roles.Any(s => s.RoleId == role.Id && s.GuildId == role.Guild.Id))
@ -84,7 +90,7 @@ namespace NadekoBot.Modules.Administration
return; return;
bool success; bool success;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
success = uow.SelfAssignedRoles.DeleteByGuildAndRoleId(role.Guild.Id, role.Id); success = uow.SelfAssignedRoles.DeleteByGuildAndRoleId(role.Guild.Id, role.Id);
await uow.CompleteAsync(); await uow.CompleteAsync();
@ -105,7 +111,7 @@ namespace NadekoBot.Modules.Administration
var removeMsg = new StringBuilder(); var removeMsg = new StringBuilder();
var msg = new StringBuilder(); var msg = new StringBuilder();
var roleCnt = 0; var roleCnt = 0;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var roleModels = uow.SelfAssignedRoles.GetFromGuild(Context.Guild.Id).ToList(); var roleModels = uow.SelfAssignedRoles.GetFromGuild(Context.Guild.Id).ToList();
msg.AppendLine(); msg.AppendLine();
@ -139,7 +145,7 @@ namespace NadekoBot.Modules.Administration
public async Task Tesar() public async Task Tesar()
{ {
bool areExclusive; bool areExclusive;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var config = uow.GuildConfigs.For(Context.Guild.Id, set => set); var config = uow.GuildConfigs.For(Context.Guild.Id, set => set);
@ -160,7 +166,7 @@ namespace NadekoBot.Modules.Administration
GuildConfig conf; GuildConfig conf;
SelfAssignedRole[] roles; SelfAssignedRole[] roles;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
conf = uow.GuildConfigs.For(Context.Guild.Id, set => set); conf = uow.GuildConfigs.For(Context.Guild.Id, set => set);
roles = uow.SelfAssignedRoles.GetFromGuild(Context.Guild.Id).ToArray(); roles = uow.SelfAssignedRoles.GetFromGuild(Context.Guild.Id).ToArray();
@ -220,7 +226,7 @@ namespace NadekoBot.Modules.Administration
bool autoDeleteSelfAssignedRoleMessages; bool autoDeleteSelfAssignedRoleMessages;
IEnumerable<SelfAssignedRole> roles; IEnumerable<SelfAssignedRole> roles;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
autoDeleteSelfAssignedRoleMessages = uow.GuildConfigs.For(Context.Guild.Id, set => set).AutoDeleteSelfAssignedRoleMessages; autoDeleteSelfAssignedRoleMessages = uow.GuildConfigs.For(Context.Guild.Id, set => set).AutoDeleteSelfAssignedRoleMessages;
roles = uow.SelfAssignedRoles.GetFromGuild(Context.Guild.Id); roles = uow.SelfAssignedRoles.GetFromGuild(Context.Guild.Id);

View File

@ -25,7 +25,6 @@ namespace NadekoBot.Modules.Administration
public UserPunishCommands(DbHandler db, MuteService muteService) public UserPunishCommands(DbHandler db, MuteService muteService)
{ {
_db = db; _db = db;
_muteService = muteService; _muteService = muteService;
} }

View File

@ -7,37 +7,25 @@ using NadekoBot.Services.Database.Models;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Discord; using Discord;
using NLog;
using System; using System;
using Newtonsoft.Json; using NadekoBot.Services.Pokemon;
using System.IO;
using System.Collections.Concurrent;
namespace NadekoBot.Modules.Pokemon namespace NadekoBot.Modules.Pokemon
{ {
[NadekoModule("Pokemon", ">")]
public class Pokemon : NadekoTopLevelModule public class Pokemon : NadekoTopLevelModule
{ {
private static readonly List<PokemonType> _pokemonTypes = new List<PokemonType>(); private readonly PokemonService _service;
private static readonly ConcurrentDictionary<ulong, PokeStats> _stats = new ConcurrentDictionary<ulong, PokeStats>(); private readonly DbHandler _db;
private readonly BotConfig _bc;
private readonly CurrencyHandler _ch;
public const string PokemonTypesFile = "data/pokemon_types.json"; public Pokemon(PokemonService pokemonService, DbHandler db, BotConfig bc, CurrencyHandler ch)
private new static Logger _log { get; }
static Pokemon()
{ {
_log = LogManager.GetCurrentClassLogger(); _service = pokemonService;
if (File.Exists(PokemonTypesFile)) _db = db;
{ _bc = bc;
_pokemonTypes = JsonConvert.DeserializeObject<List<PokemonType>>(File.ReadAllText(PokemonTypesFile)); _ch = ch;
} }
else
{
_log.Warn(PokemonTypesFile + " is missing. Pokemon types not loaded.");
}
}
private int GetDamage(PokemonType usertype, PokemonType targetType) private int GetDamage(PokemonType usertype, PokemonType targetType)
{ {
@ -52,12 +40,11 @@ namespace NadekoBot.Modules.Pokemon
return damage; return damage;
} }
private PokemonType GetPokeType(ulong id)
private static PokemonType GetPokeType(ulong id)
{ {
Dictionary<ulong, string> setTypes; Dictionary<ulong, string> setTypes;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
setTypes = uow.PokeGame.GetAll().ToDictionary(x => x.UserId, y => y.type); setTypes = uow.PokeGame.GetAll().ToDictionary(x => x.UserId, y => y.type);
} }
@ -66,17 +53,17 @@ namespace NadekoBot.Modules.Pokemon
{ {
return StringToPokemonType(setTypes[id]); return StringToPokemonType(setTypes[id]);
} }
var count = _pokemonTypes.Count; var count = _service.PokemonTypes.Count;
var remainder = Math.Abs((int)(id % (ulong)count)); var remainder = Math.Abs((int)(id % (ulong)count));
return _pokemonTypes[remainder]; return _service.PokemonTypes[remainder];
} }
private static PokemonType StringToPokemonType(string v) private PokemonType StringToPokemonType(string v)
{ {
var str = v?.ToUpperInvariant(); var str = v?.ToUpperInvariant();
var list = _pokemonTypes; var list = _service.PokemonTypes;
foreach (var p in list) foreach (var p in list)
{ {
if (str == p.Name) if (str == p.Name)
@ -111,7 +98,7 @@ namespace NadekoBot.Modules.Pokemon
// Checking stats first, then move // Checking stats first, then move
//Set up the userstats //Set up the userstats
var userStats = _stats.GetOrAdd(user.Id, new PokeStats()); var userStats = _service.Stats.GetOrAdd(user.Id, new PokeStats());
//Check if able to move //Check if able to move
//User not able if HP < 0, has made more than 4 attacks //User not able if HP < 0, has made more than 4 attacks
@ -131,7 +118,7 @@ namespace NadekoBot.Modules.Pokemon
return; return;
} }
//get target stats //get target stats
var targetStats = _stats.GetOrAdd(targetUser.Id, new PokeStats()); var targetStats = _service.Stats.GetOrAdd(targetUser.Id, new PokeStats());
//If target's HP is below 0, no use attacking //If target's HP is below 0, no use attacking
if (targetStats.Hp <= 0) if (targetStats.Hp <= 0)
@ -195,8 +182,8 @@ namespace NadekoBot.Modules.Pokemon
//update dictionary //update dictionary
//This can stay the same right? //This can stay the same right?
_stats[user.Id] = userStats; _service.Stats[user.Id] = userStats;
_stats[targetUser.Id] = targetStats; _service.Stats[targetUser.Id] = targetStats;
await Context.Channel.SendConfirmAsync(Context.User.Mention + " " + response).ConfigureAwait(false); await Context.Channel.SendConfirmAsync(Context.User.Mention + " " + response).ConfigureAwait(false);
} }
@ -228,9 +215,9 @@ namespace NadekoBot.Modules.Pokemon
return; return;
} }
if (_stats.ContainsKey(targetUser.Id)) if (_service.Stats.ContainsKey(targetUser.Id))
{ {
var targetStats = _stats[targetUser.Id]; var targetStats = _service.Stats[targetUser.Id];
if (targetStats.Hp == targetStats.MaxHp) if (targetStats.Hp == targetStats.MaxHp)
{ {
await ReplyErrorLocalized("already_full", Format.Bold(targetUser.ToString())).ConfigureAwait(false); await ReplyErrorLocalized("already_full", Format.Bold(targetUser.ToString())).ConfigureAwait(false);
@ -242,9 +229,9 @@ namespace NadekoBot.Modules.Pokemon
var target = (targetUser.Id == user.Id) ? "yourself" : targetUser.Mention; var target = (targetUser.Id == user.Id) ? "yourself" : targetUser.Mention;
if (amount > 0) if (amount > 0)
{ {
if (!await CurrencyHandler.RemoveCurrencyAsync(user, $"Poke-Heal {target}", amount, true).ConfigureAwait(false)) if (!await _ch.RemoveCurrencyAsync(user, $"Poke-Heal {target}", amount, true).ConfigureAwait(false))
{ {
await ReplyErrorLocalized("no_currency", NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false); await ReplyErrorLocalized("no_currency", _bc.CurrencySign).ConfigureAwait(false);
return; return;
} }
} }
@ -254,16 +241,16 @@ namespace NadekoBot.Modules.Pokemon
if (targetStats.Hp < 0) if (targetStats.Hp < 0)
{ {
//Could heal only for half HP? //Could heal only for half HP?
_stats[targetUser.Id].Hp = (targetStats.MaxHp / 2); _service.Stats[targetUser.Id].Hp = (targetStats.MaxHp / 2);
if (target == "yourself") if (target == "yourself")
{ {
await ReplyConfirmLocalized("revive_yourself", NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false); await ReplyConfirmLocalized("revive_yourself", _bc.CurrencySign).ConfigureAwait(false);
return; return;
} }
await ReplyConfirmLocalized("revive_other", Format.Bold(targetUser.ToString()), NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false); await ReplyConfirmLocalized("revive_other", Format.Bold(targetUser.ToString()), _bc.CurrencySign).ConfigureAwait(false);
} }
await ReplyConfirmLocalized("healed", Format.Bold(targetUser.ToString()), NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false); await ReplyConfirmLocalized("healed", Format.Bold(targetUser.ToString()), _bc.CurrencySign).ConfigureAwait(false);
} }
else else
{ {
@ -291,7 +278,7 @@ namespace NadekoBot.Modules.Pokemon
var targetType = StringToPokemonType(typeTargeted); var targetType = StringToPokemonType(typeTargeted);
if (targetType == null) if (targetType == null)
{ {
await Context.Channel.EmbedAsync(_pokemonTypes.Aggregate(new EmbedBuilder().WithDescription("List of the available types:"), await Context.Channel.EmbedAsync(_service.PokemonTypes.Aggregate(new EmbedBuilder().WithDescription("List of the available types:"),
(eb, pt) => eb.AddField(efb => efb.WithName(pt.Name) (eb, pt) => eb.AddField(efb => efb.WithName(pt.Name)
.WithValue(pt.Icon) .WithValue(pt.Icon)
.WithIsInline(true))) .WithIsInline(true)))
@ -308,16 +295,16 @@ namespace NadekoBot.Modules.Pokemon
var amount = 1; var amount = 1;
if (amount > 0) if (amount > 0)
{ {
if (!await CurrencyHandler.RemoveCurrencyAsync(user, $"{user} change type to {typeTargeted}", amount, true).ConfigureAwait(false)) if (!await _ch.RemoveCurrencyAsync(user, $"{user} change type to {typeTargeted}", amount, true).ConfigureAwait(false))
{ {
await ReplyErrorLocalized("no_currency", NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false); await ReplyErrorLocalized("no_currency", _bc.CurrencySign).ConfigureAwait(false);
return; return;
} }
} }
//Actually changing the type here //Actually changing the type here
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var pokeUsers = uow.PokeGame.GetAll().ToArray(); var pokeUsers = uow.PokeGame.GetAll().ToArray();
var setTypes = pokeUsers.ToDictionary(x => x.UserId, y => y.type); var setTypes = pokeUsers.ToDictionary(x => x.UserId, y => y.type);
@ -344,7 +331,7 @@ namespace NadekoBot.Modules.Pokemon
//Now for the response //Now for the response
await ReplyConfirmLocalized("settype_success", await ReplyConfirmLocalized("settype_success",
targetType, targetType,
NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false); _bc.CurrencySign).ConfigureAwait(false);
} }
} }
} }

View File

@ -97,8 +97,12 @@ namespace NadekoBot
var soundcloud = new SoundCloudApiService(credentials); var soundcloud = new SoundCloudApiService(credentials);
//module services //module services
//todo 90 - Make this automatic
var utilityService = new UtilityService(AllGuildConfigs, Client, BotConfig, db); var utilityService = new UtilityService(AllGuildConfigs, Client, BotConfig, db);
#region Searches
var searchesService = new SearchesService(Client, google, db); var searchesService = new SearchesService(Client, google, db);
var streamNotificationService = new StreamNotificationService(db, Client, strings);
#endregion
var clashService = new ClashOfClansService(Client, db, localization, strings); var clashService = new ClashOfClansService(Client, db, localization, strings);
var musicService = new MusicService(google, strings, localization, db, soundcloud, credentials); var musicService = new MusicService(google, strings, localization, db, soundcloud, credentials);
var crService = new CustomReactionsService(db, Client); var crService = new CustomReactionsService(db, Client);
@ -108,11 +112,16 @@ namespace NadekoBot
var selfService = new SelfService(this, commandHandler, db, BotConfig); var selfService = new SelfService(this, commandHandler, db, BotConfig);
var vcRoleService = new VcRoleService(Client, AllGuildConfigs); var vcRoleService = new VcRoleService(Client, AllGuildConfigs);
var vPlusTService = new VplusTService(Client, AllGuildConfigs, strings, db); var vPlusTService = new VplusTService(Client, AllGuildConfigs, strings, db);
var muteService = new MuteService(Client, AllGuildConfigs, db);
var ratelimitService = new RatelimitService(Client, AllGuildConfigs);
var protectionService = new ProtectionService(Client, AllGuildConfigs, muteService);
var playingRotateService = new PlayingRotateService(Client, BotConfig, musicService);
var gameVcService = new GameVoiceChannelService(Client, db, AllGuildConfigs);
var autoAssignRoleService = new AutoAssignRoleService(Client, AllGuildConfigs);
#endregion #endregion
//initialize Services //initialize Services
Services = new NServiceProvider.ServiceProviderBuilder() //todo all Adds should be interfaces Services = new NServiceProvider.ServiceProviderBuilder()
.Add<ILocalization>(localization) .Add<ILocalization>(localization)
.Add<IStatsService>(stats) .Add<IStatsService>(stats)
.Add<IImagesService>(images) .Add<IImagesService>(images)
@ -129,14 +138,22 @@ namespace NadekoBot
//modules //modules
.Add<UtilityService>(utilityService) .Add<UtilityService>(utilityService)
.Add<SearchesService>(searchesService) .Add<SearchesService>(searchesService)
.Add(streamNotificationService)
.Add<ClashOfClansService>(clashService) .Add<ClashOfClansService>(clashService)
.Add<MusicService>(musicService) .Add<MusicService>(musicService)
.Add<GreetSettingsService>(greetSettingsService) .Add<GreetSettingsService>(greetSettingsService)
.Add<CustomReactionsService>(crService) .Add<CustomReactionsService>(crService)
.Add<GamesService>(gamesService) .Add<GamesService>(gamesService)
.Add<AdministrationService>(administrationService)
.Add(selfService) .Add(selfService)
.Add(vcRoleService) .Add(vcRoleService)
.Add(vPlusTService) .Add(vPlusTService)
.Add(muteService)
.Add(ratelimitService)
.Add(playingRotateService)
.Add(gameVcService)
.Add(autoAssignRoleService)
.Add(protectionService)
.Build(); .Build();
commandHandler.AddServices(Services); commandHandler.AddServices(Services);

View File

@ -39,7 +39,17 @@
<None Remove="Modules\Permissions\**" /> <None Remove="Modules\Permissions\**" />
<Compile Remove="Modules\Gambling\Commands\Lucky7Commands.cs" /> <Compile Remove="Modules\Gambling\Commands\Lucky7Commands.cs" />
<Compile Include="Modules\Administration\Administration.cs" /> <Compile Include="Modules\Administration\Administration.cs" />
<Compile Include="Modules\Administration\Commands\AutoAssignRoleCommands.cs" />
<Compile Include="Modules\Administration\Commands\GameChannelCommands.cs" />
<Compile Include="Modules\Administration\Commands\LocalizationCommands.cs" />
<Compile Include="Modules\Administration\Commands\Migration.cs" />
<Compile Include="Modules\Administration\Commands\Migration\0_9..cs" />
<Compile Include="Modules\Administration\Commands\Migration\MigrationException.cs" />
<Compile Include="Modules\Administration\Commands\MuteCommands.cs" /> <Compile Include="Modules\Administration\Commands\MuteCommands.cs" />
<Compile Include="Modules\Administration\Commands\PlayingRotateCommands.cs" />
<Compile Include="Modules\Administration\Commands\ProtectionCommands.cs" />
<Compile Include="Modules\Administration\Commands\RatelimitCommand.cs" />
<Compile Include="Modules\Administration\Commands\SelfAssignedRolesCommand.cs" />
<Compile Include="Modules\Administration\Commands\SelfCommands.cs" /> <Compile Include="Modules\Administration\Commands\SelfCommands.cs" />
<Compile Include="Modules\Administration\Commands\ServerGreetCommands.cs" /> <Compile Include="Modules\Administration\Commands\ServerGreetCommands.cs" />
<Compile Include="Modules\Administration\Commands\UserPunishCommands.cs" /> <Compile Include="Modules\Administration\Commands\UserPunishCommands.cs" />
@ -61,7 +71,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AngleSharp" Version="0.9.9" /> <PackageReference Include="AngleSharp" Version="0.9.9" />
<PackageReference Include="Discord.Net" Version="1.0.0-rc3-00743" /> <PackageReference Include="Discord.Net" Version="1.0.0-rc3-00746" />
<PackageReference Include="libvideo" Version="1.0.1" /> <PackageReference Include="libvideo" Version="1.0.1" />
<PackageReference Include="CoreCLR-NCalc" Version="2.1.2" /> <PackageReference Include="CoreCLR-NCalc" Version="2.1.2" />
<PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.19.0.138" /> <PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.19.0.138" />

View File

@ -0,0 +1,46 @@
using Discord.WebSocket;
using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace NadekoBot.Services.Administration
{
public class AutoAssignRoleService
{
private readonly Logger _log;
private readonly DiscordShardedClient _client;
//guildid/roleid
public ConcurrentDictionary<ulong, ulong> AutoAssignedRoles { get; }
public AutoAssignRoleService(DiscordShardedClient client, IEnumerable<GuildConfig> gcs)
{
_log = LogManager.GetCurrentClassLogger();
_client = client;
AutoAssignedRoles = new ConcurrentDictionary<ulong, ulong>(
gcs.Where(x => x.AutoAssignRoleId != 0)
.ToDictionary(k => k.GuildId, v => v.AutoAssignRoleId));
_client.UserJoined += async (user) =>
{
try
{
AutoAssignedRoles.TryGetValue(user.Guild.Id, out ulong roleId);
if (roleId == 0)
return;
var role = user.Guild.Roles.FirstOrDefault(r => r.Id == roleId);
if (role != null)
await user.AddRoleAsync(role).ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex); }
};
}
}
}

View File

@ -0,0 +1,73 @@
using Discord.WebSocket;
using NadekoBot.Extensions;
using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Services.Administration
{
public class GameVoiceChannelService
{
public readonly ConcurrentHashSet<ulong> GameVoiceChannels = new ConcurrentHashSet<ulong>();
private readonly Logger _log;
private readonly DbHandler _db;
private readonly DiscordShardedClient _client;
public GameVoiceChannelService(DiscordShardedClient client, DbHandler db, IEnumerable<GuildConfig> gcs)
{
_log = LogManager.GetCurrentClassLogger();
_db = db;
_client = client;
GameVoiceChannels = new ConcurrentHashSet<ulong>(
gcs.Where(gc => gc.GameVoiceChannel != null)
.Select(gc => gc.GameVoiceChannel.Value));
_client.UserVoiceStateUpdated += Client_UserVoiceStateUpdated;
}
private Task Client_UserVoiceStateUpdated(SocketUser usr, SocketVoiceState oldState, SocketVoiceState newState)
{
var _ = Task.Run(async () =>
{
try
{
var gUser = usr as SocketGuildUser;
if (gUser == null)
return;
var game = gUser.Game?.Name.TrimTo(50).ToLowerInvariant();
if (oldState.VoiceChannel == newState.VoiceChannel ||
newState.VoiceChannel == null)
return;
if (!GameVoiceChannels.Contains(newState.VoiceChannel.Id) ||
string.IsNullOrWhiteSpace(game))
return;
var vch = gUser.Guild.VoiceChannels
.FirstOrDefault(x => x.Name.ToLowerInvariant() == game);
if (vch == null)
return;
await Task.Delay(1000).ConfigureAwait(false);
await gUser.ModifyAsync(gu => gu.Channel = vch).ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
}
});
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,104 @@
using Discord.WebSocket;
using NadekoBot.Extensions;
using NadekoBot.Services.Database.Models;
using NadekoBot.Services.Music;
using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Services.Administration
{
//todo 99 - Could make a placeholder service, which can work for any module
//and have replacements which are dependent on the types provided in the constructor
public class PlayingRotateService
{
public List<PlayingStatus> RotatingStatusMessages { get; }
public volatile bool RotatingStatuses;
private readonly Timer _t;
private readonly DiscordShardedClient _client;
private readonly BotConfig _bc;
private readonly MusicService _music;
private readonly Logger _log;
private class TimerState
{
public int Index { get; set; }
}
public PlayingRotateService(DiscordShardedClient client, BotConfig bc, MusicService music)
{
_client = client;
_bc = bc;
_music = music;
_log = LogManager.GetCurrentClassLogger();
RotatingStatusMessages = _bc.RotatingStatusMessages;
RotatingStatuses = _bc.RotatingStatuses;
_t = new Timer(async (objState) =>
{
try
{
var state = (TimerState)objState;
if (!RotatingStatuses)
return;
if (state.Index >= RotatingStatusMessages.Count)
state.Index = 0;
if (!RotatingStatusMessages.Any())
return;
var status = RotatingStatusMessages[state.Index++].Status;
if (string.IsNullOrWhiteSpace(status))
return;
PlayingPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value(_client,_music)));
var shards = _client.Shards;
for (int i = 0; i < shards.Count; i++)
{
var curShard = shards.ElementAt(i);
ShardSpecificPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value(curShard)));
try { await shards.ElementAt(i).SetGameAsync(status).ConfigureAwait(false); }
catch (Exception ex)
{
_log.Warn(ex);
}
}
}
catch (Exception ex)
{
_log.Warn("Rotating playing status errored.\n" + ex);
}
}, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
}
public Dictionary<string, Func<DiscordShardedClient, MusicService, string>> PlayingPlaceholders { get; } =
new Dictionary<string, Func<DiscordShardedClient, MusicService, string>> {
{ "%servers%", (c, ms) => c.Guilds.Count.ToString()},
{ "%users%", (c, ms) => c.Guilds.Sum(s => s.Users.Count).ToString()},
{ "%playing%", (c, ms) => {
var cnt = ms.MusicPlayers.Count(kvp => kvp.Value.CurrentSong != null);
if (cnt != 1) return cnt.ToString();
try {
var mp = ms.MusicPlayers.FirstOrDefault();
return mp.Value.CurrentSong.SongInfo.Title;
}
catch {
return "No songs";
}
}
},
{ "%queued%", (c, ms) => ms.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()},
{ "%time%", (c, ms) => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()) },
{ "%shardcount%", (c, ms) => c.Shards.Count.ToString() },
};
public Dictionary<string, Func<DiscordSocketClient, string>> ShardSpecificPlaceholders { get; } =
new Dictionary<string, Func<DiscordSocketClient, string>> {
{ "%shardid%", (client) => client.ShardId.ToString()},
{ "%shardguilds%", (client) => client.Guilds.Count.ToString()},
};
}
}

View File

@ -0,0 +1,182 @@
using Discord;
using Discord.WebSocket;
using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Services.Administration
{
public class ProtectionService
{
public readonly ConcurrentDictionary<ulong, AntiRaidStats> AntiRaidGuilds =
new ConcurrentDictionary<ulong, AntiRaidStats>();
// guildId | (userId|messages)
public readonly ConcurrentDictionary<ulong, AntiSpamStats> AntiSpamGuilds =
new ConcurrentDictionary<ulong, AntiSpamStats>();
//todo sub LogCommands to this
public event Func<IEnumerable<IGuildUser>, PunishmentAction, ProtectionType, Task> OnAntiProtectionTriggered = delegate { return Task.CompletedTask; };
private readonly Logger _log;
private readonly DiscordShardedClient _client;
private readonly MuteService _mute;
public ProtectionService(DiscordShardedClient client, IEnumerable<GuildConfig> gcs, MuteService mute)
{
_log = LogManager.GetCurrentClassLogger();
_client = client;
_mute = mute;
foreach (var gc in gcs)
{
var raid = gc.AntiRaidSetting;
var spam = gc.AntiSpamSetting;
if (raid != null)
{
var raidStats = new AntiRaidStats() { AntiRaidSettings = raid };
AntiRaidGuilds.TryAdd(gc.GuildId, raidStats);
}
if (spam != null)
AntiSpamGuilds.TryAdd(gc.GuildId, new AntiSpamStats() { AntiSpamSettings = spam });
}
_client.MessageReceived += (imsg) =>
{
var msg = imsg as IUserMessage;
if (msg == null || msg.Author.IsBot)
return Task.CompletedTask;
var channel = msg.Channel as ITextChannel;
if (channel == null)
return Task.CompletedTask;
var _ = Task.Run(async () =>
{
try
{
if (!AntiSpamGuilds.TryGetValue(channel.Guild.Id, out var spamSettings) ||
spamSettings.AntiSpamSettings.IgnoredChannels.Contains(new AntiSpamIgnore()
{
ChannelId = channel.Id
}))
return;
var stats = spamSettings.UserStats.AddOrUpdate(msg.Author.Id, (id) => new UserSpamStats(msg),
(id, old) =>
{
old.ApplyNextMessage(msg); return old;
});
if (stats.Count >= spamSettings.AntiSpamSettings.MessageThreshold)
{
if (spamSettings.UserStats.TryRemove(msg.Author.Id, out stats))
{
stats.Dispose();
await PunishUsers(spamSettings.AntiSpamSettings.Action, ProtectionType.Spamming, (IGuildUser)msg.Author)
.ConfigureAwait(false);
}
}
}
catch
{
// ignored
}
});
return Task.CompletedTask;
};
_client.UserJoined += (usr) =>
{
if (usr.IsBot)
return Task.CompletedTask;
if (!AntiRaidGuilds.TryGetValue(usr.Guild.Id, out var settings))
return Task.CompletedTask;
if (!settings.RaidUsers.Add(usr))
return Task.CompletedTask;
var _ = Task.Run(async () =>
{
try
{
++settings.UsersCount;
if (settings.UsersCount >= settings.AntiRaidSettings.UserThreshold)
{
var users = settings.RaidUsers.ToArray();
settings.RaidUsers.Clear();
await PunishUsers(settings.AntiRaidSettings.Action, ProtectionType.Raiding, users).ConfigureAwait(false);
}
await Task.Delay(1000 * settings.AntiRaidSettings.Seconds).ConfigureAwait(false);
settings.RaidUsers.TryRemove(usr);
--settings.UsersCount;
}
catch
{
// ignored
}
});
return Task.CompletedTask;
};
}
private async Task PunishUsers(PunishmentAction action, ProtectionType pt, params IGuildUser[] gus)
{
_log.Info($"[{pt}] - Punishing [{gus.Length}] users with [{action}] in {gus[0].Guild.Name} guild");
foreach (var gu in gus)
{
switch (action)
{
case PunishmentAction.Mute:
try
{
await _mute.MuteUser(gu).ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex, "I can't apply punishement"); }
break;
case PunishmentAction.Kick:
try
{
await gu.KickAsync().ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex, "I can't apply punishement"); }
break;
case PunishmentAction.Softban:
try
{
await gu.Guild.AddBanAsync(gu, 7).ConfigureAwait(false);
try
{
await gu.Guild.RemoveBanAsync(gu).ConfigureAwait(false);
}
catch
{
await gu.Guild.RemoveBanAsync(gu).ConfigureAwait(false);
// try it twice, really don't want to ban user if
// only kick has been specified as the punishement
}
}
catch (Exception ex) { _log.Warn(ex, "I can't apply punishment"); }
break;
case PunishmentAction.Ban:
try
{
await gu.Guild.AddBanAsync(gu, 7).ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex, "I can't apply punishment"); }
break;
}
}
await OnAntiProtectionTriggered(gus, action, pt).ConfigureAwait(false);
}
}
}

View File

@ -0,0 +1,26 @@
using Discord;
using NadekoBot.Services.Database.Models;
using System.Collections.Concurrent;
namespace NadekoBot.Services.Administration
{
public enum ProtectionType
{
Raiding,
Spamming,
}
public class AntiRaidStats
{
public AntiRaidSetting AntiRaidSettings { get; set; }
public int UsersCount { get; set; }
public ConcurrentHashSet<IGuildUser> RaidUsers { get; set; } = new ConcurrentHashSet<IGuildUser>();
}
public class AntiSpamStats
{
public AntiSpamSetting AntiSpamSettings { get; set; }
public ConcurrentDictionary<ulong, UserSpamStats> UserStats { get; set; }
= new ConcurrentDictionary<ulong, UserSpamStats>();
}
}

View File

@ -0,0 +1,53 @@
using Discord.WebSocket;
using NadekoBot.Extensions;
using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace NadekoBot.Services.Administration
{
public class RatelimitService
{
public ConcurrentDictionary<ulong, Ratelimiter> RatelimitingChannels = new ConcurrentDictionary<ulong, Ratelimiter>();
public ConcurrentDictionary<ulong, HashSet<ulong>> IgnoredRoles = new ConcurrentDictionary<ulong, HashSet<ulong>>();
public ConcurrentDictionary<ulong, HashSet<ulong>> IgnoredUsers = new ConcurrentDictionary<ulong, HashSet<ulong>>();
private readonly Logger _log;
private readonly DiscordShardedClient _client;
public RatelimitService(DiscordShardedClient client, IEnumerable<GuildConfig> gcs)
{
_log = LogManager.GetCurrentClassLogger();
_client = client;
IgnoredRoles = new ConcurrentDictionary<ulong, HashSet<ulong>>(
gcs.ToDictionary(x => x.GuildId,
x => new HashSet<ulong>(x.SlowmodeIgnoredRoles.Select(y => y.RoleId))));
IgnoredUsers = new ConcurrentDictionary<ulong, HashSet<ulong>>(
gcs.ToDictionary(x => x.GuildId,
x => new HashSet<ulong>(x.SlowmodeIgnoredUsers.Select(y => y.UserId))));
_client.MessageReceived += async (umsg) =>
{
try
{
var usrMsg = umsg as SocketUserMessage;
var channel = usrMsg?.Channel as SocketTextChannel;
if (channel == null || usrMsg == null || usrMsg.IsAuthor(client))
return;
if (!RatelimitingChannels.TryGetValue(channel.Id, out Ratelimiter limiter))
return;
if (limiter.CheckUserRatelimit(usrMsg.Author.Id, channel.Guild.Id, usrMsg.Author as SocketGuildUser))
await usrMsg.DeleteAsync();
}
catch (Exception ex) { _log.Warn(ex); }
};
}
}
}

View File

@ -0,0 +1,59 @@
using Discord.WebSocket;
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.Services.Administration
{
public class Ratelimiter
{
private readonly RatelimitService _svc;
public class RatelimitedUser
{
public ulong UserId { get; set; }
public int MessageCount { get; set; } = 0;
}
public ulong ChannelId { get; set; }
public int MaxMessages { get; set; }
public int PerSeconds { get; set; }
public Ratelimiter(RatelimitService svc)
{
_svc = svc;
}
public CancellationTokenSource CancelSource { get; set; } = new CancellationTokenSource();
public ConcurrentDictionary<ulong, RatelimitedUser> Users { get; set; } = new ConcurrentDictionary<ulong, RatelimitedUser>();
public bool CheckUserRatelimit(ulong id, ulong guildId, SocketGuildUser optUser)
{
if ((_svc.IgnoredUsers.TryGetValue(guildId, out HashSet<ulong> ignoreUsers) && ignoreUsers.Contains(id)) ||
(optUser != null && _svc.IgnoredRoles.TryGetValue(guildId, out HashSet<ulong> ignoreRoles) && optUser.Roles.Any(x => ignoreRoles.Contains(x.Id))))
return false;
var usr = Users.GetOrAdd(id, (key) => new RatelimitedUser() { UserId = id });
if (usr.MessageCount >= MaxMessages)
{
return true;
}
usr.MessageCount++;
var _ = Task.Run(async () =>
{
try
{
await Task.Delay(PerSeconds * 1000, CancelSource.Token);
}
catch (OperationCanceledException) { }
usr.MessageCount--;
});
return false;
}
}
}

View File

@ -13,7 +13,6 @@ namespace NadekoBot.Services.Administration
public volatile bool ForwardDMs; public volatile bool ForwardDMs;
public volatile bool ForwardDMsToAllOwners; public volatile bool ForwardDMsToAllOwners;
private readonly Logger _log;
private readonly NadekoBot _bot; private readonly NadekoBot _bot;
private readonly CommandHandler _cmdHandler; private readonly CommandHandler _cmdHandler;
private readonly DbHandler _db; private readonly DbHandler _db;

View File

@ -0,0 +1,53 @@
using Discord;
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.Services.Administration
{
public class UserSpamStats : IDisposable
{
public int Count => timers.Count;
public string LastMessage { get; set; }
private ConcurrentQueue<Timer> timers { get; }
public UserSpamStats(IUserMessage msg)
{
LastMessage = msg.Content.ToUpperInvariant();
timers = new ConcurrentQueue<Timer>();
ApplyNextMessage(msg);
}
private readonly object applyLock = new object();
public void ApplyNextMessage(IUserMessage message)
{
lock (applyLock)
{
var upperMsg = message.Content.ToUpperInvariant();
if (upperMsg != LastMessage || (string.IsNullOrWhiteSpace(upperMsg) && message.Attachments.Any()))
{
LastMessage = upperMsg;
while (timers.TryDequeue(out var old))
old.Change(Timeout.Infinite, Timeout.Infinite);
}
var t = new Timer((_) => {
if (timers.TryDequeue(out var old))
old.Change(Timeout.Infinite, Timeout.Infinite);
}, null, TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(30));
timers.Enqueue(t);
}
}
public void Dispose()
{
while (timers.TryDequeue(out var old))
old.Change(Timeout.Infinite, Timeout.Infinite);
}
}
}

View File

@ -1,8 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace NadekoBot.Modules.Pokemon namespace NadekoBot.Services.Pokemon
{ {
class PokeStats public class PokeStats
{ {
//Health left //Health left
public int Hp { get; set; } = 500; public int Hp { get; set; } = 500;

View File

@ -0,0 +1,32 @@
using Newtonsoft.Json;
using NLog;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
namespace NadekoBot.Services.Pokemon
{
public class PokemonService
{
public readonly List<PokemonType> PokemonTypes = new List<PokemonType>();
public readonly ConcurrentDictionary<ulong, PokeStats> Stats = new ConcurrentDictionary<ulong, PokeStats>();
public const string PokemonTypesFile = "data/pokemon_types.json";
private Logger _log { get; }
public PokemonService()
{
_log = LogManager.GetCurrentClassLogger();
if (File.Exists(PokemonTypesFile))
{
PokemonTypes = JsonConvert.DeserializeObject<List<PokemonType>>(File.ReadAllText(PokemonTypesFile));
}
else
{
PokemonTypes = new List<PokemonType>();
_log.Warn(PokemonTypesFile + " is missing. Pokemon types not loaded.");
}
}
}
}

View File

@ -1,6 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace NadekoBot.Modules.Pokemon namespace NadekoBot.Services.Pokemon
{ {
public class PokemonType public class PokemonType
{ {

View File

@ -18,6 +18,9 @@ namespace NadekoBot.Extensions
{ {
public static class Extensions public static class Extensions
{ {
public static bool IsAuthor(this IMessage msg, IDiscordClient client) =>
msg.Author?.Id == client.CurrentUser.Id;
private static readonly IEmote arrow_left = Emote.Parse("⬅"); private static readonly IEmote arrow_left = Emote.Parse("⬅");
private static readonly IEmote arrow_right = Emote.Parse("➡"); private static readonly IEmote arrow_right = Emote.Parse("➡");