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