Added Ellie.Bot.Modules.Permissions
This commit is contained in:
parent
e16e4b82a3
commit
abd3be86b4
16 changed files with 2136 additions and 0 deletions
143
src/Ellie.Bot.Modules.Permissions/Blacklist/BlacklistCommands.cs
Normal file
143
src/Ellie.Bot.Modules.Permissions/Blacklist/BlacklistCommands.cs
Normal file
|
@ -0,0 +1,143 @@
|
|||
#nullable disable
|
||||
using Ellie.Modules.Permissions.Services;
|
||||
using Ellie.Services.Database.Models;
|
||||
|
||||
namespace Ellie.Modules.Permissions;
|
||||
|
||||
public partial class Permissions
|
||||
{
|
||||
[Group]
|
||||
public partial class BlacklistCommands : EllieModule<BlacklistService>
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public BlacklistCommands(DiscordSocketClient client)
|
||||
=> _client = client;
|
||||
|
||||
private async Task ListBlacklistInternal(string title, BlacklistType type, int page = 0)
|
||||
{
|
||||
if (page < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(page));
|
||||
|
||||
var list = _service.GetBlacklist();
|
||||
var items = await list.Where(x => x.Type == type)
|
||||
.Select(async i =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return i.Type switch
|
||||
{
|
||||
BlacklistType.Channel => Format.Code(i.ItemId.ToString())
|
||||
+ " "
|
||||
+ (_client.GetChannel(i.ItemId)?.ToString()
|
||||
?? ""),
|
||||
BlacklistType.User => Format.Code(i.ItemId.ToString())
|
||||
+ " "
|
||||
+ ((await _client.Rest.GetUserAsync(i.ItemId))
|
||||
?.ToString()
|
||||
?? ""),
|
||||
BlacklistType.Server => Format.Code(i.ItemId.ToString())
|
||||
+ " "
|
||||
+ (_client.GetGuild(i.ItemId)?.ToString() ?? ""),
|
||||
_ => Format.Code(i.ItemId.ToString())
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
Log.Warning("Can't get {BlacklistType} [{BlacklistItemId}]",
|
||||
i.Type,
|
||||
i.ItemId);
|
||||
return Format.Code(i.ItemId.ToString());
|
||||
}
|
||||
})
|
||||
.WhenAll();
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(page,
|
||||
curPage =>
|
||||
{
|
||||
var pageItems = items.Skip(10 * curPage).Take(10).ToList();
|
||||
|
||||
if (pageItems.Count == 0)
|
||||
return _eb.Create().WithOkColor().WithTitle(title).WithDescription(GetText(strs.empty_page));
|
||||
|
||||
return _eb.Create().WithTitle(title).WithDescription(pageItems.Join('\n')).WithOkColor();
|
||||
},
|
||||
items.Length,
|
||||
10);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public Task UserBlacklist(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return Task.CompletedTask;
|
||||
|
||||
return ListBlacklistInternal(GetText(strs.blacklisted_users), BlacklistType.User, page);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public Task ChannelBlacklist(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return Task.CompletedTask;
|
||||
|
||||
return ListBlacklistInternal(GetText(strs.blacklisted_channels), BlacklistType.Channel, page);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public Task ServerBlacklist(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return Task.CompletedTask;
|
||||
|
||||
return ListBlacklistInternal(GetText(strs.blacklisted_servers), BlacklistType.Server, page);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public Task UserBlacklist(AddRemove action, ulong id)
|
||||
=> Blacklist(action, id, BlacklistType.User);
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public Task UserBlacklist(AddRemove action, IUser usr)
|
||||
=> Blacklist(action, usr.Id, BlacklistType.User);
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public Task ChannelBlacklist(AddRemove action, ulong id)
|
||||
=> Blacklist(action, id, BlacklistType.Channel);
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public Task ServerBlacklist(AddRemove action, ulong id)
|
||||
=> Blacklist(action, id, BlacklistType.Server);
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public Task ServerBlacklist(AddRemove action, IGuild guild)
|
||||
=> Blacklist(action, guild.Id, BlacklistType.Server);
|
||||
|
||||
private async Task Blacklist(AddRemove action, ulong id, BlacklistType type)
|
||||
{
|
||||
if (action == AddRemove.Add)
|
||||
await _service.Blacklist(type, id);
|
||||
else
|
||||
await _service.UnBlacklist(type, id);
|
||||
|
||||
if (action == AddRemove.Add)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.blacklisted(Format.Code(type.ToString()),
|
||||
Format.Code(id.ToString())));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.unblacklisted(Format.Code(type.ToString()),
|
||||
Format.Code(id.ToString())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
#nullable disable
|
||||
using Ellie.Common.TypeReaders;
|
||||
using static Ellie.Common.TypeReaders.TypeReaderResult;
|
||||
|
||||
namespace Ellie.Modules.Permissions;
|
||||
|
||||
public class CleverbotResponseCmdCdTypeReader : EllieTypeReader<CleverBotResponseStr>
|
||||
{
|
||||
public override ValueTask<TypeReaderResult<CleverBotResponseStr>> ReadAsync(
|
||||
ICommandContext ctx,
|
||||
string input)
|
||||
=> input.ToLowerInvariant() == CleverBotResponseStr.CLEVERBOT_RESPONSE
|
||||
? new(FromSuccess(new CleverBotResponseStr()))
|
||||
: new(FromError<CleverBotResponseStr>(CommandError.ParseFailed, "Not a valid cleverbot"));
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Ellie.Common.ModuleBehaviors;
|
||||
using Ellie.Db;
|
||||
|
||||
namespace Ellie.Modules.Permissions.Services;
|
||||
|
||||
public sealed class CmdCdService : IExecPreCommand, IReadyExecutor, IEService
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly ConcurrentDictionary<ulong, ConcurrentDictionary<string, int>> _settings = new();
|
||||
|
||||
private readonly ConcurrentDictionary<(ulong, string), ConcurrentDictionary<ulong, DateTime>> _activeCooldowns =
|
||||
new();
|
||||
|
||||
public int Priority => 0;
|
||||
|
||||
public CmdCdService(IBot bot, DbService db)
|
||||
{
|
||||
_db = db;
|
||||
_settings = bot
|
||||
.AllGuildConfigs
|
||||
.ToDictionary(x => x.GuildId, x => x.CommandCooldowns
|
||||
.DistinctBy(x => x.CommandName.ToLowerInvariant())
|
||||
.ToDictionary(c => c.CommandName, c => c.Seconds)
|
||||
.ToConcurrent())
|
||||
.ToConcurrent();
|
||||
}
|
||||
|
||||
public Task<bool> ExecPreCommandAsync(ICommandContext context, string moduleName, CommandInfo command)
|
||||
=> TryBlock(context.Guild, context.User, command.Name.ToLowerInvariant());
|
||||
|
||||
public Task<bool> TryBlock(IGuild? guild, IUser user, string commandName)
|
||||
{
|
||||
if (guild is null)
|
||||
return Task.FromResult(false);
|
||||
|
||||
if (!_settings.TryGetValue(guild.Id, out var cooldownSettings))
|
||||
return Task.FromResult(false);
|
||||
|
||||
if (!cooldownSettings.TryGetValue(commandName, out var cdSeconds))
|
||||
return Task.FromResult(false);
|
||||
|
||||
var cooldowns = _activeCooldowns.GetOrAdd(
|
||||
(guild.Id, commandName),
|
||||
static _ => new());
|
||||
|
||||
// if user is not already on cooldown, add
|
||||
if (cooldowns.TryAdd(user.Id, DateTime.UtcNow))
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
// if there is an entry, maybe it expired. Try to check if it expired and don't fail if it did
|
||||
// - just update
|
||||
if (cooldowns.TryGetValue(user.Id, out var oldValue))
|
||||
{
|
||||
var diff = DateTime.UtcNow - oldValue;
|
||||
if (diff.TotalSeconds > cdSeconds)
|
||||
{
|
||||
if (cooldowns.TryUpdate(user.Id, DateTime.UtcNow, oldValue))
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
using var timer = new PeriodicTimer(TimeSpan.FromHours(1));
|
||||
|
||||
while (await timer.WaitForNextTickAsync())
|
||||
{
|
||||
// once per hour delete expired entries
|
||||
foreach (var ((guildId, commandName), dict) in _activeCooldowns)
|
||||
{
|
||||
// if this pair no longer has associated config, that means it has been removed.
|
||||
// remove all cooldowns
|
||||
if (!_settings.TryGetValue(guildId, out var inner)
|
||||
|| !inner.TryGetValue(commandName, out var cdSeconds))
|
||||
{
|
||||
_activeCooldowns.Remove((guildId, commandName), out _);
|
||||
continue;
|
||||
}
|
||||
|
||||
Cleanup(dict, cdSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Cleanup(ConcurrentDictionary<ulong, DateTime> dict, int cdSeconds)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
foreach (var (key, _) in dict.Where(x => (now - x.Value).TotalSeconds > cdSeconds).ToArray())
|
||||
{
|
||||
dict.TryRemove(key, out _);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearCooldowns(ulong guildId, string cmdName)
|
||||
{
|
||||
if (_settings.TryGetValue(guildId, out var dict))
|
||||
dict.TryRemove(cmdName, out _);
|
||||
|
||||
_activeCooldowns.TryRemove((guildId, cmdName), out _);
|
||||
|
||||
using var ctx = _db.GetDbContext();
|
||||
var gc = ctx.GuildConfigsForId(guildId, x => x.Include(x => x.CommandCooldowns));
|
||||
gc.CommandCooldowns.RemoveWhere(x => x.CommandName == cmdName);
|
||||
ctx.SaveChanges();
|
||||
}
|
||||
|
||||
public void AddCooldown(ulong guildId, string name, int secs)
|
||||
{
|
||||
if (secs <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(secs));
|
||||
|
||||
var sett = _settings.GetOrAdd(guildId, static _ => new());
|
||||
sett[name] = secs;
|
||||
|
||||
// force cleanup
|
||||
if (_activeCooldowns.TryGetValue((guildId, name), out var dict))
|
||||
Cleanup(dict, secs);
|
||||
|
||||
using var ctx = _db.GetDbContext();
|
||||
var gc = ctx.GuildConfigsForId(guildId, x => x.Include(x => x.CommandCooldowns));
|
||||
gc.CommandCooldowns.RemoveWhere(x => x.CommandName == name);
|
||||
gc.CommandCooldowns.Add(new()
|
||||
{
|
||||
Seconds = secs,
|
||||
CommandName = name
|
||||
});
|
||||
ctx.SaveChanges();
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<(string CommandName, int Seconds)> GetCommandCooldowns(ulong guildId)
|
||||
{
|
||||
if (!_settings.TryGetValue(guildId, out var dict))
|
||||
return Array.Empty<(string, int)>();
|
||||
|
||||
return dict.Select(x => (x.Key, x.Value)).ToArray();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
#nullable disable
|
||||
using Humanizer.Localisation;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ellie.Common.TypeReaders;
|
||||
using Ellie.Db;
|
||||
using Ellie.Modules.Permissions.Services;
|
||||
using Ellie.Services.Database.Models;
|
||||
|
||||
namespace Ellie.Modules.Permissions;
|
||||
|
||||
public partial class Permissions
|
||||
{
|
||||
[Group]
|
||||
public partial class CmdCdsCommands : EllieModule
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly CmdCdService _service;
|
||||
|
||||
public CmdCdsCommands(CmdCdService service, DbService db)
|
||||
{
|
||||
_service = service;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
private async Task CmdCooldownInternal(string cmdName, int secs)
|
||||
{
|
||||
var channel = (ITextChannel)ctx.Channel;
|
||||
if (secs is < 0 or > 3600)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.invalid_second_param_between(0, 3600));
|
||||
return;
|
||||
}
|
||||
|
||||
var name = cmdName.ToLowerInvariant();
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GuildConfigsForId(channel.Guild.Id, set => set.Include(gc => gc.CommandCooldowns));
|
||||
|
||||
var toDelete = config.CommandCooldowns.FirstOrDefault(cc => cc.CommandName == name);
|
||||
if (toDelete is not null)
|
||||
uow.Set<CommandCooldown>().Remove(toDelete);
|
||||
if (secs != 0)
|
||||
{
|
||||
var cc = new CommandCooldown
|
||||
{
|
||||
CommandName = name,
|
||||
Seconds = secs
|
||||
};
|
||||
config.CommandCooldowns.Add(cc);
|
||||
_service.AddCooldown(channel.Guild.Id, name, secs);
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
if (secs == 0)
|
||||
{
|
||||
_service.ClearCooldowns(ctx.Guild.Id, cmdName);
|
||||
await ReplyConfirmLocalizedAsync(strs.cmdcd_cleared(Format.Bold(name)));
|
||||
}
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.cmdcd_add(Format.Bold(name), Format.Bold(secs.ToString())));
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(0)]
|
||||
public Task CmdCooldown(CleverBotResponseStr command, int secs)
|
||||
=> CmdCooldownInternal(CleverBotResponseStr.CLEVERBOT_RESPONSE, secs);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public Task CmdCooldown(CommandOrExprInfo command, int secs)
|
||||
=> CmdCooldownInternal(command.Name, secs);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task AllCmdCooldowns(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return;
|
||||
|
||||
var channel = (ITextChannel)ctx.Channel;
|
||||
var localSet = _service.GetCommandCooldowns(ctx.Guild.Id);
|
||||
|
||||
if (!localSet.Any())
|
||||
await ReplyConfirmLocalizedAsync(strs.cmdcd_none);
|
||||
else
|
||||
{
|
||||
await ctx.SendPaginatedConfirmAsync(page, curPage =>
|
||||
{
|
||||
var items = localSet.Skip(curPage * 15)
|
||||
.Take(15)
|
||||
.Select(x => $"{Format.Code(x.CommandName)}: {x.Seconds.Seconds().Humanize(maxUnit: TimeUnit.Second, culture: Culture)}");
|
||||
|
||||
return _eb.Create(ctx)
|
||||
.WithOkColor()
|
||||
.WithDescription(items.Join("\n"));
|
||||
|
||||
}, localSet.Count, 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
324
src/Ellie.Bot.Modules.Permissions/Filter/FilterCommands.cs
Normal file
324
src/Ellie.Bot.Modules.Permissions/Filter/FilterCommands.cs
Normal file
|
@ -0,0 +1,324 @@
|
|||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ellie.Db;
|
||||
using Ellie.Modules.Permissions.Services;
|
||||
using Ellie.Services.Database.Models;
|
||||
|
||||
namespace Ellie.Modules.Permissions;
|
||||
|
||||
public partial class Permissions
|
||||
{
|
||||
[Group]
|
||||
public partial class FilterCommands : EllieModule<FilterService>
|
||||
{
|
||||
private readonly DbService _db;
|
||||
|
||||
public FilterCommands(DbService db)
|
||||
=> _db = db;
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task FwClear()
|
||||
{
|
||||
_service.ClearFilteredWords(ctx.Guild.Id);
|
||||
await ReplyConfirmLocalizedAsync(strs.fw_cleared);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task FilterList()
|
||||
{
|
||||
var embed = _eb.Create(ctx)
|
||||
.WithOkColor()
|
||||
.WithTitle("Server filter settings");
|
||||
|
||||
var config = await _service.GetFilterSettings(ctx.Guild.Id);
|
||||
|
||||
string GetEnabledEmoji(bool value)
|
||||
=> value ? "\\🟢" : "\\🔴";
|
||||
|
||||
async Task<string> GetChannelListAsync(IReadOnlyCollection<ulong> channels)
|
||||
{
|
||||
var toReturn = (await channels
|
||||
.Select(async cid =>
|
||||
{
|
||||
var ch = await ctx.Guild.GetChannelAsync(cid);
|
||||
return ch is null
|
||||
? $"{cid} *missing*"
|
||||
: $"<#{cid}>";
|
||||
})
|
||||
.WhenAll())
|
||||
.Join('\n');
|
||||
|
||||
if (string.IsNullOrWhiteSpace(toReturn))
|
||||
return GetText(strs.no_channel_found);
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
embed.AddField($"{GetEnabledEmoji(config.FilterLinksEnabled)} Filter Links",
|
||||
await GetChannelListAsync(config.FilterLinksChannels));
|
||||
|
||||
embed.AddField($"{GetEnabledEmoji(config.FilterInvitesEnabled)} Filter Invites",
|
||||
await GetChannelListAsync(config.FilterInvitesChannels));
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task SrvrFilterInv()
|
||||
{
|
||||
var channel = (ITextChannel)ctx.Channel;
|
||||
|
||||
bool enabled;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GuildConfigsForId(channel.Guild.Id, set => set);
|
||||
enabled = config.FilterInvites = !config.FilterInvites;
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
_service.InviteFilteringServers.Add(channel.Guild.Id);
|
||||
await ReplyConfirmLocalizedAsync(strs.invite_filter_server_on);
|
||||
}
|
||||
else
|
||||
{
|
||||
_service.InviteFilteringServers.TryRemove(channel.Guild.Id);
|
||||
await ReplyConfirmLocalizedAsync(strs.invite_filter_server_off);
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ChnlFilterInv()
|
||||
{
|
||||
var channel = (ITextChannel)ctx.Channel;
|
||||
|
||||
FilterChannelId removed;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GuildConfigsForId(channel.Guild.Id,
|
||||
set => set.Include(gc => gc.FilterInvitesChannelIds));
|
||||
var match = new FilterChannelId
|
||||
{
|
||||
ChannelId = channel.Id
|
||||
};
|
||||
removed = config.FilterInvitesChannelIds.FirstOrDefault(fc => fc.Equals(match));
|
||||
|
||||
if (removed is null)
|
||||
config.FilterInvitesChannelIds.Add(match);
|
||||
else
|
||||
uow.Remove(removed);
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
if (removed is null)
|
||||
{
|
||||
_service.InviteFilteringChannels.Add(channel.Id);
|
||||
await ReplyConfirmLocalizedAsync(strs.invite_filter_channel_on);
|
||||
}
|
||||
else
|
||||
{
|
||||
_service.InviteFilteringChannels.TryRemove(channel.Id);
|
||||
await ReplyConfirmLocalizedAsync(strs.invite_filter_channel_off);
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task SrvrFilterLin()
|
||||
{
|
||||
var channel = (ITextChannel)ctx.Channel;
|
||||
|
||||
bool enabled;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GuildConfigsForId(channel.Guild.Id, set => set);
|
||||
enabled = config.FilterLinks = !config.FilterLinks;
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
_service.LinkFilteringServers.Add(channel.Guild.Id);
|
||||
await ReplyConfirmLocalizedAsync(strs.link_filter_server_on);
|
||||
}
|
||||
else
|
||||
{
|
||||
_service.LinkFilteringServers.TryRemove(channel.Guild.Id);
|
||||
await ReplyConfirmLocalizedAsync(strs.link_filter_server_off);
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ChnlFilterLin()
|
||||
{
|
||||
var channel = (ITextChannel)ctx.Channel;
|
||||
|
||||
FilterLinksChannelId removed;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config =
|
||||
uow.GuildConfigsForId(channel.Guild.Id, set => set.Include(gc => gc.FilterLinksChannelIds));
|
||||
var match = new FilterLinksChannelId
|
||||
{
|
||||
ChannelId = channel.Id
|
||||
};
|
||||
removed = config.FilterLinksChannelIds.FirstOrDefault(fc => fc.Equals(match));
|
||||
|
||||
if (removed is null)
|
||||
config.FilterLinksChannelIds.Add(match);
|
||||
else
|
||||
uow.Remove(removed);
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
if (removed is null)
|
||||
{
|
||||
_service.LinkFilteringChannels.Add(channel.Id);
|
||||
await ReplyConfirmLocalizedAsync(strs.link_filter_channel_on);
|
||||
}
|
||||
else
|
||||
{
|
||||
_service.LinkFilteringChannels.TryRemove(channel.Id);
|
||||
await ReplyConfirmLocalizedAsync(strs.link_filter_channel_off);
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task SrvrFilterWords()
|
||||
{
|
||||
var channel = (ITextChannel)ctx.Channel;
|
||||
|
||||
bool enabled;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GuildConfigsForId(channel.Guild.Id, set => set);
|
||||
enabled = config.FilterWords = !config.FilterWords;
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
_service.WordFilteringServers.Add(channel.Guild.Id);
|
||||
await ReplyConfirmLocalizedAsync(strs.word_filter_server_on);
|
||||
}
|
||||
else
|
||||
{
|
||||
_service.WordFilteringServers.TryRemove(channel.Guild.Id);
|
||||
await ReplyConfirmLocalizedAsync(strs.word_filter_server_off);
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ChnlFilterWords()
|
||||
{
|
||||
var channel = (ITextChannel)ctx.Channel;
|
||||
|
||||
FilterWordsChannelId removed;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config =
|
||||
uow.GuildConfigsForId(channel.Guild.Id, set => set.Include(gc => gc.FilterWordsChannelIds));
|
||||
|
||||
var match = new FilterWordsChannelId
|
||||
{
|
||||
ChannelId = channel.Id
|
||||
};
|
||||
removed = config.FilterWordsChannelIds.FirstOrDefault(fc => fc.Equals(match));
|
||||
if (removed is null)
|
||||
config.FilterWordsChannelIds.Add(match);
|
||||
else
|
||||
uow.Remove(removed);
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
if (removed is null)
|
||||
{
|
||||
_service.WordFilteringChannels.Add(channel.Id);
|
||||
await ReplyConfirmLocalizedAsync(strs.word_filter_channel_on);
|
||||
}
|
||||
else
|
||||
{
|
||||
_service.WordFilteringChannels.TryRemove(channel.Id);
|
||||
await ReplyConfirmLocalizedAsync(strs.word_filter_channel_off);
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task FilterWord([Leftover] string word)
|
||||
{
|
||||
var channel = (ITextChannel)ctx.Channel;
|
||||
|
||||
word = word?.Trim().ToLowerInvariant();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(word))
|
||||
return;
|
||||
|
||||
FilteredWord removed;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GuildConfigsForId(channel.Guild.Id, set => set.Include(gc => gc.FilteredWords));
|
||||
|
||||
removed = config.FilteredWords.FirstOrDefault(fw => fw.Word.Trim().ToLowerInvariant() == word);
|
||||
|
||||
if (removed is null)
|
||||
{
|
||||
config.FilteredWords.Add(new()
|
||||
{
|
||||
Word = word
|
||||
});
|
||||
}
|
||||
else
|
||||
uow.Remove(removed);
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
var filteredWords =
|
||||
_service.ServerFilteredWords.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<string>());
|
||||
|
||||
if (removed is null)
|
||||
{
|
||||
filteredWords.Add(word);
|
||||
await ReplyConfirmLocalizedAsync(strs.filter_word_add(Format.Code(word)));
|
||||
}
|
||||
else
|
||||
{
|
||||
filteredWords.TryRemove(word);
|
||||
await ReplyConfirmLocalizedAsync(strs.filter_word_remove(Format.Code(word)));
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task LstFilterWords(int page = 1)
|
||||
{
|
||||
page--;
|
||||
if (page < 0)
|
||||
return;
|
||||
|
||||
var channel = (ITextChannel)ctx.Channel;
|
||||
|
||||
_service.ServerFilteredWords.TryGetValue(channel.Guild.Id, out var fwHash);
|
||||
|
||||
var fws = fwHash.ToArray();
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(page,
|
||||
curPage => _eb.Create()
|
||||
.WithTitle(GetText(strs.filter_word_list))
|
||||
.WithDescription(string.Join("\n", fws.Skip(curPage * 10).Take(10)))
|
||||
.WithOkColor(),
|
||||
fws.Length,
|
||||
10);
|
||||
}
|
||||
}
|
||||
}
|
241
src/Ellie.Bot.Modules.Permissions/Filter/FilterService.cs
Normal file
241
src/Ellie.Bot.Modules.Permissions/Filter/FilterService.cs
Normal file
|
@ -0,0 +1,241 @@
|
|||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ellie.Common.ModuleBehaviors;
|
||||
using Ellie.Db;
|
||||
using Ellie.Services.Database.Models;
|
||||
|
||||
namespace Ellie.Modules.Permissions.Services;
|
||||
|
||||
public sealed class FilterService : IExecOnMessage
|
||||
{
|
||||
public ConcurrentHashSet<ulong> InviteFilteringChannels { get; }
|
||||
public ConcurrentHashSet<ulong> InviteFilteringServers { get; }
|
||||
|
||||
//serverid, filteredwords
|
||||
public ConcurrentDictionary<ulong, ConcurrentHashSet<string>> ServerFilteredWords { get; }
|
||||
|
||||
public ConcurrentHashSet<ulong> WordFilteringChannels { get; }
|
||||
public ConcurrentHashSet<ulong> WordFilteringServers { get; }
|
||||
|
||||
public ConcurrentHashSet<ulong> LinkFilteringChannels { get; }
|
||||
public ConcurrentHashSet<ulong> LinkFilteringServers { get; }
|
||||
|
||||
public int Priority
|
||||
=> int.MaxValue - 1;
|
||||
|
||||
private readonly DbService _db;
|
||||
|
||||
public FilterService(DiscordSocketClient client, DbService db)
|
||||
{
|
||||
_db = db;
|
||||
|
||||
using (var uow = db.GetDbContext())
|
||||
{
|
||||
var ids = client.GetGuildIds();
|
||||
var configs = uow.Set<GuildConfig>()
|
||||
.AsQueryable()
|
||||
.Include(x => x.FilteredWords)
|
||||
.Include(x => x.FilterLinksChannelIds)
|
||||
.Include(x => x.FilterWordsChannelIds)
|
||||
.Include(x => x.FilterInvitesChannelIds)
|
||||
.Where(gc => ids.Contains(gc.GuildId))
|
||||
.ToList();
|
||||
|
||||
InviteFilteringServers = new(configs.Where(gc => gc.FilterInvites).Select(gc => gc.GuildId));
|
||||
InviteFilteringChannels =
|
||||
new(configs.SelectMany(gc => gc.FilterInvitesChannelIds.Select(fci => fci.ChannelId)));
|
||||
|
||||
LinkFilteringServers = new(configs.Where(gc => gc.FilterLinks).Select(gc => gc.GuildId));
|
||||
LinkFilteringChannels =
|
||||
new(configs.SelectMany(gc => gc.FilterLinksChannelIds.Select(fci => fci.ChannelId)));
|
||||
|
||||
var dict = configs.ToDictionary(gc => gc.GuildId,
|
||||
gc => new ConcurrentHashSet<string>(gc.FilteredWords.Select(fw => fw.Word).Distinct()));
|
||||
|
||||
ServerFilteredWords = new(dict);
|
||||
|
||||
var serverFiltering = configs.Where(gc => gc.FilterWords);
|
||||
WordFilteringServers = new(serverFiltering.Select(gc => gc.GuildId));
|
||||
WordFilteringChannels =
|
||||
new(configs.SelectMany(gc => gc.FilterWordsChannelIds.Select(fwci => fwci.ChannelId)));
|
||||
}
|
||||
|
||||
client.MessageUpdated += (oldData, newMsg, channel) =>
|
||||
{
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
var guild = (channel as ITextChannel)?.Guild;
|
||||
|
||||
if (guild is null || newMsg is not IUserMessage usrMsg)
|
||||
return Task.CompletedTask;
|
||||
|
||||
return ExecOnMessageAsync(guild, usrMsg);
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
}
|
||||
|
||||
public ConcurrentHashSet<string> FilteredWordsForChannel(ulong channelId, ulong guildId)
|
||||
{
|
||||
var words = new ConcurrentHashSet<string>();
|
||||
if (WordFilteringChannels.Contains(channelId))
|
||||
ServerFilteredWords.TryGetValue(guildId, out words);
|
||||
return words;
|
||||
}
|
||||
|
||||
public void ClearFilteredWords(ulong guildId)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigsForId(guildId,
|
||||
set => set.Include(x => x.FilteredWords).Include(x => x.FilterWordsChannelIds));
|
||||
|
||||
WordFilteringServers.TryRemove(guildId);
|
||||
ServerFilteredWords.TryRemove(guildId, out _);
|
||||
|
||||
foreach (var c in gc.FilterWordsChannelIds)
|
||||
WordFilteringChannels.TryRemove(c.ChannelId);
|
||||
|
||||
gc.FilterWords = false;
|
||||
gc.FilteredWords.Clear();
|
||||
gc.FilterWordsChannelIds.Clear();
|
||||
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
public ConcurrentHashSet<string> FilteredWordsForServer(ulong guildId)
|
||||
{
|
||||
var words = new ConcurrentHashSet<string>();
|
||||
if (WordFilteringServers.Contains(guildId))
|
||||
ServerFilteredWords.TryGetValue(guildId, out words);
|
||||
return words;
|
||||
}
|
||||
|
||||
public async Task<bool> ExecOnMessageAsync(IGuild guild, IUserMessage msg)
|
||||
{
|
||||
if (msg.Author is not IGuildUser gu || gu.GuildPermissions.Administrator)
|
||||
return false;
|
||||
|
||||
var results = await Task.WhenAll(FilterInvites(guild, msg), FilterWords(guild, msg), FilterLinks(guild, msg));
|
||||
|
||||
return results.Any(x => x);
|
||||
}
|
||||
|
||||
private async Task<bool> FilterWords(IGuild guild, IUserMessage usrMsg)
|
||||
{
|
||||
if (guild is null)
|
||||
return false;
|
||||
if (usrMsg is null)
|
||||
return false;
|
||||
|
||||
var filteredChannelWords =
|
||||
FilteredWordsForChannel(usrMsg.Channel.Id, guild.Id) ?? new ConcurrentHashSet<string>();
|
||||
var filteredServerWords = FilteredWordsForServer(guild.Id) ?? new ConcurrentHashSet<string>();
|
||||
var wordsInMessage = usrMsg.Content.ToLowerInvariant().Split(' ');
|
||||
if (filteredChannelWords.Count != 0 || filteredServerWords.Count != 0)
|
||||
{
|
||||
foreach (var word in wordsInMessage)
|
||||
{
|
||||
if (filteredChannelWords.Contains(word) || filteredServerWords.Contains(word))
|
||||
{
|
||||
Log.Information("User {UserName} [{UserId}] used a filtered word in {ChannelId} channel",
|
||||
usrMsg.Author.ToString(),
|
||||
usrMsg.Author.Id,
|
||||
usrMsg.Channel.Id);
|
||||
|
||||
try
|
||||
{
|
||||
await usrMsg.DeleteAsync();
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
Log.Warning(ex,
|
||||
"I do not have permission to filter words in channel with id {Id}",
|
||||
usrMsg.Channel.Id);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<bool> FilterInvites(IGuild guild, IUserMessage usrMsg)
|
||||
{
|
||||
if (guild is null)
|
||||
return false;
|
||||
if (usrMsg is null)
|
||||
return false;
|
||||
|
||||
if ((InviteFilteringChannels.Contains(usrMsg.Channel.Id) || InviteFilteringServers.Contains(guild.Id))
|
||||
&& usrMsg.Content.IsDiscordInvite())
|
||||
{
|
||||
Log.Information("User {UserName} [{UserId}] sent a filtered invite to {ChannelId} channel",
|
||||
usrMsg.Author.ToString(),
|
||||
usrMsg.Author.Id,
|
||||
usrMsg.Channel.Id);
|
||||
|
||||
try
|
||||
{
|
||||
await usrMsg.DeleteAsync();
|
||||
return true;
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
Log.Warning(ex,
|
||||
"I do not have permission to filter invites in channel with id {Id}",
|
||||
usrMsg.Channel.Id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<bool> FilterLinks(IGuild guild, IUserMessage usrMsg)
|
||||
{
|
||||
if (guild is null)
|
||||
return false;
|
||||
if (usrMsg is null)
|
||||
return false;
|
||||
|
||||
if ((LinkFilteringChannels.Contains(usrMsg.Channel.Id) || LinkFilteringServers.Contains(guild.Id))
|
||||
&& usrMsg.Content.TryGetUrlPath(out _))
|
||||
{
|
||||
Log.Information("User {UserName} [{UserId}] sent a filtered link to {ChannelId} channel",
|
||||
usrMsg.Author.ToString(),
|
||||
usrMsg.Author.Id,
|
||||
usrMsg.Channel.Id);
|
||||
|
||||
try
|
||||
{
|
||||
await usrMsg.DeleteAsync();
|
||||
return true;
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
Log.Warning(ex, "I do not have permission to filter links in channel with id {Id}", usrMsg.Channel.Id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<ServerFilterSettings> GetFilterSettings(ulong guildId)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigsForId(guildId, set => set
|
||||
.Include(x => x.FilterInvitesChannelIds)
|
||||
.Include(x => x.FilterLinksChannelIds));
|
||||
|
||||
return new()
|
||||
{
|
||||
FilterInvitesChannels = gc.FilterInvitesChannelIds.Map(x => x.ChannelId),
|
||||
FilterLinksChannels = gc.FilterLinksChannelIds.Map(x => x.ChannelId),
|
||||
FilterInvitesEnabled = gc.FilterInvites,
|
||||
FilterLinksEnabled = gc.FilterLinks,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
#nullable disable
|
||||
namespace Ellie.Modules.Permissions.Services;
|
||||
|
||||
public readonly struct ServerFilterSettings
|
||||
{
|
||||
public bool FilterInvitesEnabled { get; init; }
|
||||
public bool FilterLinksEnabled { get; init; }
|
||||
public IReadOnlyCollection<ulong> FilterInvitesChannels { get; init; }
|
||||
public IReadOnlyCollection<ulong> FilterLinksChannels { get; init; }
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
#nullable disable
|
||||
using Ellie.Common.TypeReaders;
|
||||
using Ellie.Modules.Permissions.Services;
|
||||
|
||||
namespace Ellie.Modules.Permissions;
|
||||
|
||||
public partial class Permissions
|
||||
{
|
||||
[Group]
|
||||
public partial class GlobalPermissionCommands : EllieModule
|
||||
{
|
||||
private readonly GlobalPermissionService _service;
|
||||
private readonly DbService _db;
|
||||
|
||||
public GlobalPermissionCommands(GlobalPermissionService service, DbService db)
|
||||
{
|
||||
_service = service;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public async Task GlobalPermList()
|
||||
{
|
||||
var blockedModule = _service.BlockedModules;
|
||||
var blockedCommands = _service.BlockedCommands;
|
||||
if (!blockedModule.Any() && !blockedCommands.Any())
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.lgp_none);
|
||||
return;
|
||||
}
|
||||
|
||||
var embed = _eb.Create().WithOkColor();
|
||||
|
||||
if (blockedModule.Any())
|
||||
embed.AddField(GetText(strs.blocked_modules), string.Join("\n", _service.BlockedModules));
|
||||
|
||||
if (blockedCommands.Any())
|
||||
embed.AddField(GetText(strs.blocked_commands), string.Join("\n", _service.BlockedCommands));
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public async Task GlobalModule(ModuleOrCrInfo module)
|
||||
{
|
||||
var moduleName = module.Name.ToLowerInvariant();
|
||||
|
||||
var added = _service.ToggleModule(moduleName);
|
||||
|
||||
if (added)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.gmod_add(Format.Bold(module.Name)));
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.gmod_remove(Format.Bold(module.Name)));
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public async Task GlobalCommand(CommandOrExprInfo cmd)
|
||||
{
|
||||
var commandName = cmd.Name.ToLowerInvariant();
|
||||
var added = _service.ToggleCommand(commandName);
|
||||
|
||||
if (added)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.gcmd_add(Format.Bold(cmd.Name)));
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.gcmd_remove(Format.Bold(cmd.Name)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
#nullable disable
|
||||
using Ellie.Common.ModuleBehaviors;
|
||||
|
||||
namespace Ellie.Modules.Permissions.Services;
|
||||
|
||||
public class GlobalPermissionService : IExecPreCommand, IEService
|
||||
{
|
||||
public int Priority { get; } = 0;
|
||||
|
||||
public HashSet<string> BlockedCommands
|
||||
=> _bss.Data.Blocked.Commands;
|
||||
|
||||
public HashSet<string> BlockedModules
|
||||
=> _bss.Data.Blocked.Modules;
|
||||
|
||||
private readonly BotConfigService _bss;
|
||||
|
||||
public GlobalPermissionService(BotConfigService bss)
|
||||
=> _bss = bss;
|
||||
|
||||
|
||||
public Task<bool> ExecPreCommandAsync(ICommandContext ctx, string moduleName, CommandInfo command)
|
||||
{
|
||||
var settings = _bss.Data;
|
||||
var commandName = command.Name.ToLowerInvariant();
|
||||
|
||||
if (commandName != "resetglobalperms"
|
||||
&& (settings.Blocked.Commands.Contains(commandName)
|
||||
|| settings.Blocked.Modules.Contains(moduleName.ToLowerInvariant())))
|
||||
return Task.FromResult(true);
|
||||
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles module blacklist
|
||||
/// </summary>
|
||||
/// <param name="moduleName">Lowercase module name</param>
|
||||
/// <returns>Whether the module is added</returns>
|
||||
public bool ToggleModule(string moduleName)
|
||||
{
|
||||
var added = false;
|
||||
_bss.ModifyConfig(bs =>
|
||||
{
|
||||
if (bs.Blocked.Modules.Add(moduleName))
|
||||
added = true;
|
||||
else
|
||||
{
|
||||
bs.Blocked.Modules.Remove(moduleName);
|
||||
added = false;
|
||||
}
|
||||
});
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles command blacklist
|
||||
/// </summary>
|
||||
/// <param name="commandName">Lowercase command name</param>
|
||||
/// <returns>Whether the command is added</returns>
|
||||
public bool ToggleCommand(string commandName)
|
||||
{
|
||||
var added = false;
|
||||
_bss.ModifyConfig(bs =>
|
||||
{
|
||||
if (bs.Blocked.Commands.Add(commandName))
|
||||
added = true;
|
||||
else
|
||||
{
|
||||
bs.Blocked.Commands.Remove(commandName);
|
||||
added = false;
|
||||
}
|
||||
});
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets all global permissions
|
||||
/// </summary>
|
||||
public Task Reset()
|
||||
{
|
||||
_bss.ModifyConfig(bs =>
|
||||
{
|
||||
bs.Blocked.Commands.Clear();
|
||||
bs.Blocked.Modules.Clear();
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
32
src/Ellie.Bot.Modules.Permissions/GlobalUsings.cs
Normal file
32
src/Ellie.Bot.Modules.Permissions/GlobalUsings.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
// // global using System.Collections.Concurrent;
|
||||
global using NonBlocking;
|
||||
//
|
||||
// // packages
|
||||
global using Serilog;
|
||||
global using Humanizer;
|
||||
global using Newtonsoft;
|
||||
//
|
||||
// // ellie
|
||||
// global using Ellie;
|
||||
global using Ellie.Services;
|
||||
global using Ellise.Common; // new project
|
||||
global using Ellie.Common; // old + ellie specific things
|
||||
global using Ellie.Common.Attributes;
|
||||
global using Ellie.Extensions;
|
||||
// global using Ellie.Marmalade;
|
||||
|
||||
// discord
|
||||
global using Discord;
|
||||
global using Discord.Commands;
|
||||
global using Discord.Net;
|
||||
global using Discord.WebSocket;
|
||||
|
||||
// aliases
|
||||
global using GuildPerm = Discord.GuildPermission;
|
||||
global using ChannelPerm = Discord.ChannelPermission;
|
||||
global using BotPermAttribute = Discord.Commands.RequireBotPermissionAttribute;
|
||||
global using LeftoverAttribute = Discord.Commands.RemainderAttribute;
|
||||
global using TypeReaderResult = Ellie.Common.TypeReaders.TypeReaderResult;
|
||||
|
||||
// non-essential
|
||||
// global using JetBrains.Annotations;
|
11
src/Ellie.Bot.Modules.Permissions/PermissionCache.cs
Normal file
11
src/Ellie.Bot.Modules.Permissions/PermissionCache.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
#nullable disable
|
||||
using Ellie.Services.Database.Models;
|
||||
|
||||
namespace Ellie.Modules.Permissions.Common;
|
||||
|
||||
public class PermissionCache
|
||||
{
|
||||
public string PermRole { get; set; }
|
||||
public bool Verbose { get; set; } = true;
|
||||
public PermissionsCollection<Permissionv2> Permissions { get; set; }
|
||||
}
|
132
src/Ellie.Bot.Modules.Permissions/PermissionExtensions.cs
Normal file
132
src/Ellie.Bot.Modules.Permissions/PermissionExtensions.cs
Normal file
|
@ -0,0 +1,132 @@
|
|||
#nullable disable
|
||||
using Ellie.Services.Database.Models;
|
||||
|
||||
namespace Ellie.Modules.Permissions.Common;
|
||||
|
||||
public static class PermissionExtensions
|
||||
{
|
||||
public static bool CheckPermissions(
|
||||
this IEnumerable<Permissionv2> permsEnumerable,
|
||||
IUser user,
|
||||
IMessageChannel message,
|
||||
string commandName,
|
||||
string moduleName,
|
||||
out int permIndex)
|
||||
{
|
||||
var perms = permsEnumerable as List<Permissionv2> ?? permsEnumerable.ToList();
|
||||
|
||||
for (var i = perms.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var perm = perms[i];
|
||||
|
||||
var result = perm.CheckPermission(user, message, commandName, moduleName);
|
||||
|
||||
if (result is null)
|
||||
continue;
|
||||
permIndex = i;
|
||||
return result.Value;
|
||||
}
|
||||
|
||||
permIndex = -1; //defaut behaviour
|
||||
return true;
|
||||
}
|
||||
|
||||
//null = not applicable
|
||||
//true = applicable, allowed
|
||||
//false = applicable, not allowed
|
||||
public static bool? CheckPermission(
|
||||
this Permissionv2 perm,
|
||||
IUser user,
|
||||
IMessageChannel channel,
|
||||
string commandName,
|
||||
string moduleName)
|
||||
{
|
||||
if (!((perm.SecondaryTarget == SecondaryPermissionType.Command
|
||||
&& string.Equals(perm.SecondaryTargetName, commandName, StringComparison.InvariantCultureIgnoreCase))
|
||||
|| (perm.SecondaryTarget == SecondaryPermissionType.Module
|
||||
&& string.Equals(perm.SecondaryTargetName, moduleName, StringComparison.InvariantCultureIgnoreCase))
|
||||
|| perm.SecondaryTarget == SecondaryPermissionType.AllModules))
|
||||
return null;
|
||||
|
||||
var guildUser = user as IGuildUser;
|
||||
|
||||
switch (perm.PrimaryTarget)
|
||||
{
|
||||
case PrimaryPermissionType.User:
|
||||
if (perm.PrimaryTargetId == user.Id)
|
||||
return perm.State;
|
||||
break;
|
||||
case PrimaryPermissionType.Channel:
|
||||
if (perm.PrimaryTargetId == channel.Id)
|
||||
return perm.State;
|
||||
break;
|
||||
case PrimaryPermissionType.Role:
|
||||
if (guildUser is null)
|
||||
break;
|
||||
if (guildUser.RoleIds.Contains(perm.PrimaryTargetId))
|
||||
return perm.State;
|
||||
break;
|
||||
case PrimaryPermissionType.Server:
|
||||
if (guildUser is null)
|
||||
break;
|
||||
return perm.State;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string GetCommand(this Permissionv2 perm, string prefix, SocketGuild guild = null)
|
||||
{
|
||||
var com = string.Empty;
|
||||
switch (perm.PrimaryTarget)
|
||||
{
|
||||
case PrimaryPermissionType.User:
|
||||
com += "u";
|
||||
break;
|
||||
case PrimaryPermissionType.Channel:
|
||||
com += "c";
|
||||
break;
|
||||
case PrimaryPermissionType.Role:
|
||||
com += "r";
|
||||
break;
|
||||
case PrimaryPermissionType.Server:
|
||||
com += "s";
|
||||
break;
|
||||
}
|
||||
|
||||
switch (perm.SecondaryTarget)
|
||||
{
|
||||
case SecondaryPermissionType.Module:
|
||||
com += "m";
|
||||
break;
|
||||
case SecondaryPermissionType.Command:
|
||||
com += "c";
|
||||
break;
|
||||
case SecondaryPermissionType.AllModules:
|
||||
com = "a" + com + "m";
|
||||
break;
|
||||
}
|
||||
|
||||
var secName = perm.SecondaryTarget == SecondaryPermissionType.Command && !perm.IsCustomCommand
|
||||
? prefix + perm.SecondaryTargetName
|
||||
: perm.SecondaryTargetName;
|
||||
com += " " + (perm.SecondaryTargetName != "*" ? secName + " " : "") + (perm.State ? "enable" : "disable") + " ";
|
||||
|
||||
switch (perm.PrimaryTarget)
|
||||
{
|
||||
case PrimaryPermissionType.User:
|
||||
com += guild?.GetUser(perm.PrimaryTargetId)?.ToString() ?? $"<@{perm.PrimaryTargetId}>";
|
||||
break;
|
||||
case PrimaryPermissionType.Channel:
|
||||
com += $"<#{perm.PrimaryTargetId}>";
|
||||
break;
|
||||
case PrimaryPermissionType.Role:
|
||||
com += guild?.GetRole(perm.PrimaryTargetId)?.ToString() ?? $"<@&{perm.PrimaryTargetId}>";
|
||||
break;
|
||||
case PrimaryPermissionType.Server:
|
||||
break;
|
||||
}
|
||||
|
||||
return prefix + com;
|
||||
}
|
||||
}
|
516
src/Ellie.Bot.Modules.Permissions/Permissions.cs
Normal file
516
src/Ellie.Bot.Modules.Permissions/Permissions.cs
Normal file
|
@ -0,0 +1,516 @@
|
|||
#nullable disable
|
||||
using Ellie.Common.TypeReaders;
|
||||
using Ellie.Common.TypeReaders.Models;
|
||||
using Ellie.Db;
|
||||
using Ellie.Modules.Permissions.Common;
|
||||
using Ellie.Modules.Permissions.Services;
|
||||
using Ellie.Services.Database.Models;
|
||||
|
||||
namespace Ellie.Modules.Permissions;
|
||||
|
||||
public partial class Permissions : EllieModule<PermissionService>
|
||||
{
|
||||
public enum Reset { Reset }
|
||||
|
||||
private readonly DbService _db;
|
||||
|
||||
public Permissions(DbService db)
|
||||
=> _db = db;
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Verbose(PermissionAction action = null)
|
||||
{
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GcWithPermissionsFor(ctx.Guild.Id);
|
||||
if (action is null)
|
||||
action = new(!config.VerbosePermissions); // New behaviour, can toggle.
|
||||
config.VerbosePermissions = action.Value;
|
||||
await uow.SaveChangesAsync();
|
||||
_service.UpdateCache(config);
|
||||
}
|
||||
|
||||
if (action.Value)
|
||||
await ReplyConfirmLocalizedAsync(strs.verbose_true);
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.verbose_false);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[Priority(0)]
|
||||
public async Task PermRole([Leftover] IRole role = null)
|
||||
{
|
||||
if (role is not null && role == role.Guild.EveryoneRole)
|
||||
return;
|
||||
|
||||
if (role is null)
|
||||
{
|
||||
var cache = _service.GetCacheFor(ctx.Guild.Id);
|
||||
if (!ulong.TryParse(cache.PermRole, out var roleId)
|
||||
|| (role = ((SocketGuild)ctx.Guild).GetRole(roleId)) is null)
|
||||
await ReplyConfirmLocalizedAsync(strs.permrole_not_set);
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.permrole(Format.Bold(role.ToString())));
|
||||
return;
|
||||
}
|
||||
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GcWithPermissionsFor(ctx.Guild.Id);
|
||||
config.PermissionRole = role.Id.ToString();
|
||||
uow.SaveChanges();
|
||||
_service.UpdateCache(config);
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.permrole_changed(Format.Bold(role.Name)));
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[Priority(1)]
|
||||
public async Task PermRole(Reset _)
|
||||
{
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GcWithPermissionsFor(ctx.Guild.Id);
|
||||
config.PermissionRole = null;
|
||||
await uow.SaveChangesAsync();
|
||||
_service.UpdateCache(config);
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.permrole_reset);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ListPerms(int page = 1)
|
||||
{
|
||||
if (page < 1)
|
||||
return;
|
||||
|
||||
IList<Permissionv2> perms;
|
||||
|
||||
if (_service.Cache.TryGetValue(ctx.Guild.Id, out var permCache))
|
||||
perms = permCache.Permissions.Source.ToList();
|
||||
else
|
||||
perms = Permissionv2.GetDefaultPermlist;
|
||||
|
||||
var startPos = 20 * (page - 1);
|
||||
var toSend = Format.Bold(GetText(strs.page(page)))
|
||||
+ "\n\n"
|
||||
+ string.Join("\n",
|
||||
perms.Reverse()
|
||||
.Skip(startPos)
|
||||
.Take(20)
|
||||
.Select(p =>
|
||||
{
|
||||
var str =
|
||||
$"`{p.Index + 1}.` {Format.Bold(p.GetCommand(prefix, (SocketGuild)ctx.Guild))}";
|
||||
if (p.Index == 0)
|
||||
str += $" [{GetText(strs.uneditable)}]";
|
||||
return str;
|
||||
}));
|
||||
|
||||
await ctx.Channel.SendMessageAsync(toSend);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task RemovePerm(int index)
|
||||
{
|
||||
index -= 1;
|
||||
if (index < 0)
|
||||
return;
|
||||
try
|
||||
{
|
||||
Permissionv2 p;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GcWithPermissionsFor(ctx.Guild.Id);
|
||||
var permsCol = new PermissionsCollection<Permissionv2>(config.Permissions);
|
||||
p = permsCol[index];
|
||||
permsCol.RemoveAt(index);
|
||||
uow.Remove(p);
|
||||
await uow.SaveChangesAsync();
|
||||
_service.UpdateCache(config);
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.removed(index + 1,
|
||||
Format.Code(p.GetCommand(prefix, (SocketGuild)ctx.Guild))));
|
||||
}
|
||||
catch (IndexOutOfRangeException)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.perm_out_of_range);
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task MovePerm(int from, int to)
|
||||
{
|
||||
from -= 1;
|
||||
to -= 1;
|
||||
if (!(from == to || from < 0 || to < 0))
|
||||
{
|
||||
try
|
||||
{
|
||||
Permissionv2 fromPerm;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GcWithPermissionsFor(ctx.Guild.Id);
|
||||
var permsCol = new PermissionsCollection<Permissionv2>(config.Permissions);
|
||||
|
||||
var fromFound = @from < permsCol.Count;
|
||||
var toFound = to < permsCol.Count;
|
||||
|
||||
if (!fromFound)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.perm_not_found(++@from));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!toFound)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.perm_not_found(++to));
|
||||
return;
|
||||
}
|
||||
|
||||
fromPerm = permsCol[@from];
|
||||
|
||||
permsCol.RemoveAt(@from);
|
||||
permsCol.Insert(to, fromPerm);
|
||||
await uow.SaveChangesAsync();
|
||||
_service.UpdateCache(config);
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.moved_permission(
|
||||
Format.Code(fromPerm.GetCommand(prefix, (SocketGuild)ctx.Guild)),
|
||||
++@from,
|
||||
++to));
|
||||
|
||||
return;
|
||||
}
|
||||
catch (Exception e) when (e is ArgumentOutOfRangeException or IndexOutOfRangeException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.perm_out_of_range);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task SrvrCmd(CommandOrExprInfo command, PermissionAction action)
|
||||
{
|
||||
await _service.AddPermissions(ctx.Guild.Id,
|
||||
new Permissionv2
|
||||
{
|
||||
PrimaryTarget = PrimaryPermissionType.Server,
|
||||
PrimaryTargetId = 0,
|
||||
SecondaryTarget = SecondaryPermissionType.Command,
|
||||
SecondaryTargetName = command.Name.ToLowerInvariant(),
|
||||
State = action.Value,
|
||||
IsCustomCommand = command.IsCustom
|
||||
});
|
||||
|
||||
if (action.Value)
|
||||
await ReplyConfirmLocalizedAsync(strs.sx_enable(Format.Code(command.Name), GetText(strs.of_command)));
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.sx_disable(Format.Code(command.Name), GetText(strs.of_command)));
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task SrvrMdl(ModuleOrCrInfo module, PermissionAction action)
|
||||
{
|
||||
await _service.AddPermissions(ctx.Guild.Id,
|
||||
new Permissionv2
|
||||
{
|
||||
PrimaryTarget = PrimaryPermissionType.Server,
|
||||
PrimaryTargetId = 0,
|
||||
SecondaryTarget = SecondaryPermissionType.Module,
|
||||
SecondaryTargetName = module.Name.ToLowerInvariant(),
|
||||
State = action.Value
|
||||
});
|
||||
|
||||
if (action.Value)
|
||||
await ReplyConfirmLocalizedAsync(strs.sx_enable(Format.Code(module.Name), GetText(strs.of_module)));
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.sx_disable(Format.Code(module.Name), GetText(strs.of_module)));
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task UsrCmd(CommandOrExprInfo command, PermissionAction action, [Leftover] IGuildUser user)
|
||||
{
|
||||
await _service.AddPermissions(ctx.Guild.Id,
|
||||
new Permissionv2
|
||||
{
|
||||
PrimaryTarget = PrimaryPermissionType.User,
|
||||
PrimaryTargetId = user.Id,
|
||||
SecondaryTarget = SecondaryPermissionType.Command,
|
||||
SecondaryTargetName = command.Name.ToLowerInvariant(),
|
||||
State = action.Value,
|
||||
IsCustomCommand = command.IsCustom
|
||||
});
|
||||
|
||||
if (action.Value)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.ux_enable(Format.Code(command.Name),
|
||||
GetText(strs.of_command),
|
||||
Format.Code(user.ToString())));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.ux_disable(Format.Code(command.Name),
|
||||
GetText(strs.of_command),
|
||||
Format.Code(user.ToString())));
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task UsrMdl(ModuleOrCrInfo module, PermissionAction action, [Leftover] IGuildUser user)
|
||||
{
|
||||
await _service.AddPermissions(ctx.Guild.Id,
|
||||
new Permissionv2
|
||||
{
|
||||
PrimaryTarget = PrimaryPermissionType.User,
|
||||
PrimaryTargetId = user.Id,
|
||||
SecondaryTarget = SecondaryPermissionType.Module,
|
||||
SecondaryTargetName = module.Name.ToLowerInvariant(),
|
||||
State = action.Value
|
||||
});
|
||||
|
||||
if (action.Value)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.ux_enable(Format.Code(module.Name),
|
||||
GetText(strs.of_module),
|
||||
Format.Code(user.ToString())));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.ux_disable(Format.Code(module.Name),
|
||||
GetText(strs.of_module),
|
||||
Format.Code(user.ToString())));
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task RoleCmd(CommandOrExprInfo command, PermissionAction action, [Leftover] IRole role)
|
||||
{
|
||||
if (role == role.Guild.EveryoneRole)
|
||||
return;
|
||||
|
||||
await _service.AddPermissions(ctx.Guild.Id,
|
||||
new Permissionv2
|
||||
{
|
||||
PrimaryTarget = PrimaryPermissionType.Role,
|
||||
PrimaryTargetId = role.Id,
|
||||
SecondaryTarget = SecondaryPermissionType.Command,
|
||||
SecondaryTargetName = command.Name.ToLowerInvariant(),
|
||||
State = action.Value,
|
||||
IsCustomCommand = command.IsCustom
|
||||
});
|
||||
|
||||
if (action.Value)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.rx_enable(Format.Code(command.Name),
|
||||
GetText(strs.of_command),
|
||||
Format.Code(role.Name)));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.rx_disable(Format.Code(command.Name),
|
||||
GetText(strs.of_command),
|
||||
Format.Code(role.Name)));
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task RoleMdl(ModuleOrCrInfo module, PermissionAction action, [Leftover] IRole role)
|
||||
{
|
||||
if (role == role.Guild.EveryoneRole)
|
||||
return;
|
||||
|
||||
await _service.AddPermissions(ctx.Guild.Id,
|
||||
new Permissionv2
|
||||
{
|
||||
PrimaryTarget = PrimaryPermissionType.Role,
|
||||
PrimaryTargetId = role.Id,
|
||||
SecondaryTarget = SecondaryPermissionType.Module,
|
||||
SecondaryTargetName = module.Name.ToLowerInvariant(),
|
||||
State = action.Value
|
||||
});
|
||||
|
||||
|
||||
if (action.Value)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.rx_enable(Format.Code(module.Name),
|
||||
GetText(strs.of_module),
|
||||
Format.Code(role.Name)));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.rx_disable(Format.Code(module.Name),
|
||||
GetText(strs.of_module),
|
||||
Format.Code(role.Name)));
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ChnlCmd(CommandOrExprInfo command, PermissionAction action, [Leftover] ITextChannel chnl)
|
||||
{
|
||||
await _service.AddPermissions(ctx.Guild.Id,
|
||||
new Permissionv2
|
||||
{
|
||||
PrimaryTarget = PrimaryPermissionType.Channel,
|
||||
PrimaryTargetId = chnl.Id,
|
||||
SecondaryTarget = SecondaryPermissionType.Command,
|
||||
SecondaryTargetName = command.Name.ToLowerInvariant(),
|
||||
State = action.Value,
|
||||
IsCustomCommand = command.IsCustom
|
||||
});
|
||||
|
||||
if (action.Value)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.cx_enable(Format.Code(command.Name),
|
||||
GetText(strs.of_command),
|
||||
Format.Code(chnl.Name)));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.cx_disable(Format.Code(command.Name),
|
||||
GetText(strs.of_command),
|
||||
Format.Code(chnl.Name)));
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ChnlMdl(ModuleOrCrInfo module, PermissionAction action, [Leftover] ITextChannel chnl)
|
||||
{
|
||||
await _service.AddPermissions(ctx.Guild.Id,
|
||||
new Permissionv2
|
||||
{
|
||||
PrimaryTarget = PrimaryPermissionType.Channel,
|
||||
PrimaryTargetId = chnl.Id,
|
||||
SecondaryTarget = SecondaryPermissionType.Module,
|
||||
SecondaryTargetName = module.Name.ToLowerInvariant(),
|
||||
State = action.Value
|
||||
});
|
||||
|
||||
if (action.Value)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.cx_enable(Format.Code(module.Name),
|
||||
GetText(strs.of_module),
|
||||
Format.Code(chnl.Name)));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.cx_disable(Format.Code(module.Name),
|
||||
GetText(strs.of_module),
|
||||
Format.Code(chnl.Name)));
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task AllChnlMdls(PermissionAction action, [Leftover] ITextChannel chnl)
|
||||
{
|
||||
await _service.AddPermissions(ctx.Guild.Id,
|
||||
new Permissionv2
|
||||
{
|
||||
PrimaryTarget = PrimaryPermissionType.Channel,
|
||||
PrimaryTargetId = chnl.Id,
|
||||
SecondaryTarget = SecondaryPermissionType.AllModules,
|
||||
SecondaryTargetName = "*",
|
||||
State = action.Value
|
||||
});
|
||||
|
||||
if (action.Value)
|
||||
await ReplyConfirmLocalizedAsync(strs.acm_enable(Format.Code(chnl.Name)));
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.acm_disable(Format.Code(chnl.Name)));
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task AllRoleMdls(PermissionAction action, [Leftover] IRole role)
|
||||
{
|
||||
if (role == role.Guild.EveryoneRole)
|
||||
return;
|
||||
|
||||
await _service.AddPermissions(ctx.Guild.Id,
|
||||
new Permissionv2
|
||||
{
|
||||
PrimaryTarget = PrimaryPermissionType.Role,
|
||||
PrimaryTargetId = role.Id,
|
||||
SecondaryTarget = SecondaryPermissionType.AllModules,
|
||||
SecondaryTargetName = "*",
|
||||
State = action.Value
|
||||
});
|
||||
|
||||
if (action.Value)
|
||||
await ReplyConfirmLocalizedAsync(strs.arm_enable(Format.Code(role.Name)));
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.arm_disable(Format.Code(role.Name)));
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task AllUsrMdls(PermissionAction action, [Leftover] IUser user)
|
||||
{
|
||||
await _service.AddPermissions(ctx.Guild.Id,
|
||||
new Permissionv2
|
||||
{
|
||||
PrimaryTarget = PrimaryPermissionType.User,
|
||||
PrimaryTargetId = user.Id,
|
||||
SecondaryTarget = SecondaryPermissionType.AllModules,
|
||||
SecondaryTargetName = "*",
|
||||
State = action.Value
|
||||
});
|
||||
|
||||
if (action.Value)
|
||||
await ReplyConfirmLocalizedAsync(strs.aum_enable(Format.Code(user.ToString())));
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.aum_disable(Format.Code(user.ToString())));
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task AllSrvrMdls(PermissionAction action)
|
||||
{
|
||||
var newPerm = new Permissionv2
|
||||
{
|
||||
PrimaryTarget = PrimaryPermissionType.Server,
|
||||
PrimaryTargetId = 0,
|
||||
SecondaryTarget = SecondaryPermissionType.AllModules,
|
||||
SecondaryTargetName = "*",
|
||||
State = action.Value
|
||||
};
|
||||
|
||||
var allowUser = new Permissionv2
|
||||
{
|
||||
PrimaryTarget = PrimaryPermissionType.User,
|
||||
PrimaryTargetId = ctx.User.Id,
|
||||
SecondaryTarget = SecondaryPermissionType.AllModules,
|
||||
SecondaryTargetName = "*",
|
||||
State = true
|
||||
};
|
||||
|
||||
await _service.AddPermissions(ctx.Guild.Id, newPerm, allowUser);
|
||||
|
||||
if (action.Value)
|
||||
await ReplyConfirmLocalizedAsync(strs.asm_enable);
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.asm_disable);
|
||||
}
|
||||
}
|
74
src/Ellie.Bot.Modules.Permissions/PermissionsCollection.cs
Normal file
74
src/Ellie.Bot.Modules.Permissions/PermissionsCollection.cs
Normal file
|
@ -0,0 +1,74 @@
|
|||
#nullable disable
|
||||
namespace Ellie.Modules.Permissions.Common;
|
||||
|
||||
public class PermissionsCollection<T> : IndexedCollection<T>
|
||||
where T : class, IIndexed
|
||||
{
|
||||
public override T this[int index]
|
||||
{
|
||||
get => Source[index];
|
||||
set
|
||||
{
|
||||
lock (_localLocker)
|
||||
{
|
||||
if (index == 0) // can't set first element. It's always allow all
|
||||
throw new IndexOutOfRangeException(nameof(index));
|
||||
base[index] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly object _localLocker = new();
|
||||
|
||||
public PermissionsCollection(IEnumerable<T> source)
|
||||
: base(source)
|
||||
{
|
||||
}
|
||||
|
||||
public static implicit operator List<T>(PermissionsCollection<T> x)
|
||||
=> x.Source;
|
||||
|
||||
public override void Clear()
|
||||
{
|
||||
lock (_localLocker)
|
||||
{
|
||||
var first = Source[0];
|
||||
base.Clear();
|
||||
Source[0] = first;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Remove(T item)
|
||||
{
|
||||
bool removed;
|
||||
lock (_localLocker)
|
||||
{
|
||||
if (Source.IndexOf(item) == 0)
|
||||
throw new ArgumentException("You can't remove first permsission (allow all)");
|
||||
removed = base.Remove(item);
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
public override void Insert(int index, T item)
|
||||
{
|
||||
lock (_localLocker)
|
||||
{
|
||||
if (index == 0) // can't insert on first place. Last item is always allow all.
|
||||
throw new IndexOutOfRangeException(nameof(index));
|
||||
base.Insert(index, item);
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveAt(int index)
|
||||
{
|
||||
lock (_localLocker)
|
||||
{
|
||||
if (index == 0) // you can't remove first permission (allow all)
|
||||
throw new IndexOutOfRangeException(nameof(index));
|
||||
|
||||
base.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
}
|
184
src/Ellie.Bot.Modules.Permissions/PermissionsService.cs
Normal file
184
src/Ellie.Bot.Modules.Permissions/PermissionsService.cs
Normal file
|
@ -0,0 +1,184 @@
|
|||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ellie.Common.ModuleBehaviors;
|
||||
using Ellie.Db;
|
||||
using Ellie.Modules.Permissions.Common;
|
||||
using Ellie.Services.Database.Models;
|
||||
|
||||
namespace Ellie.Modules.Permissions.Services;
|
||||
|
||||
public class PermissionService : IExecPreCommand, IEService
|
||||
{
|
||||
public int Priority { get; } = 0;
|
||||
|
||||
//guildid, root permission
|
||||
public ConcurrentDictionary<ulong, PermissionCache> Cache { get; } = new();
|
||||
|
||||
private readonly DbService _db;
|
||||
private readonly CommandHandler _cmd;
|
||||
private readonly IBotStrings _strings;
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
|
||||
public PermissionService(
|
||||
DiscordSocketClient client,
|
||||
DbService db,
|
||||
CommandHandler cmd,
|
||||
IBotStrings strings,
|
||||
IEmbedBuilderService eb)
|
||||
{
|
||||
_db = db;
|
||||
_cmd = cmd;
|
||||
_strings = strings;
|
||||
_eb = eb;
|
||||
|
||||
using var uow = _db.GetDbContext();
|
||||
foreach (var x in uow.Set<GuildConfig>().PermissionsForAll(client.Guilds.ToArray().Select(x => x.Id).ToList()))
|
||||
{
|
||||
Cache.TryAdd(x.GuildId,
|
||||
new()
|
||||
{
|
||||
Verbose = x.VerbosePermissions,
|
||||
PermRole = x.PermissionRole,
|
||||
Permissions = new(x.Permissions)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public PermissionCache GetCacheFor(ulong guildId)
|
||||
{
|
||||
if (!Cache.TryGetValue(guildId, out var pc))
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GuildConfigsForId(guildId, set => set.Include(x => x.Permissions));
|
||||
UpdateCache(config);
|
||||
}
|
||||
|
||||
Cache.TryGetValue(guildId, out pc);
|
||||
if (pc is null)
|
||||
throw new("Cache is null.");
|
||||
}
|
||||
|
||||
return pc;
|
||||
}
|
||||
|
||||
public async Task AddPermissions(ulong guildId, params Permissionv2[] perms)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var config = uow.GcWithPermissionsFor(guildId);
|
||||
//var orderedPerms = new PermissionsCollection<Permissionv2>(config.Permissions);
|
||||
var max = config.Permissions.Max(x => x.Index); //have to set its index to be the highest
|
||||
foreach (var perm in perms)
|
||||
{
|
||||
perm.Index = ++max;
|
||||
config.Permissions.Add(perm);
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
UpdateCache(config);
|
||||
}
|
||||
|
||||
public void UpdateCache(GuildConfig config)
|
||||
=> Cache.AddOrUpdate(config.GuildId,
|
||||
new PermissionCache
|
||||
{
|
||||
Permissions = new(config.Permissions),
|
||||
PermRole = config.PermissionRole,
|
||||
Verbose = config.VerbosePermissions
|
||||
},
|
||||
(_, old) =>
|
||||
{
|
||||
old.Permissions = new(config.Permissions);
|
||||
old.PermRole = config.PermissionRole;
|
||||
old.Verbose = config.VerbosePermissions;
|
||||
return old;
|
||||
});
|
||||
|
||||
public async Task<bool> ExecPreCommandAsync(ICommandContext ctx, string moduleName, CommandInfo command)
|
||||
{
|
||||
var guild = ctx.Guild;
|
||||
var msg = ctx.Message;
|
||||
var user = ctx.User;
|
||||
var channel = ctx.Channel;
|
||||
var commandName = command.Name.ToLowerInvariant();
|
||||
|
||||
if (guild is null)
|
||||
return false;
|
||||
|
||||
var resetCommand = commandName == "resetperms";
|
||||
|
||||
var pc = GetCacheFor(guild.Id);
|
||||
if (!resetCommand
|
||||
&& !pc.Permissions.CheckPermissions(msg.Author, msg.Channel, commandName, moduleName, out var index))
|
||||
{
|
||||
if (pc.Verbose)
|
||||
{
|
||||
try
|
||||
{
|
||||
await channel.SendErrorAsync(_eb,
|
||||
_strings.GetText(strs.perm_prevent(index + 1,
|
||||
Format.Bold(pc.Permissions[index]
|
||||
.GetCommand(_cmd.GetPrefix(guild), (SocketGuild)guild))),
|
||||
guild.Id));
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (moduleName == nameof(Permissions))
|
||||
{
|
||||
if (user is not IGuildUser guildUser)
|
||||
return true;
|
||||
|
||||
if (guildUser.GuildPermissions.Administrator)
|
||||
return false;
|
||||
|
||||
var permRole = pc.PermRole;
|
||||
if (!ulong.TryParse(permRole, out var rid))
|
||||
rid = 0;
|
||||
string returnMsg;
|
||||
IRole role;
|
||||
if (string.IsNullOrWhiteSpace(permRole) || (role = guild.GetRole(rid)) is null)
|
||||
{
|
||||
returnMsg = "You need Admin permissions in order to use permission commands.";
|
||||
if (pc.Verbose)
|
||||
{
|
||||
try { await channel.SendErrorAsync(_eb, returnMsg); }
|
||||
catch { }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!guildUser.RoleIds.Contains(rid))
|
||||
{
|
||||
returnMsg = $"You need the {Format.Bold(role.Name)} role in order to use permission commands.";
|
||||
if (pc.Verbose)
|
||||
{
|
||||
try { await channel.SendErrorAsync(_eb, returnMsg); }
|
||||
catch { }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task Reset(ulong guildId)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var config = uow.GcWithPermissionsFor(guildId);
|
||||
config.Permissions = Permissionv2.GetDefaultPermlist;
|
||||
await uow.SaveChangesAsync();
|
||||
UpdateCache(config);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
#nullable disable
|
||||
using Ellie.Modules.Permissions.Services;
|
||||
|
||||
namespace Ellie.Modules.Permissions;
|
||||
|
||||
public partial class Permissions
|
||||
{
|
||||
[Group]
|
||||
public partial class ResetPermissionsCommands : EllieModule
|
||||
{
|
||||
private readonly GlobalPermissionService _gps;
|
||||
private readonly PermissionService _perms;
|
||||
|
||||
public ResetPermissionsCommands(GlobalPermissionService gps, PermissionService perms)
|
||||
{
|
||||
_gps = gps;
|
||||
_perms = perms;
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ResetPerms()
|
||||
{
|
||||
await _perms.Reset(ctx.Guild.Id);
|
||||
await ReplyConfirmLocalizedAsync(strs.perms_reset);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public async Task ResetGlobalPerms()
|
||||
{
|
||||
await _gps.Reset();
|
||||
await ReplyConfirmLocalizedAsync(strs.global_perms_reset);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue