mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-11 17:58:26 -04:00
Restructured folders and project names, ci should be fixed
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Common.Replacements;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using NadekoBot.Extensions;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public class AdministrationService : INService
|
||||
{
|
||||
public ConcurrentHashSet<ulong> DeleteMessagesOnCommand { get; }
|
||||
public ConcurrentDictionary<ulong, bool> DeleteMessagesOnCommandChannels { get; }
|
||||
|
||||
private readonly DbService _db;
|
||||
private readonly LogCommandService _logService;
|
||||
|
||||
public AdministrationService(NadekoBot bot, CommandHandler cmdHandler, DbService db,
|
||||
LogCommandService logService)
|
||||
{
|
||||
_db = db;
|
||||
_logService = logService;
|
||||
|
||||
DeleteMessagesOnCommand = new ConcurrentHashSet<ulong>(bot.AllGuildConfigs
|
||||
.Where(g => g.DeleteMessageOnCommand)
|
||||
.Select(g => g.GuildId));
|
||||
|
||||
DeleteMessagesOnCommandChannels = new ConcurrentDictionary<ulong, bool>(bot.AllGuildConfigs
|
||||
.SelectMany(x => x.DelMsgOnCmdChannels)
|
||||
.ToDictionary(x => x.ChannelId, x => x.State)
|
||||
.ToConcurrent());
|
||||
|
||||
cmdHandler.CommandExecuted += DelMsgOnCmd_Handler;
|
||||
}
|
||||
|
||||
public (bool DelMsgOnCmd, IEnumerable<DelMsgOnCmdChannel> channels) GetDelMsgOnCmdData(ulong guildId)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var conf = uow.GuildConfigs.ForId(guildId,
|
||||
set => set.Include(x => x.DelMsgOnCmdChannels));
|
||||
|
||||
return (conf.DeleteMessageOnCommand, conf.DelMsgOnCmdChannels);
|
||||
}
|
||||
}
|
||||
|
||||
private Task DelMsgOnCmd_Handler(IUserMessage msg, CommandInfo cmd)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
if (!(msg.Channel is SocketTextChannel channel))
|
||||
return;
|
||||
|
||||
//wat ?!
|
||||
if (DeleteMessagesOnCommandChannels.TryGetValue(channel.Id, out var state))
|
||||
{
|
||||
if (state && cmd.Name != "prune" && cmd.Name != "pick")
|
||||
{
|
||||
_logService.AddDeleteIgnore(msg.Id);
|
||||
try { await msg.DeleteAsync().ConfigureAwait(false); } catch { }
|
||||
}
|
||||
//if state is false, that means do not do it
|
||||
}
|
||||
else if (DeleteMessagesOnCommand.Contains(channel.Guild.Id) && cmd.Name != "prune" && cmd.Name != "pick")
|
||||
{
|
||||
_logService.AddDeleteIgnore(msg.Id);
|
||||
try { await msg.DeleteAsync().ConfigureAwait(false); } catch { }
|
||||
}
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public bool ToggleDeleteMessageOnCommand(ulong guildId)
|
||||
{
|
||||
bool enabled;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var conf = uow.GuildConfigs.ForId(guildId, set => set);
|
||||
enabled = conf.DeleteMessageOnCommand = !conf.DeleteMessageOnCommand;
|
||||
|
||||
uow.SaveChanges();
|
||||
}
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public async Task SetDelMsgOnCmdState(ulong guildId, ulong chId, Administration.State newState)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var conf = uow.GuildConfigs.ForId(guildId,
|
||||
set => set.Include(x => x.DelMsgOnCmdChannels));
|
||||
|
||||
var old = conf.DelMsgOnCmdChannels.FirstOrDefault(x => x.ChannelId == chId);
|
||||
if (newState == Administration.State.Inherit)
|
||||
{
|
||||
if (!(old is null))
|
||||
{
|
||||
conf.DelMsgOnCmdChannels.Remove(old);
|
||||
uow._context.Remove(old);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (old is null)
|
||||
{
|
||||
old = new DelMsgOnCmdChannel { ChannelId = chId };
|
||||
conf.DelMsgOnCmdChannels.Add(old);
|
||||
}
|
||||
|
||||
old.State = newState == Administration.State.Enable;
|
||||
DeleteMessagesOnCommandChannels[chId] = newState == Administration.State.Enable;
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
if (newState == Administration.State.Disable)
|
||||
{
|
||||
}
|
||||
else if (newState == Administration.State.Enable)
|
||||
{
|
||||
DeleteMessagesOnCommandChannels[chId] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
DeleteMessagesOnCommandChannels.TryRemove(chId, out var _);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeafenUsers(bool value, params IGuildUser[] users)
|
||||
{
|
||||
if (!users.Any())
|
||||
return;
|
||||
foreach (var u in users)
|
||||
{
|
||||
try
|
||||
{
|
||||
await u.ModifyAsync(usr => usr.Deaf = value).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task EditMessage(ICommandContext context, ITextChannel chanl, ulong messageId, string text)
|
||||
{
|
||||
var msg = await chanl.GetMessageAsync(messageId);
|
||||
|
||||
if (!(msg is IUserMessage umsg) || msg.Author.Id != context.Client.CurrentUser.Id)
|
||||
return;
|
||||
|
||||
var rep = new ReplacementBuilder()
|
||||
.WithDefault(context)
|
||||
.Build();
|
||||
|
||||
if (CREmbed.TryParse(text, out var crembed))
|
||||
{
|
||||
rep.Replace(crembed);
|
||||
await umsg.ModifyAsync(x =>
|
||||
{
|
||||
x.Embed = crembed.ToEmbed().Build();
|
||||
x.Content = crembed.PlainText?.SanitizeMentions() ?? "";
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await umsg.ModifyAsync(x =>
|
||||
{
|
||||
x.Content = text.SanitizeMentions();
|
||||
x.Embed = null;
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,170 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Core.Services;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Channels;
|
||||
using LinqToDB;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using NadekoBot.Extensions;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public sealed class AutoAssignRoleService : INService
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly DbService _db;
|
||||
|
||||
//guildid/roleid
|
||||
private readonly ConcurrentDictionary<ulong, IReadOnlyList<ulong>> _autoAssignableRoles;
|
||||
|
||||
private Channel<SocketGuildUser> _assignQueue = Channel.CreateBounded<SocketGuildUser>(
|
||||
new BoundedChannelOptions(100)
|
||||
{
|
||||
FullMode = BoundedChannelFullMode.DropOldest,
|
||||
SingleReader = true,
|
||||
SingleWriter = false,
|
||||
});
|
||||
|
||||
public AutoAssignRoleService(DiscordSocketClient client, NadekoBot bot, DbService db)
|
||||
{
|
||||
_client = client;
|
||||
_db = db;
|
||||
|
||||
_autoAssignableRoles = bot.AllGuildConfigs
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.AutoAssignRoleIds))
|
||||
.ToDictionary<GuildConfig, ulong, IReadOnlyList<ulong>>(k => k.GuildId, v => v.GetAutoAssignableRoles())
|
||||
.ToConcurrent();
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var user = await _assignQueue.Reader.ReadAsync();
|
||||
if (!_autoAssignableRoles.TryGetValue(user.Guild.Id, out var savedRoleIds))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
var roleIds = savedRoleIds
|
||||
.Select(roleId => user.Guild.GetRole(roleId))
|
||||
.Where(x => !(x is null))
|
||||
.ToList();
|
||||
|
||||
if (roleIds.Any())
|
||||
{
|
||||
await user.AddRolesAsync(roleIds).ConfigureAwait(false);
|
||||
await Task.Delay(250).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning(
|
||||
"Disabled 'Auto assign role' feature on {GuildName} [{GuildId}] server the roles dont exist",
|
||||
user.Guild.Name,
|
||||
user.Guild.Id);
|
||||
|
||||
await DisableAarAsync(user.Guild.Id);
|
||||
}
|
||||
}
|
||||
catch (Discord.Net.HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden)
|
||||
{
|
||||
Log.Warning("Disabled 'Auto assign role' feature on {GuildName} [{GuildId}] server because I don't have role management permissions",
|
||||
user.Guild.Name,
|
||||
user.Guild.Id);
|
||||
|
||||
await DisableAarAsync(user.Guild.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error in aar. Probably one of the roles doesn't exist");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_client.UserJoined += OnClientOnUserJoined;
|
||||
_client.RoleDeleted += OnClientRoleDeleted;
|
||||
}
|
||||
|
||||
private async Task OnClientRoleDeleted(SocketRole role)
|
||||
{
|
||||
if (_autoAssignableRoles.TryGetValue(role.Guild.Id, out var roles)
|
||||
&& roles.Contains(role.Id))
|
||||
{
|
||||
await ToggleAarAsync(role.Guild.Id, role.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnClientOnUserJoined(SocketGuildUser user)
|
||||
{
|
||||
if (_autoAssignableRoles.TryGetValue(user.Guild.Id, out _))
|
||||
await _assignQueue.Writer.WriteAsync(user);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<ulong>> ToggleAarAsync(ulong guildId, ulong roleId)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigs.ForId(guildId, set => set);
|
||||
var roles = gc.GetAutoAssignableRoles();
|
||||
if(!roles.Remove(roleId) && roles.Count < 3)
|
||||
roles.Add(roleId);
|
||||
|
||||
gc.SetAutoAssignableRoles(roles);
|
||||
await uow.SaveChangesAsync();
|
||||
|
||||
if (roles.Count > 0)
|
||||
_autoAssignableRoles[guildId] = roles;
|
||||
else
|
||||
_autoAssignableRoles.TryRemove(guildId, out _);
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
public async Task DisableAarAsync(ulong guildId)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
|
||||
await uow._context
|
||||
.GuildConfigs
|
||||
.AsNoTracking()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.UpdateAsync(_ => new GuildConfig(){ AutoAssignRoleIds = null});
|
||||
|
||||
_autoAssignableRoles.TryRemove(guildId, out _);
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task SetAarRolesAsync(ulong guildId, IEnumerable<ulong> newRoles)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
|
||||
var gc = uow.GuildConfigs.ForId(guildId, set => set);
|
||||
gc.SetAutoAssignableRoles(newRoles);
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public bool TryGetRoles(ulong guildId, out IReadOnlyList<ulong> roles)
|
||||
=> _autoAssignableRoles.TryGetValue(guildId, out roles);
|
||||
}
|
||||
|
||||
public static class GuildConfigExtensions
|
||||
{
|
||||
public static List<ulong> GetAutoAssignableRoles(this GuildConfig gc)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(gc.AutoAssignRoleIds))
|
||||
return new List<ulong>();
|
||||
|
||||
return gc.AutoAssignRoleIds.Split(',').Select(ulong.Parse).ToList();
|
||||
}
|
||||
|
||||
public static void SetAutoAssignableRoles(this GuildConfig gc, IEnumerable<ulong> roles)
|
||||
{
|
||||
gc.AutoAssignRoleIds = roles.JoinWith(',');
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,145 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Core.Services;
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Core.Modules.Administration.Services
|
||||
{
|
||||
public class DangerousCommandsService : INService
|
||||
{
|
||||
public const string WaifusDeleteSql = @"DELETE FROM WaifuUpdates;
|
||||
DELETE FROM WaifuItem;
|
||||
DELETE FROM WaifuInfo;";
|
||||
public const string WaifuDeleteSql = @"DELETE FROM WaifuUpdates WHERE UserId=(SELECT Id FROM DiscordUser WHERE UserId={0});
|
||||
DELETE FROM WaifuItem WHERE WaifuInfoId=(SELECT Id FROM WaifuInfo WHERE WaifuId=(SELECT Id FROM DiscordUser WHERE UserId={0}));
|
||||
UPDATE WaifuInfo SET ClaimerId=NULL WHERE ClaimerId=(SELECT Id FROM DiscordUser WHERE UserId={0});
|
||||
DELETE FROM WaifuInfo WHERE WaifuId=(SELECT Id FROM DiscordUser WHERE UserId={0});";
|
||||
public const string CurrencyDeleteSql = "UPDATE DiscordUser SET CurrencyAmount=0; DELETE FROM CurrencyTransactions; DELETE FROM PlantedCurrency;";
|
||||
public const string MusicPlaylistDeleteSql = "DELETE FROM MusicPlaylists;";
|
||||
public const string XpDeleteSql = @"DELETE FROM UserXpStats;
|
||||
UPDATE DiscordUser
|
||||
SET ClubId=NULL,
|
||||
IsClubAdmin=0,
|
||||
TotalXp=0;
|
||||
DELETE FROM ClubApplicants;
|
||||
DELETE FROM ClubBans;
|
||||
DELETE FROM Clubs;";
|
||||
// public const string DeleteUnusedCustomReactionsAndQuotes = @"DELETE FROM CustomReactions
|
||||
//WHERE UseCount=0 AND (DateAdded < date('now', '-7 day') OR DateAdded is null);
|
||||
|
||||
//DELETE FROM Quotes
|
||||
//WHERE UseCount=0 AND (DateAdded < date('now', '-7 day') OR DateAdded is null);";
|
||||
|
||||
private readonly DbService _db;
|
||||
|
||||
public DangerousCommandsService(DbService db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public async Task<int> ExecuteSql(string sql)
|
||||
{
|
||||
int res;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
res = await uow._context.Database.ExecuteSqlRawAsync(sql);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public class SelectResult
|
||||
{
|
||||
public List<string> ColumnNames { get; set; }
|
||||
public List<string[]> Results { get; set; }
|
||||
}
|
||||
|
||||
public SelectResult SelectSql(string sql)
|
||||
{
|
||||
var result = new SelectResult()
|
||||
{
|
||||
ColumnNames = new List<string>(),
|
||||
Results = new List<string[]>(),
|
||||
};
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var conn = uow._context.Database.GetDbConnection();
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = sql;
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
if (reader.HasRows)
|
||||
{
|
||||
for (int i = 0; i < reader.FieldCount; i++)
|
||||
{
|
||||
result.ColumnNames.Add(reader.GetName(i));
|
||||
}
|
||||
while (reader.Read())
|
||||
{
|
||||
var obj = new object[reader.FieldCount];
|
||||
reader.GetValues(obj);
|
||||
result.Results.Add(obj.Select(x => x.ToString()).ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task PurgeUserAsync(ulong userId)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
|
||||
// get waifu info
|
||||
var wi = await uow._context.Set<WaifuInfo>()
|
||||
.FirstOrDefaultAsyncEF(x => x.Waifu.UserId == userId);
|
||||
|
||||
// if it exists, delete waifu related things
|
||||
if (!(wi is null))
|
||||
{
|
||||
// remove updates which have new or old as this waifu
|
||||
await uow._context
|
||||
.WaifuUpdates
|
||||
.DeleteAsync(wu => wu.New.UserId == userId || wu.Old.UserId == userId);
|
||||
|
||||
// delete all items this waifu owns
|
||||
await uow._context
|
||||
.Set<WaifuItem>()
|
||||
.DeleteAsync(x => x.WaifuInfoId == wi.Id);
|
||||
|
||||
// all waifus this waifu claims are released
|
||||
await uow._context
|
||||
.Set<WaifuInfo>()
|
||||
.AsQueryable()
|
||||
.Where(x => x.Claimer.UserId == userId)
|
||||
.UpdateAsync(x => new WaifuInfo() {ClaimerId = null});
|
||||
|
||||
// all affinities set to this waifu are reset
|
||||
await uow._context
|
||||
.Set<WaifuInfo>()
|
||||
.AsQueryable()
|
||||
.Where(x => x.Affinity.UserId == userId)
|
||||
.UpdateAsync(x => new WaifuInfo() {AffinityId = null});
|
||||
}
|
||||
|
||||
// delete guild xp
|
||||
await uow._context
|
||||
.UserXpStats
|
||||
.DeleteAsync(x => x.UserId == userId);
|
||||
|
||||
// delete currency transactions
|
||||
await uow._context.Set<CurrencyTransaction>()
|
||||
.DeleteAsync(x => x.UserId == userId);
|
||||
|
||||
// delete user, currency, and clubs go away with it
|
||||
await uow._context.DiscordUser
|
||||
.DeleteAsync(u => u.UserId == userId);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,159 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using NadekoBot.Extensions;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public class DiscordPermOverrideService : INService, ILateBlocker
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
public int Priority { get; } = int.MaxValue;
|
||||
|
||||
private readonly ConcurrentDictionary<(ulong, string), DiscordPermOverride> _overrides;
|
||||
|
||||
public DiscordPermOverrideService(DbService db, IServiceProvider services)
|
||||
{
|
||||
_db = db;
|
||||
_services = services;
|
||||
using var uow = _db.GetDbContext();
|
||||
_overrides = uow._context.DiscordPermOverrides
|
||||
.AsNoTracking()
|
||||
.AsEnumerable()
|
||||
.ToDictionary(o => (o.GuildId ?? 0, o.Command), o => o)
|
||||
.ToConcurrent();
|
||||
}
|
||||
|
||||
public bool TryGetOverrides(ulong guildId, string commandName, out GuildPerm? perm)
|
||||
{
|
||||
commandName = commandName.ToLowerInvariant();
|
||||
if (_overrides.TryGetValue((guildId, commandName), out var dpo))
|
||||
{
|
||||
perm = dpo.Perm;
|
||||
return true;
|
||||
}
|
||||
|
||||
perm = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public Task<PreconditionResult> ExecuteOverrides(ICommandContext ctx, CommandInfo command,
|
||||
GuildPerm perms, IServiceProvider services)
|
||||
{
|
||||
var rupa = new RequireUserPermissionAttribute((GuildPermission) perms);
|
||||
return rupa.CheckPermissionsAsync(ctx, command, services);
|
||||
}
|
||||
|
||||
public async Task AddOverride(ulong guildId, string commandName, GuildPerm perm)
|
||||
{
|
||||
commandName = commandName.ToLowerInvariant();
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var over = await uow._context
|
||||
.Set<DiscordPermOverride>()
|
||||
.AsQueryable()
|
||||
.FirstOrDefaultAsync(x => x.GuildId == guildId && commandName == x.Command);
|
||||
|
||||
if (over is null)
|
||||
{
|
||||
uow._context.Set<DiscordPermOverride>()
|
||||
.Add(over = new DiscordPermOverride()
|
||||
{
|
||||
Command = commandName,
|
||||
Perm = perm,
|
||||
GuildId = guildId,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
over.Perm = perm;
|
||||
}
|
||||
|
||||
_overrides[(guildId, commandName)] = over;
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ClearAllOverrides(ulong guildId)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var overrides = await uow._context
|
||||
.Set<DiscordPermOverride>()
|
||||
.AsQueryable()
|
||||
.AsNoTracking()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.ToListAsync();
|
||||
|
||||
uow._context.RemoveRange(overrides);
|
||||
await uow.SaveChangesAsync();
|
||||
|
||||
foreach (var over in overrides)
|
||||
{
|
||||
_overrides.TryRemove((guildId, over.Command), out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RemoveOverride(ulong guildId, string commandName)
|
||||
{
|
||||
commandName = commandName.ToLowerInvariant();
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var over = await uow._context
|
||||
.Set<DiscordPermOverride>()
|
||||
.AsQueryable()
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.GuildId == guildId && x.Command == commandName);
|
||||
|
||||
if (over is null)
|
||||
return;
|
||||
|
||||
uow._context.Remove(over);
|
||||
await uow.SaveChangesAsync();
|
||||
|
||||
_overrides.TryRemove((guildId, commandName), out _);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<List<DiscordPermOverride>> GetAllOverrides(ulong guildId)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
return uow._context
|
||||
.Set<DiscordPermOverride>()
|
||||
.AsQueryable()
|
||||
.AsNoTracking()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> TryBlockLate(DiscordSocketClient client, ICommandContext context, string moduleName,
|
||||
CommandInfo command)
|
||||
{
|
||||
if (TryGetOverrides(context.Guild?.Id ?? 0, command.Name, out var perm) && !(perm is null))
|
||||
{
|
||||
var result = await new RequireUserPermissionAttribute((GuildPermission) perm)
|
||||
.CheckPermissionsAsync(context, command, _services);
|
||||
|
||||
return !result.IsSuccess;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.Services;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public class GameVoiceChannelService : INService
|
||||
{
|
||||
public ConcurrentHashSet<ulong> GameVoiceChannels { get; } = new ConcurrentHashSet<ulong>();
|
||||
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public GameVoiceChannelService(DiscordSocketClient client, DbService db, NadekoBot bot)
|
||||
{
|
||||
_db = db;
|
||||
_client = client;
|
||||
|
||||
GameVoiceChannels = new ConcurrentHashSet<ulong>(
|
||||
bot.AllGuildConfigs.Where(gc => gc.GameVoiceChannel != null)
|
||||
.Select(gc => gc.GameVoiceChannel.Value));
|
||||
|
||||
_client.UserVoiceStateUpdated += Client_UserVoiceStateUpdated;
|
||||
_client.GuildMemberUpdated += _client_GuildMemberUpdated;
|
||||
}
|
||||
|
||||
private Task _client_GuildMemberUpdated(SocketGuildUser before, SocketGuildUser after)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
//if the user is in the voice channel and that voice channel is gvc
|
||||
var vc = after.VoiceChannel;
|
||||
if (vc == null || !GameVoiceChannels.Contains(vc.Id))
|
||||
return;
|
||||
|
||||
//if the activity has changed, and is a playing activity
|
||||
if (before.Activity != after.Activity
|
||||
&& after.Activity != null
|
||||
&& after.Activity.Type == Discord.ActivityType.Playing)
|
||||
{
|
||||
//trigger gvc
|
||||
await TriggerGvc(after, after.Activity.Name);
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error running GuildMemberUpdated in gvc");
|
||||
}
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public ulong? ToggleGameVoiceChannel(ulong guildId, ulong vchId)
|
||||
{
|
||||
ulong? id;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var gc = uow.GuildConfigs.ForId(guildId, set => set);
|
||||
|
||||
if (gc.GameVoiceChannel == vchId)
|
||||
{
|
||||
GameVoiceChannels.TryRemove(vchId);
|
||||
id = gc.GameVoiceChannel = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (gc.GameVoiceChannel != null)
|
||||
GameVoiceChannels.TryRemove(gc.GameVoiceChannel.Value);
|
||||
GameVoiceChannels.Add(vchId);
|
||||
id = gc.GameVoiceChannel = vchId;
|
||||
}
|
||||
|
||||
uow.SaveChanges();
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
private Task Client_UserVoiceStateUpdated(SocketUser usr, SocketVoiceState oldState, SocketVoiceState newState)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!(usr is SocketGuildUser gUser))
|
||||
return;
|
||||
|
||||
var game = gUser.Activity?.Name;
|
||||
|
||||
if (oldState.VoiceChannel == newState.VoiceChannel ||
|
||||
newState.VoiceChannel == null)
|
||||
return;
|
||||
|
||||
if (!GameVoiceChannels.Contains(newState.VoiceChannel.Id) ||
|
||||
string.IsNullOrWhiteSpace(game))
|
||||
return;
|
||||
|
||||
await TriggerGvc(gUser, game);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error running VoiceStateUpdate in gvc");
|
||||
}
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task TriggerGvc(SocketGuildUser gUser, string game)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(game))
|
||||
return;
|
||||
|
||||
game = game.TrimTo(50).ToLowerInvariant();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public class GuildTimezoneService : INService
|
||||
{
|
||||
public static ConcurrentDictionary<ulong, GuildTimezoneService> AllServices { get; } = new ConcurrentDictionary<ulong, GuildTimezoneService>();
|
||||
private readonly ConcurrentDictionary<ulong, TimeZoneInfo> _timezones;
|
||||
private readonly DbService _db;
|
||||
|
||||
public GuildTimezoneService(DiscordSocketClient client, NadekoBot bot, DbService db)
|
||||
{
|
||||
_timezones = bot.AllGuildConfigs
|
||||
.Select(GetTimzezoneTuple)
|
||||
.Where(x => x.Timezone != null)
|
||||
.ToDictionary(x => x.GuildId, x => x.Timezone)
|
||||
.ToConcurrent();
|
||||
|
||||
var curUser = client.CurrentUser;
|
||||
if (curUser != null)
|
||||
AllServices.TryAdd(curUser.Id, this);
|
||||
_db = db;
|
||||
|
||||
bot.JoinedGuild += Bot_JoinedGuild;
|
||||
}
|
||||
|
||||
private Task Bot_JoinedGuild(GuildConfig arg)
|
||||
{
|
||||
var (guildId, tz) = GetTimzezoneTuple(arg);
|
||||
if (tz != null)
|
||||
_timezones.TryAdd(guildId, tz);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static (ulong GuildId, TimeZoneInfo Timezone) GetTimzezoneTuple(GuildConfig x)
|
||||
{
|
||||
TimeZoneInfo tz;
|
||||
try
|
||||
{
|
||||
if (x.TimeZoneId == null)
|
||||
tz = null;
|
||||
else
|
||||
tz = TimeZoneInfo.FindSystemTimeZoneById(x.TimeZoneId);
|
||||
}
|
||||
catch
|
||||
{
|
||||
tz = null;
|
||||
}
|
||||
return (x.GuildId, Timezone: tz);
|
||||
}
|
||||
|
||||
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.GetDbContext())
|
||||
{
|
||||
var gc = uow.GuildConfigs.ForId(guildId, set => set);
|
||||
|
||||
gc.TimeZoneId = tz?.Id;
|
||||
uow.SaveChanges();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
1279
src/NadekoBot/Modules/Administration/Services/LogCommandService.cs
Normal file
1279
src/NadekoBot/Modules/Administration/Services/LogCommandService.cs
Normal file
File diff suppressed because it is too large
Load Diff
477
src/NadekoBot/Modules/Administration/Services/MuteService.cs
Normal file
477
src/NadekoBot/Modules/Administration/Services/MuteService.cs
Normal file
@@ -0,0 +1,477 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
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.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using Serilog;
|
||||
|
||||
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, TimerType), Timer>> Un_Timers { get; }
|
||||
= new ConcurrentDictionary<ulong, ConcurrentDictionary<(ulong, TimerType), Timer>>();
|
||||
|
||||
public event Action<IGuildUser, IUser, MuteType, string> UserMuted = delegate { };
|
||||
public event Action<IGuildUser, IUser, MuteType, string> UserUnmuted = delegate { };
|
||||
|
||||
private static readonly OverwritePermissions denyOverwrite =
|
||||
new OverwritePermissions(addReactions: PermValue.Deny, sendMessages: PermValue.Deny,
|
||||
attachFiles: PermValue.Deny);
|
||||
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly DbService _db;
|
||||
|
||||
public MuteService(DiscordSocketClient client, DbService db)
|
||||
{
|
||||
_client = client;
|
||||
_db = db;
|
||||
|
||||
using (var uow = db.GetDbContext())
|
||||
{
|
||||
var guildIds = client.Guilds.Select(x => x.Id).ToList();
|
||||
var configs = uow._context.Set<GuildConfig>().AsQueryable()
|
||||
.Include(x => x.MutedUsers)
|
||||
.Include(x => x.UnbanTimer)
|
||||
.Include(x => x.UnmuteTimers)
|
||||
.Include(x => x.UnroleTimer)
|
||||
.Where(x => guildIds.Contains(x.GuildId))
|
||||
.ToList();
|
||||
|
||||
GuildMuteRoles = configs
|
||||
.Where(c => !string.IsNullOrWhiteSpace(c.MuteRoleName))
|
||||
.ToDictionary(c => c.GuildId, c => c.MuteRoleName)
|
||||
.ToConcurrent();
|
||||
|
||||
MutedUsers = new ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>>(configs
|
||||
.ToDictionary(
|
||||
k => k.GuildId,
|
||||
v => new ConcurrentHashSet<ulong>(v.MutedUsers.Select(m => m.UserId))
|
||||
));
|
||||
|
||||
var max = TimeSpan.FromDays(49);
|
||||
|
||||
foreach (var conf in configs)
|
||||
{
|
||||
foreach (var x in conf.UnmuteTimers)
|
||||
{
|
||||
TimeSpan after;
|
||||
if (x.UnmuteAt - TimeSpan.FromMinutes(2) <= DateTime.UtcNow)
|
||||
{
|
||||
after = TimeSpan.FromMinutes(2);
|
||||
}
|
||||
else
|
||||
{
|
||||
var unmute = x.UnmuteAt - DateTime.UtcNow;
|
||||
after = unmute > max ? max : unmute;
|
||||
}
|
||||
|
||||
StartUn_Timer(conf.GuildId, x.UserId, after, TimerType.Mute);
|
||||
}
|
||||
|
||||
foreach (var x in conf.UnbanTimer)
|
||||
{
|
||||
TimeSpan after;
|
||||
if (x.UnbanAt - TimeSpan.FromMinutes(2) <= DateTime.UtcNow)
|
||||
{
|
||||
after = TimeSpan.FromMinutes(2);
|
||||
}
|
||||
else
|
||||
{
|
||||
var unban = x.UnbanAt - DateTime.UtcNow;
|
||||
after = unban > max ? max : unban;
|
||||
}
|
||||
|
||||
StartUn_Timer(conf.GuildId, x.UserId, after, TimerType.Ban);
|
||||
}
|
||||
|
||||
foreach (var x in conf.UnroleTimer)
|
||||
{
|
||||
TimeSpan after;
|
||||
if (x.UnbanAt - TimeSpan.FromMinutes(2) <= DateTime.UtcNow)
|
||||
{
|
||||
after = TimeSpan.FromMinutes(2);
|
||||
}
|
||||
else
|
||||
{
|
||||
var unban = x.UnbanAt - DateTime.UtcNow;
|
||||
after = unban > max ? max : unban;
|
||||
}
|
||||
|
||||
StartUn_Timer(conf.GuildId, x.UserId, after, TimerType.AddRole, x.RoleId);
|
||||
}
|
||||
}
|
||||
|
||||
_client.UserJoined += Client_UserJoined;
|
||||
}
|
||||
|
||||
UserMuted += OnUserMuted;
|
||||
UserUnmuted += OnUserUnmuted;
|
||||
}
|
||||
|
||||
private void OnUserMuted(IGuildUser user, IUser mod, MuteType type, string reason)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(reason))
|
||||
return;
|
||||
|
||||
var _ = Task.Run(() => user.SendMessageAsync(embed: new EmbedBuilder()
|
||||
.WithDescription($"You've been muted in {user.Guild} server")
|
||||
.AddField("Mute Type", type.ToString())
|
||||
.AddField("Moderator", mod.ToString())
|
||||
.AddField("Reason", reason)
|
||||
.Build()));
|
||||
}
|
||||
|
||||
private void OnUserUnmuted(IGuildUser user, IUser mod, MuteType type, string reason)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(reason))
|
||||
return;
|
||||
|
||||
var _ = Task.Run(() => user.SendMessageAsync(embed: new EmbedBuilder()
|
||||
.WithDescription($"You've been unmuted in {user.Guild} server")
|
||||
.AddField("Unmute Type", type.ToString())
|
||||
.AddField("Moderator", mod.ToString())
|
||||
.AddField("Reason", reason)
|
||||
.Build()));
|
||||
}
|
||||
|
||||
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, _client.CurrentUser, reason: "Sticky mute").ConfigureAwait(false));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error in MuteService UserJoined event");
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task SetMuteRoleAsync(ulong guildId, string name)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GuildConfigs.ForId(guildId, set => set);
|
||||
config.MuteRoleName = name;
|
||||
GuildMuteRoles.AddOrUpdate(guildId, name, (id, old) => name);
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task MuteUser(IGuildUser usr, IUser mod, MuteType type = MuteType.All, string reason = "")
|
||||
{
|
||||
if (type == MuteType.All)
|
||||
{
|
||||
try { await usr.ModifyAsync(x => x.Mute = true).ConfigureAwait(false); } catch { }
|
||||
var muteRole = await GetMuteRole(usr.Guild).ConfigureAwait(false);
|
||||
if (!usr.RoleIds.Contains(muteRole.Id))
|
||||
await usr.AddRoleAsync(muteRole).ConfigureAwait(false);
|
||||
StopTimer(usr.GuildId, usr.Id, TimerType.Mute);
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GuildConfigs.ForId(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.SaveChangesAsync();
|
||||
}
|
||||
UserMuted(usr, mod, MuteType.All, reason);
|
||||
}
|
||||
else if (type == MuteType.Voice)
|
||||
{
|
||||
try
|
||||
{
|
||||
await usr.ModifyAsync(x => x.Mute = true).ConfigureAwait(false);
|
||||
UserMuted(usr, mod, MuteType.Voice, reason);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
else if (type == MuteType.Chat)
|
||||
{
|
||||
await usr.AddRoleAsync(await GetMuteRole(usr.Guild).ConfigureAwait(false)).ConfigureAwait(false);
|
||||
UserMuted(usr, mod, MuteType.Chat, reason);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UnmuteUser(ulong guildId, ulong usrId, IUser mod, MuteType type = MuteType.All, string reason = "")
|
||||
{
|
||||
var usr = _client.GetGuild(guildId)?.GetUser(usrId);
|
||||
if (type == MuteType.All)
|
||||
{
|
||||
StopTimer(guildId, usrId, TimerType.Mute);
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GuildConfigs.ForId(guildId, set => set.Include(gc => gc.MutedUsers)
|
||||
.Include(gc => gc.UnmuteTimers));
|
||||
var match = new MutedUserId()
|
||||
{
|
||||
UserId = usrId
|
||||
};
|
||||
var toRemove = config.MutedUsers.FirstOrDefault(x => x.Equals(match));
|
||||
if (toRemove != null)
|
||||
{
|
||||
uow._context.Remove(toRemove);
|
||||
}
|
||||
if (MutedUsers.TryGetValue(guildId, out ConcurrentHashSet<ulong> muted))
|
||||
muted.TryRemove(usrId);
|
||||
|
||||
config.UnmuteTimers.RemoveWhere(x => x.UserId == usrId);
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
if (usr != null)
|
||||
{
|
||||
try { await usr.ModifyAsync(x => x.Mute = false).ConfigureAwait(false); } catch { }
|
||||
try { await usr.RemoveRoleAsync(await GetMuteRole(usr.Guild).ConfigureAwait(false)).ConfigureAwait(false); } catch { /*ignore*/ }
|
||||
UserUnmuted(usr, mod, MuteType.All, reason);
|
||||
}
|
||||
}
|
||||
else if (type == MuteType.Voice)
|
||||
{
|
||||
if (usr == null)
|
||||
return;
|
||||
try
|
||||
{
|
||||
await usr.ModifyAsync(x => x.Mute = false).ConfigureAwait(false);
|
||||
UserUnmuted(usr, mod, MuteType.Voice, reason);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
else if (type == MuteType.Chat)
|
||||
{
|
||||
if (usr == null)
|
||||
return;
|
||||
await usr.RemoveRoleAsync(await GetMuteRole(usr.Guild).ConfigureAwait(false)).ConfigureAwait(false);
|
||||
UserUnmuted(usr, mod, MuteType.Chat, reason);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IRole> GetMuteRole(IGuild guild)
|
||||
{
|
||||
if (guild == null)
|
||||
throw new ArgumentNullException(nameof(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, isMentionable: false).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, isMentionable: false).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var toOverwrite in (await guild.GetTextChannelsAsync().ConfigureAwait(false)))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!toOverwrite.PermissionOverwrites.Any(x => x.TargetId == muteRole.Id
|
||||
&& x.TargetType == PermissionTarget.Role))
|
||||
{
|
||||
await toOverwrite.AddPermissionOverwriteAsync(muteRole, denyOverwrite)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await Task.Delay(200).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
return muteRole;
|
||||
}
|
||||
|
||||
public async Task TimedMute(IGuildUser user, IUser mod, TimeSpan after, MuteType muteType = MuteType.All, string reason = "")
|
||||
{
|
||||
await MuteUser(user, mod, muteType, reason).ConfigureAwait(false); // mute the user. This will also remove any previous unmute timers
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GuildConfigs.ForId(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.SaveChanges();
|
||||
}
|
||||
|
||||
StartUn_Timer(user.GuildId, user.Id, after, TimerType.Mute); // start the timer
|
||||
}
|
||||
|
||||
public async Task TimedBan(IGuild guild, IUser user, TimeSpan after, string reason)
|
||||
{
|
||||
await guild.AddBanAsync(user.Id, 0, reason).ConfigureAwait(false);
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GuildConfigs.ForId(guild.Id, set => set.Include(x => x.UnbanTimer));
|
||||
config.UnbanTimer.Add(new UnbanTimer()
|
||||
{
|
||||
UserId = user.Id,
|
||||
UnbanAt = DateTime.UtcNow + after,
|
||||
}); // add teh unmute timer to the database
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
StartUn_Timer(guild.Id, user.Id, after, TimerType.Ban); // start the timer
|
||||
}
|
||||
|
||||
public async Task TimedRole(IGuildUser user, TimeSpan after, string reason, IRole role)
|
||||
{
|
||||
await user.AddRoleAsync(role).ConfigureAwait(false);
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GuildConfigs.ForId(user.GuildId, set => set.Include(x => x.UnroleTimer));
|
||||
config.UnroleTimer.Add(new UnroleTimer()
|
||||
{
|
||||
UserId = user.Id,
|
||||
UnbanAt = DateTime.UtcNow + after,
|
||||
RoleId = role.Id
|
||||
}); // add teh unmute timer to the database
|
||||
uow.SaveChanges();
|
||||
}
|
||||
StartUn_Timer(user.GuildId, user.Id, after, TimerType.AddRole, role.Id); // start the timer
|
||||
}
|
||||
|
||||
public enum TimerType { Mute, Ban, AddRole }
|
||||
public void StartUn_Timer(ulong guildId, ulong userId, TimeSpan after, TimerType type, ulong? roleId = null)
|
||||
{
|
||||
//load the unmute timers for this guild
|
||||
var userUnTimers = Un_Timers.GetOrAdd(guildId, new ConcurrentDictionary<(ulong, TimerType), Timer>());
|
||||
|
||||
//unmute timer to be added
|
||||
var toAdd = new Timer(async _ =>
|
||||
{
|
||||
if (type == TimerType.Ban)
|
||||
{
|
||||
try
|
||||
{
|
||||
RemoveTimerFromDb(guildId, userId, type);
|
||||
StopTimer(guildId, userId, type);
|
||||
var guild = _client.GetGuild(guildId); // load the guild
|
||||
if (guild != null)
|
||||
{
|
||||
await guild.RemoveBanAsync(userId).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Couldn't unban user {0} in guild {1}", userId, guildId);
|
||||
}
|
||||
}
|
||||
else if (type == TimerType.AddRole)
|
||||
{
|
||||
try
|
||||
{
|
||||
RemoveTimerFromDb(guildId, userId, type);
|
||||
StopTimer(guildId, userId, type);
|
||||
var guild = _client.GetGuild(guildId);
|
||||
var user = guild?.GetUser(userId);
|
||||
var role = guild.GetRole(roleId.Value);
|
||||
if (guild != null && user != null && user.Roles.Contains(role))
|
||||
{
|
||||
await user.RemoveRoleAsync(role).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Couldn't remove role from user {0} in guild {1}", userId, guildId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// unmute the user, this will also remove the timer from the db
|
||||
await UnmuteUser(guildId, userId, _client.CurrentUser, reason: "Timed mute expired").ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
RemoveTimerFromDb(guildId, userId, type); // if unmute errored, just remove unmute from db
|
||||
Log.Warning(ex, "Couldn't unmute user {0} in guild {1}", userId, guildId);
|
||||
}
|
||||
}
|
||||
}, null, after, Timeout.InfiniteTimeSpan);
|
||||
|
||||
//add it, or stop the old one and add this one
|
||||
userUnTimers.AddOrUpdate((userId, type), (key) => toAdd, (key, old) =>
|
||||
{
|
||||
old.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
return toAdd;
|
||||
});
|
||||
}
|
||||
|
||||
public void StopTimer(ulong guildId, ulong userId, TimerType type)
|
||||
{
|
||||
if (!Un_Timers.TryGetValue(guildId, out ConcurrentDictionary<(ulong, TimerType), Timer> userTimer))
|
||||
return;
|
||||
|
||||
if (userTimer.TryRemove((userId, type), out Timer removed))
|
||||
{
|
||||
removed.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveTimerFromDb(ulong guildId, ulong userId, TimerType type)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
object toDelete;
|
||||
if (type == TimerType.Mute)
|
||||
{
|
||||
var config = uow.GuildConfigs.ForId(guildId, set => set.Include(x => x.UnmuteTimers));
|
||||
toDelete = config.UnmuteTimers.FirstOrDefault(x => x.UserId == userId);
|
||||
}
|
||||
else
|
||||
{
|
||||
var config = uow.GuildConfigs.ForId(guildId, set => set.Include(x => x.UnbanTimer));
|
||||
toDelete = config.UnbanTimer.FirstOrDefault(x => x.UserId == userId);
|
||||
}
|
||||
if (toDelete != null)
|
||||
{
|
||||
uow._context.Remove(toDelete);
|
||||
}
|
||||
uow.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
//using NadekoBot.Core.Services;
|
||||
//using System;
|
||||
//using System.Collections.Generic;
|
||||
//using System.IO;
|
||||
//using System.Linq;
|
||||
//using System.Text.RegularExpressions;
|
||||
|
||||
//namespace NadekoBot.Modules.Administration.Services
|
||||
//{
|
||||
// public class PackagesService : INService
|
||||
// {
|
||||
// public IEnumerable<string> Packages { get; private set; }
|
||||
|
||||
// public PackagesService()
|
||||
// {
|
||||
// ReloadAvailablePackages();
|
||||
// }
|
||||
|
||||
// public void ReloadAvailablePackages()
|
||||
// {
|
||||
// Packages = Directory.GetDirectories(Path.Combine(Appctx.BaseDirectory, "modules\\"), "NadekoBot.Modules.*", SearchOption.AllDirectories)
|
||||
// .SelectMany(x => Directory.GetFiles(x, "NadekoBot.Modules.*.dll"))
|
||||
// .Select(x => Path.GetFileNameWithoutExtension(x))
|
||||
// .Select(x =>
|
||||
// {
|
||||
// var m = Regex.Match(x, @"NadekoBot\.Modules\.(?<name>.*)");
|
||||
// return m.Groups["name"].Value;
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
//}
|
@@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Common.Replacements;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using Discord;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Core.Common;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public sealed class PlayingRotateService : INService
|
||||
{
|
||||
private readonly Timer _t;
|
||||
private readonly BotConfigService _bss;
|
||||
private readonly Replacer _rep;
|
||||
private readonly DbService _db;
|
||||
private readonly NadekoBot _bot;
|
||||
|
||||
private class TimerState
|
||||
{
|
||||
public int Index { get; set; }
|
||||
}
|
||||
|
||||
public PlayingRotateService(DiscordSocketClient client, DbService db, NadekoBot bot,
|
||||
BotConfigService bss, IEnumerable<IPlaceholderProvider> phProviders)
|
||||
{
|
||||
_db = db;
|
||||
_bot = bot;
|
||||
_bss = bss;
|
||||
|
||||
if (client.ShardId == 0)
|
||||
{
|
||||
_rep = new ReplacementBuilder()
|
||||
.WithClient(client)
|
||||
.WithProviders(phProviders)
|
||||
.Build();
|
||||
|
||||
_t = new Timer(RotatingStatuses, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
|
||||
}
|
||||
}
|
||||
|
||||
private async void RotatingStatuses(object objState)
|
||||
{
|
||||
try
|
||||
{
|
||||
var state = (TimerState) objState;
|
||||
|
||||
if (!_bss.Data.RotateStatuses) return;
|
||||
|
||||
IReadOnlyList<RotatingPlayingStatus> rotatingStatuses;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
rotatingStatuses = uow._context.RotatingStatus
|
||||
.AsNoTracking()
|
||||
.OrderBy(x => x.Id)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
if (rotatingStatuses.Count == 0)
|
||||
return;
|
||||
|
||||
var playingStatus = state.Index >= rotatingStatuses.Count
|
||||
? rotatingStatuses[state.Index = 0]
|
||||
: rotatingStatuses[state.Index++];
|
||||
|
||||
var statusText = _rep.Replace(playingStatus.Status);
|
||||
await _bot.SetGameAsync(statusText, playingStatus.Type);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Rotating playing status errored: {ErrorMessage}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> RemovePlayingAsync(int index)
|
||||
{
|
||||
if (index < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
|
||||
using var uow = _db.GetDbContext();
|
||||
var toRemove = await uow._context.RotatingStatus
|
||||
.AsQueryable()
|
||||
.AsNoTracking()
|
||||
.Skip(index)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (toRemove is null)
|
||||
return null;
|
||||
|
||||
uow._context.Remove(toRemove);
|
||||
await uow.SaveChangesAsync();
|
||||
return toRemove.Status;
|
||||
}
|
||||
|
||||
public async Task AddPlaying(ActivityType t, string status)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var toAdd = new RotatingPlayingStatus {Status = status, Type = t};
|
||||
uow._context.Add(toAdd);
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public bool ToggleRotatePlaying()
|
||||
{
|
||||
var enabled = false;
|
||||
_bss.ModifyConfig(bs => { enabled = bs.RotateStatuses = !bs.RotateStatuses; });
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public IReadOnlyList<RotatingPlayingStatus> GetRotatingStatuses()
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow._context.RotatingStatus.AsNoTracking().ToList();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,487 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Modules.Administration.Common;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using NadekoBot.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Core.Common.TypeReaders.Models;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public class ProtectionService : INService
|
||||
{
|
||||
private readonly ConcurrentDictionary<ulong, AntiRaidStats> _antiRaidGuilds
|
||||
= new ConcurrentDictionary<ulong, AntiRaidStats>();
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, AntiSpamStats> _antiSpamGuilds
|
||||
= new ConcurrentDictionary<ulong, AntiSpamStats>();
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, AntiAltStats> _antiAltGuilds
|
||||
= new ConcurrentDictionary<ulong, AntiAltStats>();
|
||||
|
||||
public event Func<PunishmentAction, ProtectionType, IGuildUser[], Task> OnAntiProtectionTriggered
|
||||
= delegate { return Task.CompletedTask; };
|
||||
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly MuteService _mute;
|
||||
private readonly DbService _db;
|
||||
private readonly UserPunishService _punishService;
|
||||
|
||||
private readonly Channel<PunishQueueItem> PunishUserQueue =
|
||||
System.Threading.Channels.Channel.CreateUnbounded<PunishQueueItem>(new UnboundedChannelOptions()
|
||||
{
|
||||
SingleReader = true,
|
||||
SingleWriter = false
|
||||
});
|
||||
|
||||
public ProtectionService(DiscordSocketClient client, NadekoBot bot,
|
||||
MuteService mute, DbService db, UserPunishService punishService)
|
||||
{
|
||||
_client = client;
|
||||
_mute = mute;
|
||||
_db = db;
|
||||
_punishService = punishService;
|
||||
|
||||
var ids = client.GetGuildIds();
|
||||
using (var uow = db.GetDbContext())
|
||||
{
|
||||
var configs = uow._context.Set<GuildConfig>()
|
||||
.AsQueryable()
|
||||
.Include(x => x.AntiRaidSetting)
|
||||
.Include(x => x.AntiSpamSetting)
|
||||
.ThenInclude(x => x.IgnoredChannels)
|
||||
.Include(x => x.AntiAltSetting)
|
||||
.Where(x => ids.Contains(x.GuildId))
|
||||
.ToList();
|
||||
|
||||
foreach (var gc in configs)
|
||||
{
|
||||
Initialize(gc);
|
||||
}
|
||||
}
|
||||
|
||||
_client.MessageReceived += HandleAntiSpam;
|
||||
_client.UserJoined += HandleUserJoined;
|
||||
|
||||
bot.JoinedGuild += _bot_JoinedGuild;
|
||||
_client.LeftGuild += _client_LeftGuild;
|
||||
|
||||
_ = Task.Run(RunQueue);
|
||||
}
|
||||
|
||||
private async Task RunQueue()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var item = await PunishUserQueue.Reader.ReadAsync();
|
||||
|
||||
var muteTime = item.MuteTime;
|
||||
var gu = item.User;
|
||||
try
|
||||
{
|
||||
await _punishService.ApplyPunishment(gu.Guild, gu, _client.CurrentUser,
|
||||
item.Action, muteTime, item.RoleId, $"{item.Type} Protection");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error in punish queue: {Message}", ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Task _client_LeftGuild(SocketGuild guild)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
TryStopAntiRaid(guild.Id);
|
||||
TryStopAntiSpam(guild.Id);
|
||||
await TryStopAntiAlt(guild.Id);
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task _bot_JoinedGuild(GuildConfig gc)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var gcWithData = uow.GuildConfigs.ForId(gc.GuildId,
|
||||
set => set
|
||||
.Include(x => x.AntiRaidSetting)
|
||||
.Include(x => x.AntiAltSetting)
|
||||
.Include(x => x.AntiSpamSetting)
|
||||
.ThenInclude(x => x.IgnoredChannels));
|
||||
|
||||
Initialize(gcWithData);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void Initialize(GuildConfig gc)
|
||||
{
|
||||
var raid = gc.AntiRaidSetting;
|
||||
var spam = gc.AntiSpamSetting;
|
||||
|
||||
if (raid != null)
|
||||
{
|
||||
var raidStats = new AntiRaidStats() { AntiRaidSettings = raid };
|
||||
_antiRaidGuilds[gc.GuildId] = raidStats;
|
||||
}
|
||||
|
||||
if (spam != null)
|
||||
_antiSpamGuilds[gc.GuildId] = new AntiSpamStats() { AntiSpamSettings = spam };
|
||||
|
||||
var alt = gc.AntiAltSetting;
|
||||
if (!(alt is null))
|
||||
_antiAltGuilds[gc.GuildId] = new AntiAltStats(alt);
|
||||
}
|
||||
|
||||
private Task HandleUserJoined(SocketGuildUser user)
|
||||
{
|
||||
if (user.IsBot)
|
||||
return Task.CompletedTask;
|
||||
|
||||
_antiRaidGuilds.TryGetValue(user.Guild.Id, out var maybeStats);
|
||||
_antiAltGuilds.TryGetValue(user.Guild.Id, out var maybeAlts);
|
||||
|
||||
if (maybeStats is null && maybeAlts is null)
|
||||
return Task.CompletedTask;
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
if (maybeAlts is AntiAltStats alts)
|
||||
{
|
||||
if (user.CreatedAt != default)
|
||||
{
|
||||
var diff = DateTime.UtcNow - user.CreatedAt.UtcDateTime;
|
||||
if (diff < alts.MinAge)
|
||||
{
|
||||
alts.Increment();
|
||||
|
||||
await PunishUsers(
|
||||
alts.Action,
|
||||
ProtectionType.Alting,
|
||||
alts.ActionDurationMinutes,
|
||||
alts.RoleId,
|
||||
user);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!(maybeStats is AntiRaidStats stats) || !stats.RaidUsers.Add(user))
|
||||
return;
|
||||
|
||||
++stats.UsersCount;
|
||||
|
||||
if (stats.UsersCount >= stats.AntiRaidSettings.UserThreshold)
|
||||
{
|
||||
var users = stats.RaidUsers.ToArray();
|
||||
stats.RaidUsers.Clear();
|
||||
var settings = stats.AntiRaidSettings;
|
||||
|
||||
await PunishUsers(settings.Action, ProtectionType.Raiding,
|
||||
settings.PunishDuration, null, users).ConfigureAwait(false);
|
||||
}
|
||||
await Task.Delay(1000 * stats.AntiRaidSettings.Seconds).ConfigureAwait(false);
|
||||
|
||||
stats.RaidUsers.TryRemove(user);
|
||||
--stats.UsersCount;
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task HandleAntiSpam(SocketMessage arg)
|
||||
{
|
||||
if (!(arg is SocketUserMessage msg) || msg.Author.IsBot)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (!(msg.Channel is ITextChannel channel))
|
||||
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();
|
||||
var settings = spamSettings.AntiSpamSettings;
|
||||
await PunishUsers(settings.Action, ProtectionType.Spamming, settings.MuteTime,
|
||||
settings.RoleId, (IGuildUser)msg.Author)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task PunishUsers(PunishmentAction action, ProtectionType pt, int muteTime, ulong? roleId,
|
||||
params IGuildUser[] gus)
|
||||
{
|
||||
Log.Information(
|
||||
"[{PunishType}] - Punishing [{Count}] users with [{PunishAction}] in {GuildName} guild",
|
||||
pt,
|
||||
gus.Length,
|
||||
action,
|
||||
gus[0].Guild.Name);
|
||||
|
||||
foreach (var gu in gus)
|
||||
{
|
||||
await PunishUserQueue.Writer.WriteAsync(new PunishQueueItem()
|
||||
{
|
||||
Action = action,
|
||||
Type = pt,
|
||||
User = gu,
|
||||
MuteTime = muteTime,
|
||||
RoleId = roleId
|
||||
});
|
||||
}
|
||||
|
||||
_ = OnAntiProtectionTriggered(action, pt, gus);
|
||||
}
|
||||
|
||||
public async Task<AntiRaidStats> StartAntiRaidAsync(ulong guildId, int userThreshold, int seconds,
|
||||
PunishmentAction action, int minutesDuration)
|
||||
{
|
||||
var g = _client.GetGuild(guildId);
|
||||
await _mute.GetMuteRole(g).ConfigureAwait(false);
|
||||
|
||||
if (action == PunishmentAction.AddRole)
|
||||
return null;
|
||||
|
||||
if (!IsDurationAllowed(action))
|
||||
minutesDuration = 0;
|
||||
|
||||
var stats = new AntiRaidStats()
|
||||
{
|
||||
AntiRaidSettings = new AntiRaidSetting()
|
||||
{
|
||||
Action = action,
|
||||
Seconds = seconds,
|
||||
UserThreshold = userThreshold,
|
||||
PunishDuration = minutesDuration
|
||||
}
|
||||
};
|
||||
|
||||
_antiRaidGuilds.AddOrUpdate(guildId, stats, (key, old) => stats);
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var gc = uow.GuildConfigs.ForId(guildId, set => set.Include(x => x.AntiRaidSetting));
|
||||
|
||||
gc.AntiRaidSetting = stats.AntiRaidSettings;
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public bool TryStopAntiRaid(ulong guildId)
|
||||
{
|
||||
if (_antiRaidGuilds.TryRemove(guildId, out _))
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var gc = uow.GuildConfigs.ForId(guildId, set => set.Include(x => x.AntiRaidSetting));
|
||||
|
||||
gc.AntiRaidSetting = null;
|
||||
uow.SaveChanges();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryStopAntiSpam(ulong guildId)
|
||||
{
|
||||
if (_antiSpamGuilds.TryRemove(guildId, out var removed))
|
||||
{
|
||||
removed.UserStats.ForEach(x => x.Value.Dispose());
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var gc = uow.GuildConfigs.ForId(guildId, set => set.Include(x => x.AntiSpamSetting)
|
||||
.ThenInclude(x => x.IgnoredChannels));
|
||||
|
||||
gc.AntiSpamSetting = null;
|
||||
uow.SaveChanges();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<AntiSpamStats> StartAntiSpamAsync(ulong guildId, int messageCount, PunishmentAction action,
|
||||
int punishDurationMinutes, ulong? roleId)
|
||||
{
|
||||
var g = _client.GetGuild(guildId);
|
||||
await _mute.GetMuteRole(g).ConfigureAwait(false);
|
||||
|
||||
if (!IsDurationAllowed(action))
|
||||
punishDurationMinutes = 0;
|
||||
|
||||
var stats = new AntiSpamStats
|
||||
{
|
||||
AntiSpamSettings = new AntiSpamSetting()
|
||||
{
|
||||
Action = action,
|
||||
MessageThreshold = messageCount,
|
||||
MuteTime = punishDurationMinutes,
|
||||
RoleId = roleId,
|
||||
}
|
||||
};
|
||||
|
||||
stats = _antiSpamGuilds.AddOrUpdate(guildId, stats, (key, old) =>
|
||||
{
|
||||
stats.AntiSpamSettings.IgnoredChannels = old.AntiSpamSettings.IgnoredChannels;
|
||||
return stats;
|
||||
});
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var gc = uow.GuildConfigs.ForId(guildId, set => set.Include(x => x.AntiSpamSetting));
|
||||
|
||||
if (gc.AntiSpamSetting != null)
|
||||
{
|
||||
gc.AntiSpamSetting.Action = stats.AntiSpamSettings.Action;
|
||||
gc.AntiSpamSetting.MessageThreshold = stats.AntiSpamSettings.MessageThreshold;
|
||||
gc.AntiSpamSetting.MuteTime = stats.AntiSpamSettings.MuteTime;
|
||||
gc.AntiSpamSetting.RoleId = stats.AntiSpamSettings.RoleId;
|
||||
}
|
||||
else
|
||||
{
|
||||
gc.AntiSpamSetting = stats.AntiSpamSettings;
|
||||
}
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
public async Task<bool?> AntiSpamIgnoreAsync(ulong guildId, ulong channelId)
|
||||
{
|
||||
var obj = new AntiSpamIgnore()
|
||||
{
|
||||
ChannelId = channelId
|
||||
};
|
||||
bool added;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var gc = uow.GuildConfigs.ForId(guildId, set => set.Include(x => x.AntiSpamSetting).ThenInclude(x => x.IgnoredChannels));
|
||||
var spam = gc.AntiSpamSetting;
|
||||
if (spam is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (spam.IgnoredChannels.Add(obj)) // if adding to db is successful
|
||||
{
|
||||
if (_antiSpamGuilds.TryGetValue(guildId, out var temp))
|
||||
temp.AntiSpamSettings.IgnoredChannels.Add(obj); // add to local cache
|
||||
added = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var toRemove = spam.IgnoredChannels.First(x => x.ChannelId == channelId);
|
||||
uow._context.Set<AntiSpamIgnore>().Remove(toRemove); // remove from db
|
||||
if (_antiSpamGuilds.TryGetValue(guildId, out var temp))
|
||||
{
|
||||
temp.AntiSpamSettings.IgnoredChannels.Remove(toRemove); // remove from local cache
|
||||
}
|
||||
added = false;
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
return added;
|
||||
}
|
||||
|
||||
public (AntiSpamStats, AntiRaidStats, AntiAltStats) GetAntiStats(ulong guildId)
|
||||
{
|
||||
_antiRaidGuilds.TryGetValue(guildId, out var antiRaidStats);
|
||||
_antiSpamGuilds.TryGetValue(guildId, out var antiSpamStats);
|
||||
_antiAltGuilds.TryGetValue(guildId, out var antiAltStats);
|
||||
|
||||
return (antiSpamStats, antiRaidStats, antiAltStats);
|
||||
}
|
||||
|
||||
public bool IsDurationAllowed(PunishmentAction action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case PunishmentAction.Ban:
|
||||
case PunishmentAction.Mute:
|
||||
case PunishmentAction.ChatMute:
|
||||
case PunishmentAction.VoiceMute:
|
||||
case PunishmentAction.AddRole:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StartAntiAltAsync(ulong guildId, int minAgeMinutes, PunishmentAction action,
|
||||
int actionDurationMinutes = 0, ulong? roleId = null)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigs.ForId(guildId, set => set.Include(x => x.AntiAltSetting));
|
||||
gc.AntiAltSetting = new AntiAltSetting()
|
||||
{
|
||||
Action = action,
|
||||
ActionDurationMinutes = actionDurationMinutes,
|
||||
MinAge = TimeSpan.FromMinutes(minAgeMinutes),
|
||||
RoleId = roleId,
|
||||
};
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
_antiAltGuilds[guildId] = new AntiAltStats(gc.AntiAltSetting);
|
||||
}
|
||||
|
||||
public async Task<bool> TryStopAntiAlt(ulong guildId)
|
||||
{
|
||||
if (!_antiAltGuilds.TryRemove(guildId, out _))
|
||||
return false;
|
||||
|
||||
using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigs.ForId(guildId, set => set.Include(x => x.AntiAltSetting));
|
||||
gc.AntiAltSetting = null;
|
||||
await uow.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.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);
|
||||
private readonly LogCommandService _logService;
|
||||
|
||||
public PruneService(LogCommandService logService)
|
||||
{
|
||||
this._logService = logService;
|
||||
}
|
||||
|
||||
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).FlattenAsync().ConfigureAwait(false)).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)
|
||||
{
|
||||
_logService.AddDeleteIgnore(x.Id);
|
||||
|
||||
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).FlattenAsync().ConfigureAwait(false)).Where(predicate).Take(amount).ToArray();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
//ignore
|
||||
}
|
||||
finally
|
||||
{
|
||||
_pruningGuilds.TryRemove(channel.GuildId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,190 @@
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using NadekoBot.Extensions;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public class RoleCommandsService : INService
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly ConcurrentDictionary<ulong, IndexedCollection<ReactionRoleMessage>> _models;
|
||||
|
||||
public RoleCommandsService(DiscordSocketClient client, DbService db,
|
||||
NadekoBot bot)
|
||||
{
|
||||
_db = db;
|
||||
_client = client;
|
||||
#if !GLOBAL_NADEKO
|
||||
_models = bot.AllGuildConfigs.ToDictionary(x => x.GuildId,
|
||||
x => x.ReactionRoleMessages)
|
||||
.ToConcurrent();
|
||||
|
||||
_client.ReactionAdded += _client_ReactionAdded;
|
||||
_client.ReactionRemoved += _client_ReactionRemoved;
|
||||
#endif
|
||||
}
|
||||
|
||||
private Task _client_ReactionAdded(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel chan, SocketReaction reaction)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!reaction.User.IsSpecified ||
|
||||
reaction.User.Value.IsBot ||
|
||||
!(reaction.User.Value is SocketGuildUser gusr))
|
||||
return;
|
||||
|
||||
if (!(chan is SocketGuildChannel gch))
|
||||
return;
|
||||
|
||||
if (!_models.TryGetValue(gch.Guild.Id, out var confs))
|
||||
return;
|
||||
|
||||
var conf = confs.FirstOrDefault(x => x.MessageId == msg.Id);
|
||||
|
||||
if (conf == null)
|
||||
return;
|
||||
|
||||
// compare emote names for backwards compatibility :facepalm:
|
||||
var reactionRole = conf.ReactionRoles.FirstOrDefault(x => x.EmoteName == reaction.Emote.Name || x.EmoteName == reaction.Emote.ToString());
|
||||
if (reactionRole != null)
|
||||
{
|
||||
if (conf.Exclusive)
|
||||
{
|
||||
var roleIds = conf.ReactionRoles.Select(x => x.RoleId)
|
||||
.Where(x => x != reactionRole.RoleId)
|
||||
.Select(x => gusr.Guild.GetRole(x))
|
||||
.Where(x => x != null);
|
||||
|
||||
var __ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
//if the role is exclusive,
|
||||
// remove all other reactions user added to the message
|
||||
var dl = await msg.GetOrDownloadAsync().ConfigureAwait(false);
|
||||
foreach (var r in dl.Reactions)
|
||||
{
|
||||
if (r.Key.Name == reaction.Emote.Name)
|
||||
continue;
|
||||
try { await dl.RemoveReactionAsync(r.Key, gusr).ConfigureAwait(false); } catch { }
|
||||
await Task.Delay(100).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
await gusr.RemoveRolesAsync(roleIds).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var toAdd = gusr.Guild.GetRole(reactionRole.RoleId);
|
||||
if (toAdd != null && !gusr.Roles.Contains(toAdd))
|
||||
{
|
||||
await gusr.AddRolesAsync(new[] { toAdd }).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var dl = await msg.GetOrDownloadAsync().ConfigureAwait(false);
|
||||
await dl.RemoveReactionAsync(reaction.Emote, dl.Author,
|
||||
new RequestOptions()
|
||||
{
|
||||
RetryMode = RetryMode.RetryRatelimit | RetryMode.Retry502
|
||||
}).ConfigureAwait(false);
|
||||
Log.Warning("User {0} is adding unrelated reactions to the reaction roles message.", dl.Author);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task _client_ReactionRemoved(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel chan, SocketReaction reaction)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!reaction.User.IsSpecified ||
|
||||
reaction.User.Value.IsBot ||
|
||||
!(reaction.User.Value is SocketGuildUser gusr))
|
||||
return;
|
||||
|
||||
if (!(chan is SocketGuildChannel gch))
|
||||
return;
|
||||
|
||||
if (!_models.TryGetValue(gch.Guild.Id, out var confs))
|
||||
return;
|
||||
|
||||
var conf = confs.FirstOrDefault(x => x.MessageId == msg.Id);
|
||||
|
||||
if (conf == null)
|
||||
return;
|
||||
|
||||
var reactionRole = conf.ReactionRoles.FirstOrDefault(x => x.EmoteName == reaction.Emote.Name || x.EmoteName == reaction.Emote.ToString());
|
||||
|
||||
if (reactionRole != null)
|
||||
{
|
||||
var role = gusr.Guild.GetRole(reactionRole.RoleId);
|
||||
if (role == null)
|
||||
return;
|
||||
await gusr.RemoveRoleAsync(role).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public bool Get(ulong id, out IndexedCollection<ReactionRoleMessage> rrs)
|
||||
{
|
||||
return _models.TryGetValue(id, out rrs);
|
||||
}
|
||||
|
||||
public bool Add(ulong id, ReactionRoleMessage rrm)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var gc = uow.GuildConfigs.ForId(id, set => set
|
||||
.Include(x => x.ReactionRoleMessages)
|
||||
.ThenInclude(x => x.ReactionRoles));
|
||||
if (gc.ReactionRoleMessages.Count >= 10)
|
||||
return false;
|
||||
gc.ReactionRoleMessages.Add(rrm);
|
||||
_models.AddOrUpdate(id,
|
||||
gc.ReactionRoleMessages,
|
||||
delegate { return gc.ReactionRoleMessages; });
|
||||
uow.SaveChanges();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Remove(ulong id, int index)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var gc = uow.GuildConfigs.ForId(id,
|
||||
set => set.Include(x => x.ReactionRoleMessages)
|
||||
.ThenInclude(x => x.ReactionRoles));
|
||||
uow._context.Set<ReactionRole>()
|
||||
.RemoveRange(gc.ReactionRoleMessages[index].ReactionRoles);
|
||||
gc.ReactionRoleMessages.RemoveAt(index);
|
||||
_models.AddOrUpdate(id,
|
||||
gc.ReactionRoleMessages,
|
||||
delegate { return gc.ReactionRoleMessages; });
|
||||
uow.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,270 @@
|
||||
using Discord;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Modules.Xp;
|
||||
|
||||
namespace NadekoBot.Core.Modules.Administration.Services
|
||||
{
|
||||
public class SelfAssignedRolesService : INService
|
||||
{
|
||||
private readonly DbService _db;
|
||||
|
||||
public enum RemoveResult
|
||||
{
|
||||
Removed, // successfully removed
|
||||
Err_Not_Assignable, // not assignable (error)
|
||||
Err_Not_Have, // you don't have a role you want to remove (error)
|
||||
Err_Not_Perms, // bot doesn't have perms (error)
|
||||
}
|
||||
|
||||
public enum AssignResult
|
||||
{
|
||||
Assigned, // successfully removed
|
||||
Err_Not_Assignable, // not assignable (error)
|
||||
Err_Already_Have, // you already have that role (error)
|
||||
Err_Not_Perms, // bot doesn't have perms (error)
|
||||
Err_Lvl_Req, // you are not required level (error)
|
||||
}
|
||||
|
||||
public SelfAssignedRolesService(DbService db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public bool AddNew(ulong guildId, IRole role, int group)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var roles = uow.SelfAssignedRoles.GetFromGuild(guildId);
|
||||
if (roles.Any(s => s.RoleId == role.Id && s.GuildId == role.Guild.Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uow.SelfAssignedRoles.Add(new SelfAssignedRole
|
||||
{
|
||||
Group = group,
|
||||
RoleId = role.Id,
|
||||
GuildId = role.Guild.Id
|
||||
});
|
||||
uow.SaveChanges();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ToggleAdSarm(ulong guildId)
|
||||
{
|
||||
bool newval;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GuildConfigs.ForId(guildId, set => set);
|
||||
newval = config.AutoDeleteSelfAssignedRoleMessages = !config.AutoDeleteSelfAssignedRoleMessages;
|
||||
uow.SaveChanges();
|
||||
}
|
||||
return newval;
|
||||
}
|
||||
|
||||
public async Task<(AssignResult Result, bool AutoDelete, object extra)> Assign(IGuildUser guildUser, IRole role)
|
||||
{
|
||||
LevelStats userLevelData;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var stats = uow.Xp.GetOrCreateUser(guildUser.Guild.Id, guildUser.Id);
|
||||
userLevelData = new LevelStats(stats.Xp + stats.AwardedXp);
|
||||
}
|
||||
|
||||
var (autoDelete, exclusive, roles) = GetAdAndRoles(guildUser.Guild.Id);
|
||||
|
||||
var theRoleYouWant = roles.FirstOrDefault(r => r.RoleId == role.Id);
|
||||
if (theRoleYouWant == null)
|
||||
{
|
||||
return (AssignResult.Err_Not_Assignable, autoDelete, null);
|
||||
}
|
||||
else if (theRoleYouWant.LevelRequirement > userLevelData.Level)
|
||||
{
|
||||
return (AssignResult.Err_Lvl_Req, autoDelete, theRoleYouWant.LevelRequirement);
|
||||
}
|
||||
else if (guildUser.RoleIds.Contains(role.Id))
|
||||
{
|
||||
return (AssignResult.Err_Already_Have, autoDelete, null);
|
||||
}
|
||||
|
||||
var roleIds = roles
|
||||
.Where(x => x.Group == theRoleYouWant.Group)
|
||||
.Select(x => x.RoleId).ToArray();
|
||||
if (exclusive)
|
||||
{
|
||||
var sameRoles = guildUser.RoleIds
|
||||
.Where(r => roleIds.Contains(r));
|
||||
|
||||
foreach (var roleId in sameRoles)
|
||||
{
|
||||
var sameRole = guildUser.Guild.GetRole(roleId);
|
||||
if (sameRole != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await guildUser.RemoveRoleAsync(sameRole).ConfigureAwait(false);
|
||||
await Task.Delay(300).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
await guildUser.AddRoleAsync(role).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return (AssignResult.Err_Not_Perms, autoDelete, ex);
|
||||
}
|
||||
|
||||
return (AssignResult.Assigned, autoDelete, null);
|
||||
}
|
||||
|
||||
public async Task<bool> SetNameAsync(ulong guildId, int group, string name)
|
||||
{
|
||||
bool set = false;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var gc = uow.GuildConfigs.ForId(guildId, y => y.Include(x => x.SelfAssignableRoleGroupNames));
|
||||
var toUpdate = gc.SelfAssignableRoleGroupNames.FirstOrDefault(x => x.Number == group);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
if (toUpdate != null)
|
||||
gc.SelfAssignableRoleGroupNames.Remove(toUpdate);
|
||||
}
|
||||
else if (toUpdate == null)
|
||||
{
|
||||
gc.SelfAssignableRoleGroupNames.Add(new GroupName
|
||||
{
|
||||
Name = name,
|
||||
Number = group,
|
||||
});
|
||||
set = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
toUpdate.Name = name;
|
||||
set = true;
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
public async Task<(RemoveResult Result, bool AutoDelete)> Remove(IGuildUser guildUser, IRole role)
|
||||
{
|
||||
var (autoDelete, _, roles) = GetAdAndRoles(guildUser.Guild.Id);
|
||||
|
||||
if (roles.FirstOrDefault(r => r.RoleId == role.Id) == null)
|
||||
{
|
||||
return (RemoveResult.Err_Not_Assignable, autoDelete);
|
||||
}
|
||||
if (!guildUser.RoleIds.Contains(role.Id))
|
||||
{
|
||||
return (RemoveResult.Err_Not_Have, autoDelete);
|
||||
}
|
||||
try
|
||||
{
|
||||
await guildUser.RemoveRoleAsync(role).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return (RemoveResult.Err_Not_Perms, autoDelete);
|
||||
}
|
||||
|
||||
return (RemoveResult.Removed, autoDelete);
|
||||
}
|
||||
|
||||
public bool RemoveSar(ulong guildId, ulong roleId)
|
||||
{
|
||||
bool success;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
success = uow.SelfAssignedRoles.DeleteByGuildAndRoleId(guildId, roleId);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
public (bool AutoDelete, bool Exclusive, IEnumerable<SelfAssignedRole>) GetAdAndRoles(ulong guildId)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var gc = uow.GuildConfigs.ForId(guildId, set => set);
|
||||
var autoDelete = gc.AutoDeleteSelfAssignedRoleMessages;
|
||||
var exclusive = gc.ExclusiveSelfAssignedRoles;
|
||||
var roles = uow.SelfAssignedRoles.GetFromGuild(guildId);
|
||||
|
||||
return (autoDelete, exclusive, roles);
|
||||
}
|
||||
}
|
||||
|
||||
public bool SetLevelReq(ulong guildId, IRole role, int level)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var roles = uow.SelfAssignedRoles.GetFromGuild(guildId);
|
||||
var sar = roles.FirstOrDefault(x => x.RoleId == role.Id);
|
||||
if (sar != null)
|
||||
{
|
||||
sar.LevelRequirement = level;
|
||||
uow.SaveChanges();
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ToggleEsar(ulong guildId)
|
||||
{
|
||||
bool areExclusive;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GuildConfigs.ForId(guildId, set => set);
|
||||
|
||||
areExclusive = config.ExclusiveSelfAssignedRoles = !config.ExclusiveSelfAssignedRoles;
|
||||
uow.SaveChanges();
|
||||
}
|
||||
return areExclusive;
|
||||
}
|
||||
|
||||
public (bool Exclusive, IEnumerable<(SelfAssignedRole Model, IRole Role)> Roles, IDictionary<int, string> GroupNames) GetRoles(IGuild guild)
|
||||
{
|
||||
var exclusive = false;
|
||||
|
||||
IEnumerable<(SelfAssignedRole Model, IRole Role)> roles;
|
||||
IDictionary<int, string> groupNames;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var gc = uow.GuildConfigs.ForId(guild.Id, set => set.Include(x => x.SelfAssignableRoleGroupNames));
|
||||
exclusive = gc.ExclusiveSelfAssignedRoles;
|
||||
groupNames = gc.SelfAssignableRoleGroupNames.ToDictionary(x => x.Number, x => x.Name);
|
||||
var roleModels = uow.SelfAssignedRoles.GetFromGuild(guild.Id);
|
||||
roles = roleModels
|
||||
.Select(x => (Model: x, Role: guild.GetRole(x.RoleId)));
|
||||
uow.SelfAssignedRoles.RemoveRange(roles.Where(x => x.Role == null).Select(x => x.Model).ToArray());
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
return (exclusive, roles.Where(x => x.Role != null), groupNames);
|
||||
}
|
||||
}
|
||||
}
|
436
src/NadekoBot/Modules/Administration/Services/SelfService.cs
Normal file
436
src/NadekoBot/Modules/Administration/Services/SelfService.cs
Normal file
@@ -0,0 +1,436 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.Services;
|
||||
using StackExchange.Redis;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Newtonsoft.Json;
|
||||
using NadekoBot.Common.ShardCom;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using System.Threading;
|
||||
using System.Collections.Concurrent;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public sealed class SelfService : ILateExecutor, IReadyExecutor, INService
|
||||
{
|
||||
private readonly ConnectionMultiplexer _redis;
|
||||
private readonly CommandHandler _cmdHandler;
|
||||
private readonly DbService _db;
|
||||
private readonly IBotStrings _strings;
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
private readonly IBotCredentials _creds;
|
||||
|
||||
private ImmutableDictionary<ulong, IDMChannel> ownerChannels =
|
||||
new Dictionary<ulong, IDMChannel>().ToImmutableDictionary();
|
||||
|
||||
private ConcurrentDictionary<ulong?, ConcurrentDictionary<int, Timer>> _autoCommands =
|
||||
new ConcurrentDictionary<ulong?, ConcurrentDictionary<int, Timer>>();
|
||||
|
||||
private readonly IDataCache _cache;
|
||||
private readonly IImageCache _imgs;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly BotConfigService _bss;
|
||||
|
||||
public SelfService(DiscordSocketClient client, CommandHandler cmdHandler, DbService db,
|
||||
IBotStrings strings, IBotCredentials creds, IDataCache cache, IHttpClientFactory factory,
|
||||
BotConfigService bss)
|
||||
{
|
||||
_redis = cache.Redis;
|
||||
_cmdHandler = cmdHandler;
|
||||
_db = db;
|
||||
_strings = strings;
|
||||
_client = client;
|
||||
_creds = creds;
|
||||
_cache = cache;
|
||||
_imgs = cache.LocalImages;
|
||||
_httpFactory = factory;
|
||||
_bss = bss;
|
||||
|
||||
var sub = _redis.GetSubscriber();
|
||||
if (_client.ShardId == 0)
|
||||
{
|
||||
sub.Subscribe(_creds.RedisKey() + "_reload_images",
|
||||
delegate { _imgs.Reload(); }, CommandFlags.FireAndForget);
|
||||
}
|
||||
|
||||
sub.Subscribe(_creds.RedisKey() + "_leave_guild", async (ch, v) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var guildStr = v.ToString()?.Trim().ToUpperInvariant();
|
||||
if (string.IsNullOrWhiteSpace(guildStr))
|
||||
return;
|
||||
var server = _client.Guilds.FirstOrDefault(g => g.Id.ToString() == guildStr) ??
|
||||
_client.Guilds.FirstOrDefault(g => g.Name.Trim().ToUpperInvariant() == guildStr);
|
||||
|
||||
if (server == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (server.OwnerId != _client.CurrentUser.Id)
|
||||
{
|
||||
await server.LeaveAsync().ConfigureAwait(false);
|
||||
Log.Information($"Left server {server.Name} [{server.Id}]");
|
||||
}
|
||||
else
|
||||
{
|
||||
await server.DeleteAsync().ConfigureAwait(false);
|
||||
Log.Information($"Deleted server {server.Name} [{server.Id}]");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}, CommandFlags.FireAndForget);
|
||||
}
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
|
||||
_autoCommands = uow._context
|
||||
.AutoCommands
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Interval >= 5)
|
||||
.AsEnumerable()
|
||||
.GroupBy(x => x.GuildId)
|
||||
.ToDictionary(x => x.Key,
|
||||
y => y.ToDictionary(x => x.Id, TimerFromAutoCommand)
|
||||
.ToConcurrent())
|
||||
.ToConcurrent();
|
||||
|
||||
var startupCommands = uow._context.AutoCommands.AsNoTracking().Where(x => x.Interval == 0);
|
||||
foreach (var cmd in startupCommands)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ExecuteCommand(cmd).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (_client.ShardId == 0)
|
||||
{
|
||||
await LoadOwnerChannels().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private Timer TimerFromAutoCommand(AutoCommand x)
|
||||
{
|
||||
return new Timer(async (obj) => await ExecuteCommand((AutoCommand) obj).ConfigureAwait(false),
|
||||
x,
|
||||
x.Interval * 1000,
|
||||
x.Interval * 1000);
|
||||
}
|
||||
|
||||
private async Task ExecuteCommand(AutoCommand cmd)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (cmd.GuildId is null)
|
||||
return;
|
||||
|
||||
var guildShard = (int) ((cmd.GuildId.Value >> 22) % (ulong) _creds.TotalShards);
|
||||
if (guildShard != _client.ShardId)
|
||||
return;
|
||||
var prefix = _cmdHandler.GetPrefix(cmd.GuildId);
|
||||
//if someone already has .die as their startup command, ignore it
|
||||
if (cmd.CommandText.StartsWith(prefix + "die", StringComparison.InvariantCulture))
|
||||
return;
|
||||
await _cmdHandler.ExecuteExternal(cmd.GuildId, cmd.ChannelId, cmd.CommandText).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error in SelfService ExecuteCommand");
|
||||
}
|
||||
}
|
||||
|
||||
public void AddNewAutoCommand(AutoCommand cmd)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
uow._context.AutoCommands.Add(cmd);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
if (cmd.Interval >= 5)
|
||||
{
|
||||
var autos = _autoCommands.GetOrAdd(cmd.GuildId, new ConcurrentDictionary<int, Timer>());
|
||||
autos.AddOrUpdate(cmd.Id, key => TimerFromAutoCommand(cmd), (key, old) =>
|
||||
{
|
||||
old.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
return TimerFromAutoCommand(cmd);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<AutoCommand> GetStartupCommands()
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow._context
|
||||
.AutoCommands
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Interval == 0)
|
||||
.OrderBy(x => x.Id)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public IEnumerable<AutoCommand> GetAutoCommands()
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow._context
|
||||
.AutoCommands
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Interval >= 5)
|
||||
.OrderBy(x => x.Id)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private async Task LoadOwnerChannels()
|
||||
{
|
||||
var channels = await Task.WhenAll(_creds.OwnerIds.Select(id =>
|
||||
{
|
||||
var user = _client.GetUser(id);
|
||||
if (user == null)
|
||||
return Task.FromResult<IDMChannel>(null);
|
||||
|
||||
return user.GetOrCreateDMChannelAsync();
|
||||
})).ConfigureAwait(false);
|
||||
|
||||
ownerChannels = channels.Where(x => x != null)
|
||||
.ToDictionary(x => x.Recipient.Id, x => x)
|
||||
.ToImmutableDictionary();
|
||||
|
||||
if (!ownerChannels.Any())
|
||||
Log.Warning(
|
||||
"No owner channels created! Make sure you've specified the correct OwnerId in the credentials.json file and invited the bot to a Discord server.");
|
||||
else
|
||||
Log.Information($"Created {ownerChannels.Count} out of {_creds.OwnerIds.Length} owner message channels.");
|
||||
}
|
||||
|
||||
public Task LeaveGuild(string guildStr)
|
||||
{
|
||||
var sub = _cache.Redis.GetSubscriber();
|
||||
return sub.PublishAsync(_creds.RedisKey() + "_leave_guild", guildStr);
|
||||
}
|
||||
|
||||
// forwards dms
|
||||
public async Task LateExecute(DiscordSocketClient client, IGuild guild, IUserMessage msg)
|
||||
{
|
||||
var bs = _bss.Data;
|
||||
if (msg.Channel is IDMChannel && _bss.Data.ForwardMessages && ownerChannels.Any())
|
||||
{
|
||||
var title = _strings.GetText("dm_from") +
|
||||
$" [{msg.Author}]({msg.Author.Id})";
|
||||
|
||||
var attachamentsTxt = _strings.GetText("attachments");
|
||||
|
||||
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 (bs.ForwardToAllOwners)
|
||||
{
|
||||
var allOwnerChannels = ownerChannels.Values;
|
||||
|
||||
foreach (var ownerCh in allOwnerChannels.Where(ch => ch.Recipient.Id != msg.Author.Id))
|
||||
{
|
||||
try
|
||||
{
|
||||
await ownerCh.SendConfirmAsync(title, toSend).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Log.Warning("Can't contact owner with id {0}", ownerCh.Recipient.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var firstOwnerChannel = ownerChannels.Values.First();
|
||||
if (firstOwnerChannel.Recipient.Id != msg.Author.Id)
|
||||
{
|
||||
try
|
||||
{
|
||||
await firstOwnerChannel.SendConfirmAsync(title, toSend).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool RestartBot()
|
||||
{
|
||||
var cmd = _creds.RestartCommand;
|
||||
if (string.IsNullOrWhiteSpace(cmd?.Cmd))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Restart();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RemoveStartupCommand(int index, out AutoCommand cmd)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
cmd = uow._context.AutoCommands
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Interval == 0)
|
||||
.Skip(index)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (cmd != null)
|
||||
{
|
||||
uow._context.Remove(cmd);
|
||||
uow.SaveChanges();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool RemoveAutoCommand(int index, out AutoCommand cmd)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
cmd = uow._context.AutoCommands
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Interval >= 5)
|
||||
.Skip(index)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (cmd != null)
|
||||
{
|
||||
uow._context.Remove(cmd);
|
||||
if (_autoCommands.TryGetValue(cmd.GuildId, out var autos))
|
||||
if (autos.TryRemove(cmd.Id, out var timer))
|
||||
timer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
uow.SaveChanges();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> SetAvatar(string img)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(img))
|
||||
return false;
|
||||
|
||||
if (!Uri.IsWellFormedUriString(img, UriKind.Absolute))
|
||||
return false;
|
||||
|
||||
var uri = new Uri(img);
|
||||
|
||||
using (var http = _httpFactory.CreateClient())
|
||||
using (var sr = await http.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
|
||||
{
|
||||
if (!sr.IsImage())
|
||||
return false;
|
||||
|
||||
// i can't just do ReadAsStreamAsync because dicord.net's image poops itself
|
||||
var imgData = await sr.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
|
||||
using (var imgStream = imgData.ToStream())
|
||||
{
|
||||
await _client.CurrentUser.ModifyAsync(u => u.Avatar = new Image(imgStream)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ClearStartupCommands()
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var toRemove = uow._context
|
||||
.AutoCommands
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Interval == 0);
|
||||
|
||||
uow._context.AutoCommands.RemoveRange(toRemove);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
public void ReloadImages()
|
||||
{
|
||||
var sub = _cache.Redis.GetSubscriber();
|
||||
sub.Publish(_creds.RedisKey() + "_reload_images", "");
|
||||
}
|
||||
|
||||
public void Die()
|
||||
{
|
||||
var sub = _cache.Redis.GetSubscriber();
|
||||
sub.Publish(_creds.RedisKey() + "_die", "", CommandFlags.FireAndForget);
|
||||
}
|
||||
|
||||
public void Restart()
|
||||
{
|
||||
Process.Start(_creds.RestartCommand.Cmd, _creds.RestartCommand.Args);
|
||||
var sub = _cache.Redis.GetSubscriber();
|
||||
sub.Publish(_creds.RedisKey() + "_die", "", CommandFlags.FireAndForget);
|
||||
}
|
||||
|
||||
public bool RestartShard(int shardId)
|
||||
{
|
||||
if (shardId < 0 || shardId >= _creds.TotalShards)
|
||||
return false;
|
||||
|
||||
var pub = _cache.Redis.GetSubscriber();
|
||||
pub.Publish(_creds.RedisKey() + "_shardcoord_stop",
|
||||
JsonConvert.SerializeObject(shardId),
|
||||
CommandFlags.FireAndForget);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ForwardMessages()
|
||||
{
|
||||
var isForwarding = false;
|
||||
_bss.ModifyConfig(config => { isForwarding = config.ForwardMessages = !config.ForwardMessages; });
|
||||
|
||||
return isForwarding;
|
||||
}
|
||||
|
||||
public bool ForwardToAll()
|
||||
{
|
||||
var isToAll = false;
|
||||
_bss.ModifyConfig(config => { isToAll = config.ForwardToAllOwners = !config.ForwardToAllOwners; });
|
||||
return isToAll;
|
||||
}
|
||||
|
||||
public IEnumerable<ShardComMessage> GetAllShardStatuses()
|
||||
{
|
||||
var db = _cache.Redis.GetDatabase();
|
||||
return db.ListRange(_creds.RedisKey() + "_shardstats")
|
||||
.Select(x => JsonConvert.DeserializeObject<ShardComMessage>(x));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,488 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Replacements;
|
||||
using NadekoBot.Core.Common.TypeReaders.Models;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Permissions.Services;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public class UserPunishService : INService
|
||||
{
|
||||
private readonly MuteService _mute;
|
||||
private readonly DbService _db;
|
||||
private readonly BlacklistService _blacklistService;
|
||||
private readonly Timer _warnExpiryTimer;
|
||||
|
||||
public UserPunishService(MuteService mute, DbService db, BlacklistService blacklistService)
|
||||
{
|
||||
_mute = mute;
|
||||
_db = db;
|
||||
_blacklistService = blacklistService;
|
||||
|
||||
_warnExpiryTimer = new Timer(async _ =>
|
||||
{
|
||||
await CheckAllWarnExpiresAsync();
|
||||
}, null, TimeSpan.FromSeconds(0), TimeSpan.FromHours(12));
|
||||
}
|
||||
|
||||
public async Task<WarningPunishment> Warn(IGuild guild, ulong userId, IUser mod, string reason)
|
||||
{
|
||||
var modName = mod.ToString();
|
||||
|
||||
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.GetDbContext())
|
||||
{
|
||||
ps = uow.GuildConfigs.ForId(guildId, set => set.Include(x => x.WarnPunishments))
|
||||
.WarnPunishments;
|
||||
|
||||
warnings += uow.Warnings
|
||||
.ForId(guildId, userId)
|
||||
.Where(w => !w.Forgiven && w.UserId == userId)
|
||||
.Count();
|
||||
|
||||
uow.Warnings.Add(warn);
|
||||
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
var p = ps.FirstOrDefault(x => x.Count == warnings);
|
||||
|
||||
if (p != null)
|
||||
{
|
||||
var user = await guild.GetUserAsync(userId).ConfigureAwait(false);
|
||||
if (user == null)
|
||||
return null;
|
||||
|
||||
await ApplyPunishment(guild, user, mod, p.Punishment, p.Time, p.RoleId, "Warned too many times.");
|
||||
return p;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task ApplyPunishment(IGuild guild, IGuildUser user, IUser mod, PunishmentAction p, int minutes,
|
||||
ulong? roleId, string reason)
|
||||
{
|
||||
switch (p)
|
||||
{
|
||||
case PunishmentAction.Mute:
|
||||
if (minutes == 0)
|
||||
await _mute.MuteUser(user, mod, reason: reason).ConfigureAwait(false);
|
||||
else
|
||||
await _mute.TimedMute(user, mod, TimeSpan.FromMinutes(minutes), reason: reason)
|
||||
.ConfigureAwait(false);
|
||||
break;
|
||||
case PunishmentAction.VoiceMute:
|
||||
if (minutes == 0)
|
||||
await _mute.MuteUser(user, mod, MuteType.Voice, reason).ConfigureAwait(false);
|
||||
else
|
||||
await _mute.TimedMute(user, mod, TimeSpan.FromMinutes(minutes), MuteType.Voice, reason)
|
||||
.ConfigureAwait(false);
|
||||
break;
|
||||
case PunishmentAction.ChatMute:
|
||||
if (minutes == 0)
|
||||
await _mute.MuteUser(user, mod, MuteType.Chat, reason).ConfigureAwait(false);
|
||||
else
|
||||
await _mute.TimedMute(user, mod, TimeSpan.FromMinutes(minutes), MuteType.Chat, reason)
|
||||
.ConfigureAwait(false);
|
||||
break;
|
||||
case PunishmentAction.Kick:
|
||||
await user.KickAsync(reason).ConfigureAwait(false);
|
||||
break;
|
||||
case PunishmentAction.Ban:
|
||||
if (minutes == 0)
|
||||
await guild.AddBanAsync(user, reason: reason).ConfigureAwait(false);
|
||||
else
|
||||
await _mute.TimedBan(user.Guild, user, TimeSpan.FromMinutes(minutes), reason)
|
||||
.ConfigureAwait(false);
|
||||
break;
|
||||
case PunishmentAction.Softban:
|
||||
await guild.AddBanAsync(user, 7, reason: $"Softban | {reason}").ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await guild.RemoveBanAsync(user).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await guild.RemoveBanAsync(user).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
break;
|
||||
case PunishmentAction.RemoveRoles:
|
||||
await user.RemoveRolesAsync(user.GetRoles().Where(x => !x.IsManaged && x != x.Guild.EveryoneRole))
|
||||
.ConfigureAwait(false);
|
||||
break;
|
||||
case PunishmentAction.AddRole:
|
||||
if (roleId is null)
|
||||
return;
|
||||
var role = guild.GetRole(roleId.Value);
|
||||
if (!(role is null))
|
||||
{
|
||||
if (minutes == 0)
|
||||
await user.AddRoleAsync(role).ConfigureAwait(false);
|
||||
else
|
||||
await _mute.TimedRole(user, TimeSpan.FromMinutes(minutes), reason, role)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning($"Can't find role {roleId.Value} on server {guild.Id} to apply punishment.");
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CheckAllWarnExpiresAsync()
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var cleared = await uow._context.Database.ExecuteSqlRawAsync($@"UPDATE Warnings
|
||||
SET Forgiven = 1,
|
||||
ForgivenBy = 'Expiry'
|
||||
WHERE GuildId in (SELECT GuildId FROM GuildConfigs WHERE WarnExpireHours > 0 AND WarnExpireAction = 0)
|
||||
AND Forgiven = 0
|
||||
AND DateAdded < datetime('now', (SELECT '-' || WarnExpireHours || ' hours' FROM GuildConfigs as gc WHERE gc.GuildId = Warnings.GuildId));");
|
||||
|
||||
var deleted = await uow._context.Database.ExecuteSqlRawAsync($@"DELETE FROM Warnings
|
||||
WHERE GuildId in (SELECT GuildId FROM GuildConfigs WHERE WarnExpireHours > 0 AND WarnExpireAction = 1)
|
||||
AND DateAdded < datetime('now', (SELECT '-' || WarnExpireHours || ' hours' FROM GuildConfigs as gc WHERE gc.GuildId = Warnings.GuildId));");
|
||||
|
||||
if(cleared > 0 || deleted > 0)
|
||||
{
|
||||
Log.Information($"Cleared {cleared} warnings and deleted {deleted} warnings due to expiry.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CheckWarnExpiresAsync(ulong guildId)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GuildConfigs.ForId(guildId, inc => inc);
|
||||
|
||||
if (config.WarnExpireHours == 0)
|
||||
return;
|
||||
|
||||
var hours = $"{-config.WarnExpireHours} hours";
|
||||
if (config.WarnExpireAction == WarnExpireAction.Clear)
|
||||
{
|
||||
await uow._context.Database.ExecuteSqlInterpolatedAsync($@"UPDATE warnings
|
||||
SET Forgiven = 1,
|
||||
ForgivenBy = 'Expiry'
|
||||
WHERE GuildId={guildId}
|
||||
AND Forgiven = 0
|
||||
AND DateAdded < datetime('now', {hours})");
|
||||
}
|
||||
else if (config.WarnExpireAction == WarnExpireAction.Delete)
|
||||
{
|
||||
await uow._context.Database.ExecuteSqlInterpolatedAsync($@"DELETE FROM warnings
|
||||
WHERE GuildId={guildId}
|
||||
AND DateAdded < datetime('now', {hours})");
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public Task<int> GetWarnExpire(ulong guildId)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var config = uow.GuildConfigs.ForId(guildId);
|
||||
return Task.FromResult(config.WarnExpireHours / 24);
|
||||
}
|
||||
|
||||
public async Task WarnExpireAsync(ulong guildId, int days, bool delete)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GuildConfigs.ForId(guildId, inc => inc);
|
||||
|
||||
config.WarnExpireHours = days * 24;
|
||||
config.WarnExpireAction = delete ? WarnExpireAction.Delete : WarnExpireAction.Clear;
|
||||
await uow.SaveChangesAsync();
|
||||
|
||||
// no need to check for warn expires
|
||||
if (config.WarnExpireHours == 0)
|
||||
return;
|
||||
}
|
||||
|
||||
await CheckWarnExpiresAsync(guildId);
|
||||
}
|
||||
|
||||
public IGrouping<ulong, Warning>[] WarnlogAll(ulong gid)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
return uow.Warnings.GetForGuild(gid).GroupBy(x => x.UserId).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public Warning[] UserWarnings(ulong gid, ulong userId)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
return uow.Warnings.ForId(gid, userId);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> WarnClearAsync(ulong guildId, ulong userId, int index, string moderator)
|
||||
{
|
||||
bool toReturn = true;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
await uow.Warnings.ForgiveAll(guildId, userId, moderator);
|
||||
}
|
||||
else
|
||||
{
|
||||
toReturn = uow.Warnings.Forgive(guildId, userId, moderator, index - 1);
|
||||
}
|
||||
uow.SaveChanges();
|
||||
}
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
public bool WarnPunish(ulong guildId, int number, PunishmentAction punish, StoopidTime time, IRole role = null)
|
||||
{
|
||||
// these 3 don't make sense with time
|
||||
if ((punish == PunishmentAction.Softban || punish == PunishmentAction.Kick || punish == PunishmentAction.RemoveRoles) && time != null)
|
||||
return false;
|
||||
if (number <= 0 || (time != null && time.Time > TimeSpan.FromDays(49)))
|
||||
return false;
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var ps = uow.GuildConfigs.ForId(guildId, set => set.Include(x => x.WarnPunishments)).WarnPunishments;
|
||||
var toDelete = ps.Where(x => x.Count == number);
|
||||
|
||||
uow._context.RemoveRange(toDelete);
|
||||
|
||||
ps.Add(new WarningPunishment()
|
||||
{
|
||||
Count = number,
|
||||
Punishment = punish,
|
||||
Time = (int?)(time?.Time.TotalMinutes) ?? 0,
|
||||
RoleId = punish == PunishmentAction.AddRole ? role.Id : default(ulong?),
|
||||
});
|
||||
uow.SaveChanges();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool WarnPunishRemove(ulong guildId, int number)
|
||||
{
|
||||
if (number <= 0)
|
||||
return false;
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var ps = uow.GuildConfigs.ForId(guildId, set => set.Include(x => x.WarnPunishments)).WarnPunishments;
|
||||
var p = ps.FirstOrDefault(x => x.Count == number);
|
||||
|
||||
if (p != null)
|
||||
{
|
||||
uow._context.Remove(p);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public WarningPunishment[] WarnPunishList(ulong guildId)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
return uow.GuildConfigs.ForId(guildId, gc => gc.Include(x => x.WarnPunishments))
|
||||
.WarnPunishments
|
||||
.OrderBy(x => x.Count)
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public (IEnumerable<(string Original, ulong? Id, string Reason)> Bans, int Missing) MassKill(SocketGuild guild, string people)
|
||||
{
|
||||
var gusers = guild.Users;
|
||||
//get user objects and reasons
|
||||
var bans = people.Split("\n")
|
||||
.Select(x =>
|
||||
{
|
||||
var split = x.Trim().Split(" ");
|
||||
|
||||
var reason = string.Join(" ", split.Skip(1));
|
||||
|
||||
if (ulong.TryParse(split[0], out var id))
|
||||
return (Original: split[0], Id: id, Reason: reason);
|
||||
|
||||
return (Original: split[0],
|
||||
Id: gusers
|
||||
.FirstOrDefault(u => u.ToString().ToLowerInvariant() == x)
|
||||
?.Id,
|
||||
Reason: reason);
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
//if user is null, means that person couldn't be found
|
||||
var missing = bans
|
||||
.Count(x => !x.Id.HasValue);
|
||||
|
||||
//get only data for found users
|
||||
var found = bans
|
||||
.Where(x => x.Id.HasValue)
|
||||
.Select(x => x.Id.Value)
|
||||
.ToList();
|
||||
|
||||
_blacklistService.BlacklistUsers(found);
|
||||
|
||||
return (bans, missing);
|
||||
}
|
||||
|
||||
public string GetBanTemplate(ulong guildId)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var template = uow._context.BanTemplates
|
||||
.AsQueryable()
|
||||
.FirstOrDefault(x => x.GuildId == guildId);
|
||||
return template?.Text;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetBanTemplate(ulong guildId, string text)
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var template = uow._context.BanTemplates
|
||||
.AsQueryable()
|
||||
.FirstOrDefault(x => x.GuildId == guildId);
|
||||
|
||||
if (text == null)
|
||||
{
|
||||
if (template is null)
|
||||
return;
|
||||
|
||||
uow._context.Remove(template);
|
||||
}
|
||||
else if (template == null)
|
||||
{
|
||||
uow._context.BanTemplates.Add(new BanTemplate()
|
||||
{
|
||||
GuildId = guildId,
|
||||
Text = text,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
template.Text = text;
|
||||
}
|
||||
|
||||
uow.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
public CREmbed GetBanUserDmEmbed(ICommandContext context, IGuildUser target, string defaultMessage,
|
||||
string banReason, TimeSpan? duration)
|
||||
{
|
||||
return GetBanUserDmEmbed(
|
||||
(DiscordSocketClient) context.Client,
|
||||
(SocketGuild) context.Guild,
|
||||
(IGuildUser) context.User,
|
||||
target,
|
||||
defaultMessage,
|
||||
banReason,
|
||||
duration);
|
||||
}
|
||||
|
||||
public CREmbed GetBanUserDmEmbed(DiscordSocketClient client, SocketGuild guild,
|
||||
IGuildUser moderator, IGuildUser target, string defaultMessage, string banReason, TimeSpan? duration)
|
||||
{
|
||||
var template = GetBanTemplate(guild.Id);
|
||||
|
||||
banReason = string.IsNullOrWhiteSpace(banReason)
|
||||
? "-"
|
||||
: banReason;
|
||||
|
||||
var replacer = new ReplacementBuilder()
|
||||
.WithServer(client, guild)
|
||||
.WithOverride("%ban.mod%", () => moderator.ToString())
|
||||
.WithOverride("%ban.mod.fullname%", () => moderator.ToString())
|
||||
.WithOverride("%ban.mod.name%", () => moderator.Username)
|
||||
.WithOverride("%ban.mod.discrim%", () => moderator.Discriminator)
|
||||
.WithOverride("%ban.user%", () => target.ToString())
|
||||
.WithOverride("%ban.user.fullname%", () => target.ToString())
|
||||
.WithOverride("%ban.user.name%", () => target.Username)
|
||||
.WithOverride("%ban.user.discrim%", () => target.Discriminator)
|
||||
.WithOverride("%reason%", () => banReason)
|
||||
.WithOverride("%ban.reason%", () => banReason)
|
||||
.WithOverride("%ban.duration%", () => duration?.ToString(@"d\.hh\:mm")?? "perma")
|
||||
.Build();
|
||||
|
||||
CREmbed crEmbed = null;
|
||||
// if template isn't set, use the old message style
|
||||
if (string.IsNullOrWhiteSpace(template))
|
||||
{
|
||||
template = JsonConvert.SerializeObject(new
|
||||
{
|
||||
color = NadekoBot.ErrorColor.RawValue,
|
||||
description = defaultMessage
|
||||
});
|
||||
|
||||
CREmbed.TryParse(template, out crEmbed);
|
||||
}
|
||||
// if template is set to "-" do not dm the user
|
||||
else if (template == "-")
|
||||
{
|
||||
return default;
|
||||
}
|
||||
// if template is an embed, send that embed with replacements
|
||||
else if (CREmbed.TryParse(template, out crEmbed))
|
||||
{
|
||||
replacer.Replace(crEmbed);
|
||||
}
|
||||
// otherwise, treat template as a regular string with replacements
|
||||
else
|
||||
{
|
||||
template = JsonConvert.SerializeObject(new
|
||||
{
|
||||
color = NadekoBot.ErrorColor.RawValue,
|
||||
description = replacer.Replace(template)
|
||||
});
|
||||
|
||||
CREmbed.TryParse(template, out crEmbed);
|
||||
}
|
||||
|
||||
return crEmbed;
|
||||
}
|
||||
}
|
||||
}
|
230
src/NadekoBot/Modules/Administration/Services/VcRoleService.cs
Normal file
230
src/NadekoBot/Modules/Administration/Services/VcRoleService.cs
Normal file
@@ -0,0 +1,230 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using Serilog;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services
|
||||
{
|
||||
public class VcRoleService : INService
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, IRole>> VcRoles { get; }
|
||||
public ConcurrentDictionary<ulong, ConcurrentQueue<(bool, IGuildUser, IRole)>> ToAssign { get; }
|
||||
|
||||
public VcRoleService(DiscordSocketClient client, NadekoBot bot, DbService db)
|
||||
{
|
||||
_db = db;
|
||||
_client = client;
|
||||
|
||||
_client.UserVoiceStateUpdated += ClientOnUserVoiceStateUpdated;
|
||||
VcRoles = new ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, IRole>>();
|
||||
ToAssign = new ConcurrentDictionary<ulong, ConcurrentQueue<(bool, IGuildUser, IRole)>>();
|
||||
var missingRoles = new ConcurrentBag<VcRoleInfo>();
|
||||
|
||||
using (var uow = db.GetDbContext())
|
||||
{
|
||||
var guildIds = client.Guilds.Select(x => x.Id).ToList();
|
||||
var configs = uow._context.Set<GuildConfig>()
|
||||
.AsQueryable()
|
||||
.Include(x => x.VcRoleInfos)
|
||||
.Where(x => guildIds.Contains(x.GuildId))
|
||||
.ToList();
|
||||
|
||||
Task.WhenAll(configs.Select(InitializeVcRole));
|
||||
}
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var tasks = ToAssign.Values.Select(queue => Task.Run(async () =>
|
||||
{
|
||||
while (queue.TryDequeue(out var item))
|
||||
{
|
||||
var (add, user, role) = item;
|
||||
if (add)
|
||||
{
|
||||
if (!user.RoleIds.Contains(role.Id))
|
||||
{
|
||||
try { await user.AddRoleAsync(role).ConfigureAwait(false); } catch { }
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (user.RoleIds.Contains(role.Id))
|
||||
{
|
||||
try { await user.RemoveRoleAsync(role).ConfigureAwait(false); } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(250).ConfigureAwait(false);
|
||||
}
|
||||
}));
|
||||
|
||||
await Task.WhenAll(tasks.Append(Task.Delay(1000))).ConfigureAwait(false);
|
||||
}
|
||||
});
|
||||
|
||||
_client.LeftGuild += _client_LeftGuild;
|
||||
bot.JoinedGuild += Bot_JoinedGuild;
|
||||
}
|
||||
|
||||
private Task Bot_JoinedGuild(GuildConfig arg)
|
||||
{
|
||||
// includeall no longer loads vcrole
|
||||
// need to load new guildconfig with vc role included
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var configWithVcRole = uow.GuildConfigs.ForId(
|
||||
arg.GuildId,
|
||||
set => set.Include(x => x.VcRoleInfos)
|
||||
);
|
||||
var _ = InitializeVcRole(configWithVcRole);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task _client_LeftGuild(SocketGuild arg)
|
||||
{
|
||||
VcRoles.TryRemove(arg.Id, out _);
|
||||
ToAssign.TryRemove(arg.Id, out _);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task InitializeVcRole(GuildConfig gconf)
|
||||
{
|
||||
await Task.Yield();
|
||||
var g = _client.GetGuild(gconf.GuildId);
|
||||
if (g == null)
|
||||
return;
|
||||
|
||||
var infos = new ConcurrentDictionary<ulong, IRole>();
|
||||
var missingRoles = new List<VcRoleInfo>();
|
||||
VcRoles.AddOrUpdate(gconf.GuildId, infos, delegate { return 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.GetDbContext())
|
||||
{
|
||||
Log.Warning($"Removing {missingRoles.Count} missing roles from {nameof(VcRoleService)}");
|
||||
uow._context.RemoveRange(missingRoles);
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddVcRole(ulong guildId, IRole role, ulong vcId)
|
||||
{
|
||||
if (role == null)
|
||||
throw new ArgumentNullException(nameof(role));
|
||||
|
||||
var guildVcRoles = VcRoles.GetOrAdd(guildId, new ConcurrentDictionary<ulong, IRole>());
|
||||
|
||||
guildVcRoles.AddOrUpdate(vcId, role, (key, old) => role);
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var conf = uow.GuildConfigs.ForId(guildId, set => set.Include(x => x.VcRoleInfos));
|
||||
var toDelete = conf.VcRoleInfos.FirstOrDefault(x => x.VoiceChannelId == vcId); // remove old one
|
||||
if(toDelete != null)
|
||||
{
|
||||
uow._context.Remove(toDelete);
|
||||
}
|
||||
conf.VcRoleInfos.Add(new VcRoleInfo()
|
||||
{
|
||||
VoiceChannelId = vcId,
|
||||
RoleId = role.Id,
|
||||
}); // add new one
|
||||
uow.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
public bool RemoveVcRole(ulong guildId, ulong vcId)
|
||||
{
|
||||
if (!VcRoles.TryGetValue(guildId, out var guildVcRoles))
|
||||
return false;
|
||||
|
||||
if (!guildVcRoles.TryRemove(vcId, out _))
|
||||
return false;
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var conf = uow.GuildConfigs.ForId(guildId, set => set.Include(x => x.VcRoleInfos));
|
||||
var toRemove = conf.VcRoleInfos.Where(x => x.VoiceChannelId == vcId).ToList();
|
||||
uow._context.RemoveRange(toRemove);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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(() =>
|
||||
{
|
||||
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))
|
||||
{
|
||||
Assign(false, gusr, role);
|
||||
}
|
||||
//add new
|
||||
if (newVc != null && guildVcRoles.TryGetValue(newVc.Id, out role))
|
||||
{
|
||||
Assign(true, gusr, role);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error in VcRoleService VoiceStateUpdate");
|
||||
}
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void Assign(bool v, SocketGuildUser gusr, IRole role)
|
||||
{
|
||||
var queue = ToAssign.GetOrAdd(gusr.Guild.Id, new ConcurrentQueue<(bool, IGuildUser, IRole)>());
|
||||
queue.Enqueue((v, gusr, role));
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user