Initial split of the modules
This commit is contained in:
@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NLog;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public class AdministrationService : INService
|
||||
{
|
||||
public readonly ConcurrentHashSet<ulong> DeleteMessagesOnCommand;
|
||||
private readonly Logger _log;
|
||||
|
||||
public AdministrationService(IEnumerable<GuildConfig> gcs, CommandHandler cmdHandler)
|
||||
{
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
DeleteMessagesOnCommand = new ConcurrentHashSet<ulong>(gcs.Where(g => g.DeleteMessageOnCommand).Select(g => g.GuildId));
|
||||
cmdHandler.CommandExecuted += DelMsgOnCmd_Handler;
|
||||
}
|
||||
|
||||
private Task DelMsgOnCmd_Handler(IUserMessage msg, CommandInfo cmd)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var channel = msg.Channel as SocketTextChannel;
|
||||
if (channel == null)
|
||||
return;
|
||||
if (DeleteMessagesOnCommand.Contains(channel.Guild.Id) && cmd.Name != "prune" && cmd.Name != "pick")
|
||||
await msg.DeleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn("Delmsgoncmd errored...");
|
||||
_log.Warn(ex);
|
||||
}
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NLog;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public class AutoAssignRoleService : INService
|
||||
{
|
||||
private readonly Logger _log;
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
//guildid/roleid
|
||||
public ConcurrentDictionary<ulong, ulong> AutoAssignedRoles { get; }
|
||||
|
||||
public AutoAssignRoleService(DiscordSocketClient 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 += (user) =>
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
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); }
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NLog;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public class GameVoiceChannelService : INService
|
||||
{
|
||||
public readonly ConcurrentHashSet<ulong> GameVoiceChannels = new ConcurrentHashSet<ulong>();
|
||||
|
||||
private readonly Logger _log;
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public GameVoiceChannelService(DiscordSocketClient client, DbService 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public class GuildTimezoneService : INService
|
||||
{
|
||||
// todo 70 this is a hack >.<
|
||||
public static ConcurrentDictionary<ulong, GuildTimezoneService> AllServices { get; } = new ConcurrentDictionary<ulong, GuildTimezoneService>();
|
||||
private ConcurrentDictionary<ulong, TimeZoneInfo> _timezones;
|
||||
private readonly DbService _db;
|
||||
|
||||
public GuildTimezoneService(DiscordSocketClient client, IEnumerable<GuildConfig> gcs, DbService db)
|
||||
{
|
||||
_timezones = gcs
|
||||
.Select(x =>
|
||||
{
|
||||
TimeZoneInfo tz;
|
||||
try
|
||||
{
|
||||
if (x.TimeZoneId == null)
|
||||
tz = null;
|
||||
else
|
||||
tz = TimeZoneInfo.FindSystemTimeZoneById(x.TimeZoneId);
|
||||
}
|
||||
catch
|
||||
{
|
||||
tz = null;
|
||||
}
|
||||
return (x.GuildId, tz);
|
||||
})
|
||||
.Where(x => x.Item2 != null)
|
||||
.ToDictionary(x => x.Item1, x => x.Item2)
|
||||
.ToConcurrent();
|
||||
|
||||
var curUser = client.CurrentUser;
|
||||
if (curUser != null)
|
||||
AllServices.TryAdd(curUser.Id, this);
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public TimeZoneInfo GetTimeZoneOrDefault(ulong guildId)
|
||||
{
|
||||
if (_timezones.TryGetValue(guildId, out var tz))
|
||||
return tz;
|
||||
return null;
|
||||
}
|
||||
|
||||
public void SetTimeZone(ulong guildId, TimeZoneInfo tz)
|
||||
{
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var gc = uow.GuildConfigs.For(guildId, set => set);
|
||||
|
||||
gc.TimeZoneId = tz?.Id;
|
||||
uow.Complete();
|
||||
|
||||
if (tz == null)
|
||||
_timezones.TryRemove(guildId, out tz);
|
||||
else
|
||||
_timezones.AddOrUpdate(guildId, tz, (key, old) => tz);
|
||||
}
|
||||
}
|
||||
|
||||
public TimeZoneInfo GetTimeZoneOrUtc(ulong guildId)
|
||||
=> GetTimeZoneOrDefault(guildId) ?? TimeZoneInfo.Utc;
|
||||
}
|
||||
}
|
1027
NadekoBot.Core/Modules/Administration/Services/LogCommandService.cs
Normal file
1027
NadekoBot.Core/Modules/Administration/Services/LogCommandService.cs
Normal file
File diff suppressed because it is too large
Load Diff
280
NadekoBot.Core/Modules/Administration/Services/MuteService.cs
Normal file
280
NadekoBot.Core/Modules/Administration/Services/MuteService.cs
Normal file
@ -0,0 +1,280 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NLog;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public enum MuteType
|
||||
{
|
||||
Voice,
|
||||
Chat,
|
||||
All
|
||||
}
|
||||
|
||||
public class MuteService : INService
|
||||
{
|
||||
public ConcurrentDictionary<ulong, string> GuildMuteRoles { get; }
|
||||
public ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> MutedUsers { get; }
|
||||
public ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, Timer>> UnmuteTimers { get; }
|
||||
= new ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, Timer>>();
|
||||
|
||||
public event Action<IGuildUser, MuteType> UserMuted = delegate { };
|
||||
public event Action<IGuildUser, MuteType> UserUnmuted = delegate { };
|
||||
|
||||
private static readonly OverwritePermissions denyOverwrite = new OverwritePermissions(addReactions: PermValue.Deny, sendMessages: PermValue.Deny, attachFiles: PermValue.Deny);
|
||||
|
||||
private readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly DbService _db;
|
||||
|
||||
public MuteService(DiscordSocketClient client, IEnumerable<GuildConfig> gcs, DbService db)
|
||||
{
|
||||
_client = client;
|
||||
_db = db;
|
||||
|
||||
GuildMuteRoles = gcs
|
||||
.Where(c => !string.IsNullOrWhiteSpace(c.MuteRoleName))
|
||||
.ToDictionary(c => c.GuildId, c => c.MuteRoleName)
|
||||
.ToConcurrent();
|
||||
|
||||
MutedUsers = new ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>>(gcs.ToDictionary(
|
||||
k => k.GuildId,
|
||||
v => new ConcurrentHashSet<ulong>(v.MutedUsers.Select(m => m.UserId))
|
||||
));
|
||||
|
||||
foreach (var conf in gcs)
|
||||
{
|
||||
foreach (var x in conf.UnmuteTimers)
|
||||
{
|
||||
TimeSpan after;
|
||||
if (x.UnmuteAt - TimeSpan.FromMinutes(2) <= DateTime.UtcNow)
|
||||
{
|
||||
after = TimeSpan.FromMinutes(2);
|
||||
}
|
||||
else
|
||||
{
|
||||
after = x.UnmuteAt - DateTime.UtcNow;
|
||||
}
|
||||
StartUnmuteTimer(conf.GuildId, x.UserId, after);
|
||||
}
|
||||
}
|
||||
|
||||
_client.UserJoined += Client_UserJoined;
|
||||
}
|
||||
|
||||
private Task Client_UserJoined(IGuildUser usr)
|
||||
{
|
||||
try
|
||||
{
|
||||
MutedUsers.TryGetValue(usr.Guild.Id, out ConcurrentHashSet<ulong> muted);
|
||||
|
||||
if (muted == null || !muted.Contains(usr.Id))
|
||||
return Task.CompletedTask;
|
||||
var _ = Task.Run(() => MuteUser(usr).ConfigureAwait(false));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn(ex);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task MuteUser(IGuildUser usr, MuteType type = MuteType.All)
|
||||
{
|
||||
if (type == MuteType.All)
|
||||
{
|
||||
await usr.ModifyAsync(x => x.Mute = true).ConfigureAwait(false);
|
||||
var muteRole = await GetMuteRole(usr.Guild);
|
||||
if (!usr.RoleIds.Contains(muteRole.Id))
|
||||
await usr.AddRoleAsync(muteRole).ConfigureAwait(false);
|
||||
StopUnmuteTimer(usr.GuildId, usr.Id);
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var config = uow.GuildConfigs.For(usr.Guild.Id,
|
||||
set => set.Include(gc => gc.MutedUsers)
|
||||
.Include(gc => gc.UnmuteTimers));
|
||||
config.MutedUsers.Add(new MutedUserId()
|
||||
{
|
||||
UserId = usr.Id
|
||||
});
|
||||
if (MutedUsers.TryGetValue(usr.Guild.Id, out ConcurrentHashSet<ulong> muted))
|
||||
muted.Add(usr.Id);
|
||||
|
||||
config.UnmuteTimers.RemoveWhere(x => x.UserId == usr.Id);
|
||||
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
UserMuted(usr, MuteType.All);
|
||||
}
|
||||
else if (type == MuteType.Voice)
|
||||
{
|
||||
await usr.ModifyAsync(x => x.Mute = true).ConfigureAwait(false);
|
||||
UserMuted(usr, MuteType.Voice);
|
||||
}
|
||||
else if (type == MuteType.Chat)
|
||||
{
|
||||
await usr.AddRoleAsync(await GetMuteRole(usr.Guild).ConfigureAwait(false)).ConfigureAwait(false);
|
||||
UserMuted(usr, MuteType.Chat);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UnmuteUser(IGuildUser usr, MuteType type = MuteType.All)
|
||||
{
|
||||
if (type == MuteType.All)
|
||||
{
|
||||
StopUnmuteTimer(usr.GuildId, usr.Id);
|
||||
try { await usr.ModifyAsync(x => x.Mute = false).ConfigureAwait(false); } catch { }
|
||||
try { await usr.RemoveRoleAsync(await GetMuteRole(usr.Guild)).ConfigureAwait(false); } catch { /*ignore*/ }
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var config = uow.GuildConfigs.For(usr.Guild.Id, set => set.Include(gc => gc.MutedUsers)
|
||||
.Include(gc => gc.UnmuteTimers));
|
||||
config.MutedUsers.Remove(new MutedUserId()
|
||||
{
|
||||
UserId = usr.Id
|
||||
});
|
||||
if (MutedUsers.TryGetValue(usr.Guild.Id, out ConcurrentHashSet<ulong> muted))
|
||||
muted.TryRemove(usr.Id);
|
||||
|
||||
config.UnmuteTimers.RemoveWhere(x => x.UserId == usr.Id);
|
||||
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
UserUnmuted(usr, MuteType.All);
|
||||
}
|
||||
else if (type == MuteType.Voice)
|
||||
{
|
||||
await usr.ModifyAsync(x => x.Mute = false).ConfigureAwait(false);
|
||||
UserUnmuted(usr, MuteType.Voice);
|
||||
}
|
||||
else if (type == MuteType.Chat)
|
||||
{
|
||||
await usr.RemoveRoleAsync(await GetMuteRole(usr.Guild).ConfigureAwait(false)).ConfigureAwait(false);
|
||||
UserUnmuted(usr, MuteType.Chat);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IRole> GetMuteRole(IGuild guild)
|
||||
{
|
||||
const string defaultMuteRoleName = "nadeko-mute";
|
||||
|
||||
var muteRoleName = GuildMuteRoles.GetOrAdd(guild.Id, defaultMuteRoleName);
|
||||
|
||||
var muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName);
|
||||
if (muteRole == null)
|
||||
{
|
||||
|
||||
//if it doesn't exist, create it
|
||||
try { muteRole = await guild.CreateRoleAsync(muteRoleName, GuildPermissions.None).ConfigureAwait(false); }
|
||||
catch
|
||||
{
|
||||
//if creations fails, maybe the name is not correct, find default one, if doesn't work, create default one
|
||||
muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName) ??
|
||||
await guild.CreateRoleAsync(defaultMuteRoleName, GuildPermissions.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var toOverwrite in (await guild.GetTextChannelsAsync()))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!toOverwrite.PermissionOverwrites.Select(x => x.Permissions).Contains(denyOverwrite))
|
||||
{
|
||||
await toOverwrite.AddPermissionOverwriteAsync(muteRole, denyOverwrite)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await Task.Delay(200).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
return muteRole;
|
||||
}
|
||||
|
||||
public async Task TimedMute(IGuildUser user, TimeSpan after)
|
||||
{
|
||||
await MuteUser(user).ConfigureAwait(false); // mute the user. This will also remove any previous unmute timers
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var config = uow.GuildConfigs.For(user.GuildId, set => set.Include(x => x.UnmuteTimers));
|
||||
config.UnmuteTimers.Add(new UnmuteTimer()
|
||||
{
|
||||
UserId = user.Id,
|
||||
UnmuteAt = DateTime.UtcNow + after,
|
||||
}); // add teh unmute timer to the database
|
||||
uow.Complete();
|
||||
}
|
||||
StartUnmuteTimer(user.GuildId, user.Id, after); // start the timer
|
||||
}
|
||||
|
||||
public void StartUnmuteTimer(ulong guildId, ulong userId, TimeSpan after)
|
||||
{
|
||||
//load the unmute timers for this guild
|
||||
var userUnmuteTimers = UnmuteTimers.GetOrAdd(guildId, new ConcurrentDictionary<ulong, Timer>());
|
||||
|
||||
//unmute timer to be added
|
||||
var toAdd = new Timer(async _ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var guild = _client.GetGuild(guildId); // load the guild
|
||||
if (guild == null)
|
||||
{
|
||||
RemoveUnmuteTimerFromDb(guildId, userId);
|
||||
return; // if guild can't be found, just remove the timer from db
|
||||
}
|
||||
// unmute the user, this will also remove the timer from the db
|
||||
await UnmuteUser(guild.GetUser(userId)).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
RemoveUnmuteTimerFromDb(guildId, userId); // if unmute errored, just remove unmute from db
|
||||
_log.Warn("Couldn't unmute user {0} in guild {1}", userId, guildId);
|
||||
_log.Warn(ex);
|
||||
}
|
||||
}, null, after, Timeout.InfiniteTimeSpan);
|
||||
|
||||
//add it, or stop the old one and add this one
|
||||
userUnmuteTimers.AddOrUpdate(userId, (key) => toAdd, (key, old) =>
|
||||
{
|
||||
old.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
return toAdd;
|
||||
});
|
||||
}
|
||||
|
||||
public void StopUnmuteTimer(ulong guildId, ulong userId)
|
||||
{
|
||||
if (!UnmuteTimers.TryGetValue(guildId, out ConcurrentDictionary<ulong, Timer> userUnmuteTimers)) return;
|
||||
|
||||
if (userUnmuteTimers.TryRemove(userId, out Timer removed))
|
||||
{
|
||||
removed.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveUnmuteTimerFromDb(ulong guildId, ulong userId)
|
||||
{
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var config = uow.GuildConfigs.For(guildId, set => set.Include(x => x.UnmuteTimers));
|
||||
config.UnmuteTimers.RemoveWhere(x => x.UserId == userId);
|
||||
uow.Complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Common.Replacements;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NLog;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public class PlayingRotateService : INService
|
||||
{
|
||||
private readonly Timer _t;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly Logger _log;
|
||||
private readonly IDataCache _cache;
|
||||
private readonly Replacer _rep;
|
||||
private readonly DbService _db;
|
||||
private readonly IBotConfigProvider _bcp;
|
||||
|
||||
public BotConfig BotConfig => _bcp.BotConfig;
|
||||
|
||||
private class TimerState
|
||||
{
|
||||
public int Index { get; set; }
|
||||
}
|
||||
|
||||
public PlayingRotateService(DiscordSocketClient client, IBotConfigProvider bcp,
|
||||
DbService db, IDataCache cache, NadekoBot bot)
|
||||
{
|
||||
_client = client;
|
||||
_bcp = bcp;
|
||||
_db = db;
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
_cache = cache;
|
||||
|
||||
if (client.ShardId == 0)
|
||||
{
|
||||
|
||||
_rep = new ReplacementBuilder()
|
||||
.WithClient(client)
|
||||
.WithStats(client)
|
||||
//todo type readers
|
||||
//.WithMusic(music)
|
||||
.Build();
|
||||
|
||||
_t = new Timer(async (objState) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
bcp.Reload();
|
||||
|
||||
var state = (TimerState)objState;
|
||||
if (!BotConfig.RotatingStatuses)
|
||||
return;
|
||||
if (state.Index >= BotConfig.RotatingStatusMessages.Count)
|
||||
state.Index = 0;
|
||||
|
||||
if (!BotConfig.RotatingStatusMessages.Any())
|
||||
return;
|
||||
var status = BotConfig.RotatingStatusMessages[state.Index++].Status;
|
||||
if (string.IsNullOrWhiteSpace(status))
|
||||
return;
|
||||
|
||||
status = _rep.Replace(status);
|
||||
|
||||
try
|
||||
{
|
||||
await bot.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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Modules.Administration.Common;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NLog;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public class ProtectionService : INService
|
||||
{
|
||||
public readonly ConcurrentDictionary<ulong, AntiRaidStats> AntiRaidGuilds =
|
||||
new ConcurrentDictionary<ulong, AntiRaidStats>();
|
||||
// guildId | (userId|messages)
|
||||
public readonly ConcurrentDictionary<ulong, AntiSpamStats> AntiSpamGuilds =
|
||||
new ConcurrentDictionary<ulong, AntiSpamStats>();
|
||||
|
||||
public event Func<PunishmentAction, ProtectionType, IGuildUser[], Task> OnAntiProtectionTriggered = delegate { return Task.CompletedTask; };
|
||||
|
||||
private readonly Logger _log;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly MuteService _mute;
|
||||
|
||||
public ProtectionService(DiscordSocketClient 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, spamSettings.AntiSpamSettings.MuteTime, (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, 0, 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, int muteTime, 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
|
||||
{
|
||||
if (muteTime <= 0)
|
||||
await _mute.MuteUser(gu).ConfigureAwait(false);
|
||||
else
|
||||
await _mute.TimedMute(gu, TimeSpan.FromSeconds(muteTime)).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(action, pt, gus).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public class PruneService : INService
|
||||
{
|
||||
//channelids where prunes are currently occuring
|
||||
private ConcurrentHashSet<ulong> _pruningGuilds = new ConcurrentHashSet<ulong>();
|
||||
private readonly TimeSpan twoWeeks = TimeSpan.FromDays(14);
|
||||
|
||||
public async Task PruneWhere(ITextChannel channel, int amount, Func<IMessage, bool> predicate)
|
||||
{
|
||||
channel.ThrowIfNull(nameof(channel));
|
||||
if (amount <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
||||
|
||||
if (!_pruningGuilds.Add(channel.GuildId))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
IMessage[] msgs;
|
||||
IMessage lastMessage = null;
|
||||
msgs = (await channel.GetMessagesAsync(50).Flatten()).Where(predicate).Take(amount).ToArray();
|
||||
while (amount > 0 && msgs.Any())
|
||||
{
|
||||
lastMessage = msgs[msgs.Length - 1];
|
||||
|
||||
var bulkDeletable = new List<IMessage>();
|
||||
var singleDeletable = new List<IMessage>();
|
||||
foreach (var x in msgs)
|
||||
{
|
||||
if (DateTime.UtcNow - x.CreatedAt < twoWeeks)
|
||||
bulkDeletable.Add(x);
|
||||
else
|
||||
singleDeletable.Add(x);
|
||||
}
|
||||
|
||||
if (bulkDeletable.Count > 0)
|
||||
await Task.WhenAll(Task.Delay(1000), channel.DeleteMessagesAsync(bulkDeletable)).ConfigureAwait(false);
|
||||
|
||||
var i = 0;
|
||||
foreach (var group in singleDeletable.GroupBy(x => ++i / (singleDeletable.Count / 5)))
|
||||
await Task.WhenAll(Task.Delay(1000), Task.WhenAll(group.Select(x => x.DeleteAsync()))).ConfigureAwait(false);
|
||||
|
||||
//this isn't good, because this still work as if i want to remove only specific user's messages from the last
|
||||
//100 messages, Maybe this needs to be reduced by msgs.Length instead of 100
|
||||
amount -= 50;
|
||||
if(amount > 0)
|
||||
msgs = (await channel.GetMessagesAsync(lastMessage, Direction.Before, 50).Flatten()).Where(predicate).Take(amount).ToArray();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
//ignore
|
||||
}
|
||||
finally
|
||||
{
|
||||
_pruningGuilds.TryRemove(channel.GuildId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Administration.Common;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NLog;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public class SlowmodeService : IEarlyBlocker, INService
|
||||
{
|
||||
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 DiscordSocketClient _client;
|
||||
|
||||
public SlowmodeService(DiscordSocketClient 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))));
|
||||
}
|
||||
|
||||
public async Task<bool> TryBlockEarly(IGuild guild, IUserMessage usrMsg)
|
||||
{
|
||||
if (guild == null)
|
||||
return false;
|
||||
try
|
||||
{
|
||||
var channel = usrMsg?.Channel as SocketTextChannel;
|
||||
|
||||
if (channel == null || usrMsg == null || usrMsg.IsAuthor(_client))
|
||||
return false;
|
||||
if (!RatelimitingChannels.TryGetValue(channel.Id, out Ratelimiter limiter))
|
||||
return false;
|
||||
|
||||
if (limiter.CheckUserRatelimit(usrMsg.Author.Id, channel.Guild.Id, usrMsg.Author as SocketGuildUser))
|
||||
{
|
||||
await usrMsg.DeleteAsync();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn(ex);
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
161
NadekoBot.Core/Modules/Administration/Services/SelfService.cs
Normal file
161
NadekoBot.Core/Modules/Administration/Services/SelfService.cs
Normal file
@ -0,0 +1,161 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Impl;
|
||||
using NLog;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public class SelfService : ILateExecutor, INService
|
||||
{
|
||||
public bool ForwardDMs => _bc.BotConfig.ForwardMessages;
|
||||
public bool ForwardDMsToAllOwners => _bc.BotConfig.ForwardToAllOwners;
|
||||
|
||||
private readonly NadekoBot _bot;
|
||||
private readonly CommandHandler _cmdHandler;
|
||||
private readonly DbService _db;
|
||||
private readonly Logger _log;
|
||||
private readonly ILocalization _localization;
|
||||
private readonly NadekoStrings _strings;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IBotCredentials _creds;
|
||||
private ImmutableArray<AsyncLazy<IDMChannel>> ownerChannels = new ImmutableArray<AsyncLazy<IDMChannel>>();
|
||||
private readonly IBotConfigProvider _bc;
|
||||
|
||||
public SelfService(DiscordSocketClient client, NadekoBot bot, CommandHandler cmdHandler, DbService db,
|
||||
IBotConfigProvider bc, ILocalization localization, NadekoStrings strings, IBotCredentials creds)
|
||||
{
|
||||
_bot = bot;
|
||||
_cmdHandler = cmdHandler;
|
||||
_db = db;
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
_localization = localization;
|
||||
_strings = strings;
|
||||
_client = client;
|
||||
_creds = creds;
|
||||
_bc = bc;
|
||||
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
await bot.Ready.Task.ConfigureAwait(false);
|
||||
|
||||
foreach (var cmd in bc.BotConfig.StartupCommands)
|
||||
{
|
||||
await cmdHandler.ExecuteExternal(cmd.GuildId, cmd.ChannelId, cmd.CommandText);
|
||||
await Task.Delay(400).ConfigureAwait(false);
|
||||
}
|
||||
});
|
||||
|
||||
var ___ = Task.Run(async () =>
|
||||
{
|
||||
await bot.Ready.Task.ConfigureAwait(false);
|
||||
|
||||
await Task.Delay(5000);
|
||||
|
||||
_client.Guilds.SelectMany(g => g.Users);
|
||||
|
||||
if(client.ShardId == 0)
|
||||
LoadOwnerChannels();
|
||||
});
|
||||
}
|
||||
|
||||
private void LoadOwnerChannels()
|
||||
{
|
||||
var hs = new HashSet<ulong>(_creds.OwnerIds);
|
||||
var channels = new Dictionary<ulong, AsyncLazy<IDMChannel>>();
|
||||
|
||||
if (hs.Count > 0)
|
||||
{
|
||||
foreach (var g in _client.Guilds)
|
||||
{
|
||||
if (hs.Count == 0)
|
||||
break;
|
||||
|
||||
foreach (var u in g.Users)
|
||||
{
|
||||
if (hs.Remove(u.Id))
|
||||
{
|
||||
channels.Add(u.Id, new AsyncLazy<IDMChannel>(async () => await u.GetOrCreateDMChannelAsync()));
|
||||
if (hs.Count == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ownerChannels = channels.OrderBy(x => _creds.OwnerIds.IndexOf(x.Key))
|
||||
.Select(x => x.Value)
|
||||
.ToImmutableArray();
|
||||
|
||||
if (!ownerChannels.Any())
|
||||
_log.Warn("No owner channels created! Make sure you've specified correct OwnerId in the credentials.json file.");
|
||||
else
|
||||
_log.Info($"Created {ownerChannels.Length} out of {_creds.OwnerIds.Length} owner message channels.");
|
||||
}
|
||||
|
||||
// forwards dms
|
||||
public async Task LateExecute(DiscordSocketClient client, IGuild guild, IUserMessage msg)
|
||||
{
|
||||
if (msg.Channel is IDMChannel && ForwardDMs && ownerChannels.Length > 0)
|
||||
{
|
||||
var title = _strings.GetText("dm_from",
|
||||
_localization.DefaultCultureInfo,
|
||||
"Administration".ToLowerInvariant()) +
|
||||
$" [{msg.Author}]({msg.Author.Id})";
|
||||
|
||||
var attachamentsTxt = _strings.GetText("attachments",
|
||||
_localization.DefaultCultureInfo,
|
||||
"Administration".ToLowerInvariant());
|
||||
|
||||
var toSend = msg.Content;
|
||||
|
||||
if (msg.Attachments.Count > 0)
|
||||
{
|
||||
toSend += $"\n\n{Format.Code(attachamentsTxt)}:\n" +
|
||||
string.Join("\n", msg.Attachments.Select(a => a.ProxyUrl));
|
||||
}
|
||||
|
||||
if (ForwardDMsToAllOwners)
|
||||
{
|
||||
var allOwnerChannels = await Task.WhenAll(ownerChannels
|
||||
.Select(x => x.Value))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
foreach (var ownerCh in allOwnerChannels.Where(ch => ch.Recipient.Id != msg.Author.Id))
|
||||
{
|
||||
try
|
||||
{
|
||||
await ownerCh.SendConfirmAsync(title, toSend).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_log.Warn("Can't contact owner with id {0}", ownerCh.Recipient.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var firstOwnerChannel = await ownerChannels[0];
|
||||
if (firstOwnerChannel.Recipient.Id != msg.Author.Id)
|
||||
{
|
||||
try
|
||||
{
|
||||
await firstOwnerChannel.SendConfirmAsync(title, toSend).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public class UserPunishService : INService
|
||||
{
|
||||
private readonly MuteService _mute;
|
||||
private readonly DbService _db;
|
||||
|
||||
public UserPunishService(MuteService mute, DbService db)
|
||||
{
|
||||
_mute = mute;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public async Task<PunishmentAction?> Warn(IGuild guild, ulong userId, string modName, string reason)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(reason))
|
||||
reason = "-";
|
||||
|
||||
var guildId = guild.Id;
|
||||
|
||||
var warn = new Warning()
|
||||
{
|
||||
UserId = userId,
|
||||
GuildId = guildId,
|
||||
Forgiven = false,
|
||||
Reason = reason,
|
||||
Moderator = modName,
|
||||
};
|
||||
|
||||
int warnings = 1;
|
||||
List<WarningPunishment> ps;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
ps = uow.GuildConfigs.For(guildId, set => set.Include(x => x.WarnPunishments))
|
||||
.WarnPunishments;
|
||||
|
||||
warnings += uow.Warnings
|
||||
.For(guildId, userId)
|
||||
.Where(w => !w.Forgiven && w.UserId == userId)
|
||||
.Count();
|
||||
|
||||
uow.Warnings.Add(warn);
|
||||
|
||||
uow.Complete();
|
||||
}
|
||||
|
||||
var p = ps.FirstOrDefault(x => x.Count == warnings);
|
||||
|
||||
if (p != null)
|
||||
{
|
||||
var user = await guild.GetUserAsync(userId);
|
||||
if (user == null)
|
||||
return null;
|
||||
switch (p.Punishment)
|
||||
{
|
||||
case PunishmentAction.Mute:
|
||||
if (p.Time == 0)
|
||||
await _mute.MuteUser(user).ConfigureAwait(false);
|
||||
else
|
||||
await _mute.TimedMute(user, TimeSpan.FromMinutes(p.Time)).ConfigureAwait(false);
|
||||
break;
|
||||
case PunishmentAction.Kick:
|
||||
await user.KickAsync().ConfigureAwait(false);
|
||||
break;
|
||||
case PunishmentAction.Ban:
|
||||
await guild.AddBanAsync(user).ConfigureAwait(false);
|
||||
break;
|
||||
case PunishmentAction.Softban:
|
||||
await guild.AddBanAsync(user, 7).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await guild.RemoveBanAsync(user).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await guild.RemoveBanAsync(user).ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return p.Punishment;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
119
NadekoBot.Core/Modules/Administration/Services/VcRoleService.cs
Normal file
119
NadekoBot.Core/Modules/Administration/Services/VcRoleService.cs
Normal file
@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NLog;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public class VcRoleService : INService
|
||||
{
|
||||
private readonly Logger _log;
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, IRole>> VcRoles { get; }
|
||||
|
||||
public VcRoleService(DiscordSocketClient client, IEnumerable<GuildConfig> gcs, DbService db)
|
||||
{
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
_db = db;
|
||||
_client = client;
|
||||
|
||||
_client.UserVoiceStateUpdated += ClientOnUserVoiceStateUpdated;
|
||||
VcRoles = new ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, IRole>>();
|
||||
var missingRoles = new List<VcRoleInfo>();
|
||||
foreach (var gconf in gcs)
|
||||
{
|
||||
var g = _client.GetGuild(gconf.GuildId);
|
||||
if (g == null)
|
||||
continue;
|
||||
|
||||
var infos = new ConcurrentDictionary<ulong, IRole>();
|
||||
VcRoles.TryAdd(gconf.GuildId, infos);
|
||||
foreach (var ri in gconf.VcRoleInfos)
|
||||
{
|
||||
var role = g.GetRole(ri.RoleId);
|
||||
if (role == null)
|
||||
{
|
||||
missingRoles.Add(ri);
|
||||
continue;
|
||||
}
|
||||
|
||||
infos.TryAdd(ri.VoiceChannelId, role);
|
||||
}
|
||||
}
|
||||
if(missingRoles.Any())
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
_log.Warn($"Removing {missingRoles.Count} missing roles from {nameof(VcRoleService)}");
|
||||
uow._context.RemoveRange(missingRoles);
|
||||
uow.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
private Task ClientOnUserVoiceStateUpdated(SocketUser usr, SocketVoiceState oldState,
|
||||
SocketVoiceState newState)
|
||||
{
|
||||
|
||||
var gusr = usr as SocketGuildUser;
|
||||
if (gusr == null)
|
||||
return Task.CompletedTask;
|
||||
|
||||
var oldVc = oldState.VoiceChannel;
|
||||
var newVc = newState.VoiceChannel;
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (oldVc != newVc)
|
||||
{
|
||||
ulong guildId;
|
||||
guildId = newVc?.Guild.Id ?? oldVc.Guild.Id;
|
||||
|
||||
if (VcRoles.TryGetValue(guildId, out ConcurrentDictionary<ulong, IRole> guildVcRoles))
|
||||
{
|
||||
//remove old
|
||||
if (oldVc != null && guildVcRoles.TryGetValue(oldVc.Id, out IRole role))
|
||||
{
|
||||
try
|
||||
{
|
||||
await gusr.RemoveRoleAsync(role).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(500).ConfigureAwait(false);
|
||||
await gusr.RemoveRoleAsync(role).ConfigureAwait(false);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
//add new
|
||||
if (newVc != null && guildVcRoles.TryGetValue(newVc.Id, out role))
|
||||
{
|
||||
if (!gusr.Roles.Contains(role))
|
||||
{
|
||||
await Task.Delay(500).ConfigureAwait(false);
|
||||
await gusr.AddRoleAsync(role).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn(ex);
|
||||
}
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
154
NadekoBot.Core/Modules/Administration/Services/VplusTService.cs
Normal file
154
NadekoBot.Core/Modules/Administration/Services/VplusTService.cs
Normal file
@ -0,0 +1,154 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Services.Impl;
|
||||
using NLog;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public class VplusTService : INService
|
||||
{
|
||||
private readonly Regex _channelNameRegex = new Regex(@"[^a-zA-Z0-9 -]", RegexOptions.Compiled);
|
||||
|
||||
public readonly ConcurrentHashSet<ulong> VoicePlusTextCache;
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, SemaphoreSlim> _guildLockObjects = new ConcurrentDictionary<ulong, SemaphoreSlim>();
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly NadekoStrings _strings;
|
||||
private readonly DbService _db;
|
||||
private readonly Logger _log;
|
||||
|
||||
public VplusTService(DiscordSocketClient client, IEnumerable<GuildConfig> gcs, NadekoStrings strings,
|
||||
DbService db)
|
||||
{
|
||||
_client = client;
|
||||
_strings = strings;
|
||||
_db = db;
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
VoicePlusTextCache = new ConcurrentHashSet<ulong>(gcs.Where(g => g.VoicePlusTextEnabled).Select(g => g.GuildId));
|
||||
_client.UserVoiceStateUpdated += UserUpdatedEventHandler;
|
||||
}
|
||||
|
||||
private Task UserUpdatedEventHandler(SocketUser iuser, SocketVoiceState before, SocketVoiceState after)
|
||||
{
|
||||
var user = (iuser as SocketGuildUser);
|
||||
var guild = user?.Guild;
|
||||
|
||||
if (guild == null)
|
||||
return Task.CompletedTask;
|
||||
|
||||
var botUserPerms = guild.CurrentUser.GuildPermissions;
|
||||
|
||||
if (before.VoiceChannel == after.VoiceChannel)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (!VoicePlusTextCache.Contains(guild.Id))
|
||||
return Task.CompletedTask;
|
||||
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
if (!botUserPerms.ManageChannels || !botUserPerms.ManageRoles)
|
||||
{
|
||||
try
|
||||
{
|
||||
await guild.Owner.SendErrorAsync(
|
||||
_strings.GetText("vt_exit",
|
||||
guild.Id,
|
||||
"Administration".ToLowerInvariant(),
|
||||
Format.Bold(guild.Name))).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
uow.GuildConfigs.For(guild.Id, set => set).VoicePlusTextEnabled = false;
|
||||
VoicePlusTextCache.TryRemove(guild.Id);
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var semaphore = _guildLockObjects.GetOrAdd(guild.Id, (key) => new SemaphoreSlim(1, 1));
|
||||
|
||||
try
|
||||
{
|
||||
await semaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
var beforeVch = before.VoiceChannel;
|
||||
if (beforeVch != null)
|
||||
{
|
||||
var beforeRoleName = GetRoleName(beforeVch);
|
||||
var beforeRole = guild.Roles.FirstOrDefault(x => x.Name == beforeRoleName);
|
||||
if (beforeRole != null)
|
||||
{
|
||||
_log.Info("Removing role " + beforeRoleName + " from user " + user.Username);
|
||||
await user.RemoveRoleAsync(beforeRole).ConfigureAwait(false);
|
||||
await Task.Delay(200).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
var afterVch = after.VoiceChannel;
|
||||
if (afterVch != null && guild.AFKChannel?.Id != afterVch.Id)
|
||||
{
|
||||
var roleName = GetRoleName(afterVch);
|
||||
var roleToAdd = guild.Roles.FirstOrDefault(x => x.Name == roleName) ??
|
||||
(IRole)await guild.CreateRoleAsync(roleName, GuildPermissions.None).ConfigureAwait(false);
|
||||
|
||||
ITextChannel textChannel = guild.TextChannels
|
||||
.FirstOrDefault(t => t.Name == GetChannelName(afterVch.Name).ToLowerInvariant());
|
||||
if (textChannel == null)
|
||||
{
|
||||
var created = (await guild.CreateTextChannelAsync(GetChannelName(afterVch.Name).ToLowerInvariant()).ConfigureAwait(false));
|
||||
|
||||
try { await guild.CurrentUser.AddRoleAsync(roleToAdd).ConfigureAwait(false); } catch {/*ignored*/}
|
||||
await Task.Delay(50).ConfigureAwait(false);
|
||||
await created.AddPermissionOverwriteAsync(roleToAdd, new OverwritePermissions(
|
||||
readMessages: PermValue.Allow,
|
||||
sendMessages: PermValue.Allow))
|
||||
.ConfigureAwait(false);
|
||||
await Task.Delay(50).ConfigureAwait(false);
|
||||
await created.AddPermissionOverwriteAsync(guild.EveryoneRole, new OverwritePermissions(
|
||||
readMessages: PermValue.Deny,
|
||||
sendMessages: PermValue.Deny))
|
||||
.ConfigureAwait(false);
|
||||
await Task.Delay(50).ConfigureAwait(false);
|
||||
}
|
||||
_log.Info("Adding role " + roleToAdd.Name + " to user " + user.Username);
|
||||
await user.AddRoleAsync(roleToAdd).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn(ex);
|
||||
}
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public string GetChannelName(string voiceName) =>
|
||||
_channelNameRegex.Replace(voiceName, "").Trim().Replace(" ", "-").TrimTo(90, true) + "-voice";
|
||||
|
||||
public string GetRoleName(IVoiceChannel ch) =>
|
||||
"nvoice-" + ch.Id;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user