change: sar rework, improved

This commit is contained in:
Kwoth
2024-11-17 23:36:11 +00:00
parent d7fbf44d53
commit 0a9d53d380
30 changed files with 8416 additions and 555 deletions

View File

@@ -136,7 +136,7 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, INService
await using var linqCtx = ctx.CreateLinqToDBContext();
await using var tempTable = linqCtx.CreateTempTable<CleanupId>();
foreach (var chunk in allIds.Chunk(20000))
foreach (var chunk in allIds.Chunk(10000))
{
await tempTable.BulkCopyAsync(chunk.Select(x => new CleanupId()
{
@@ -187,13 +187,6 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, INService
.Contains(x.GuildId))
.DeleteAsync();
// delete ignored users
await ctx.GetTable<DiscordPermOverride>()
.Where(x => x.GuildId != null
&& !tempTable.Select(x => x.GuildId)
.Contains(x.GuildId.Value))
.DeleteAsync();
// delete perm overrides
await ctx.GetTable<DiscordPermOverride>()
.Where(x => x.GuildId != null
@@ -218,7 +211,49 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, INService
.Where(x => !tempTable.Select(x => x.GuildId)
.Contains(x.GuildId))
.DeleteAsync();
// delete sar
await ctx.GetTable<SarGroup>()
.Where(x => !tempTable.Select(x => x.GuildId)
.Contains(x.GuildId))
.DeleteAsync();
// delete warnings
await ctx.GetTable<Warning>()
.Where(x => !tempTable.Select(x => x.GuildId)
.Contains(x.GuildId))
.DeleteAsync();
// delete warn punishments
await ctx.GetTable<WarningPunishment>()
.Where(x => !tempTable.Select(x => x.GuildId)
.Contains(x.GuildId))
.DeleteAsync();
// delete sticky roles
await ctx.GetTable<StickyRole>()
.Where(x => !tempTable.Select(x => x.GuildId)
.Contains(x.GuildId))
.DeleteAsync();
// delete at channels
await ctx.GetTable<AutoTranslateChannel>()
.Where(x => !tempTable.Select(x => x.GuildId)
.Contains(x.GuildId))
.DeleteAsync();
// delete ban templates
await ctx.GetTable<BanTemplate>()
.Where(x => !tempTable.Select(x => x.GuildId)
.Contains(x.GuildId))
.DeleteAsync();
// delete reminders
await ctx.GetTable<Reminder>()
.Where(x => !tempTable.Select(x => x.GuildId)
.Contains(x.ServerId))
.DeleteAsync();
return new()
{
GuildCount = guildIds.Keys.Count,

View File

@@ -6,16 +6,155 @@ namespace NadekoBot.Modules.Administration;
public partial class Administration
{
[Group]
public partial class SelfAssignedRolesHelpers : NadekoModule<SelfAssignedRolesService>
{
private readonly SarAssignerService _sas;
public SelfAssignedRolesHelpers(SarAssignerService sas)
{
_sas = sas;
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task Iam([Leftover] IRole role)
{
var guildUser = (IGuildUser)ctx.User;
var group = await _service.GetRoleGroup(ctx.User.Id, role.Id);
IUserMessage msg = null;
try
{
if (group is null)
{
msg = await Response().Error(strs.self_assign_not).SendAsync();
return;
}
var tcs = new TaskCompletionSource<SarAssignResult>(TaskCreationOptions.RunContinuationsAsynchronously);
await _sas.Add(new()
{
Group = group,
RoleId = role.Id,
User = guildUser,
CompletionTask = tcs
});
var res = await tcs.Task;
if (res.TryPickT0(out _, out var error))
{
msg = await Response()
.Confirm(strs.self_assign_success(Format.Bold(role.Name)))
.SendAsync();
}
else
{
var resStr = error.Match(
_ => strs.error_occured,
lvlReq => strs.self_assign_not_level(Format.Bold(lvlReq.Level.ToString())),
roleRq => strs.self_assign_role_req(Format.Bold(ctx.Guild.GetRole(roleRq.RoleId).ToString()
?? "missing role " + roleRq.RoleId),
group.Name),
_ => strs.self_assign_already(Format.Bold(role.Name)),
_ => strs.self_assign_perms);
msg = await Response().Error(resStr).SendAsync();
}
}
finally
{
var ad = _service.GetAutoDelete(ctx.Guild.Id);
if (ad)
{
msg?.DeleteAfter(3);
ctx.Message.DeleteAfter(3);
}
}
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task Iamnot([Leftover] IRole role)
{
var guildUser = (IGuildUser)ctx.User;
IUserMessage msg = null;
try
{
if (guildUser.RoleIds.Contains(role.Id))
{
msg = await Response().Error(strs.self_assign_not_have(Format.Bold(role.Name))).SendAsync();
return;
}
var group = await _service.GetRoleGroup(role.Guild.Id, role.Id);
if (group is null || group.Roles.All(x => x.RoleId != role.Id))
{
msg = await Response().Error(strs.self_assign_not).SendAsync();
return;
}
if (role.Position >= ((SocketGuild)ctx.Guild).CurrentUser.Roles.Max(x => x.Position))
{
msg = await Response().Error(strs.self_assign_perms).SendAsync();
return;
}
await guildUser.RemoveRoleAsync(role);
msg = await Response().Confirm(strs.self_assign_remove(Format.Bold(role.Name))).SendAsync();
}
finally
{
var ad = _service.GetAutoDelete(ctx.Guild.Id);
if (ad)
{
msg?.DeleteAfter(3);
ctx.Message.DeleteAfter(3);
}
}
}
}
[Group("sar")]
public partial class SelfAssignedRolesCommands : NadekoModule<SelfAssignedRolesService>
{
private readonly SarAssignerService _sas;
public SelfAssignedRolesCommands(SarAssignerService sas)
{
_sas = sas;
}
protected async Task<bool> CheckRoleHierarchy(IRole role)
{
var botUser = ((SocketGuild)ctx.Guild).CurrentUser;
var ownerId = ctx.Guild.OwnerId;
var modMaxRole = ((IGuildUser)ctx.User).GetRoles().Max(r => r.Position);
var botMaxRole = botUser.GetRoles().Max(r => r.Position);
// role must be lower than the bot role
// and the mod must have a higher role
if (botMaxRole <= role.Position
|| (ctx.User.Id != ownerId && role.Position >= modMaxRole))
{
await Response().Error(strs.hierarchy).SendAsync();
return false;
}
return true;
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageMessages)]
[BotPerm(GuildPerm.ManageMessages)]
public async Task AdSarm()
public async Task SarAutoDelete()
{
var newVal = _service.ToggleAdSarm(ctx.Guild.Id);
var newVal = await _service.ToggleAutoDelete(ctx.Guild.Id);
if (newVal)
await Response().Confirm(strs.adsarm_enable(prefix)).SendAsync();
@@ -28,30 +167,24 @@ public partial class Administration
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[Priority(1)]
public Task Asar([Leftover] IRole role)
=> Asar(0, role);
public Task SarAdd([Leftover] IRole role)
=> SarAdd(0, role);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[Priority(0)]
public async Task Asar(int group, [Leftover] IRole role)
public async Task SarAdd(int group, [Leftover] IRole role)
{
var guser = (IGuildUser)ctx.User;
if (ctx.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= role.Position)
if (!await CheckRoleHierarchy(role))
return;
var succ = _service.AddNew(ctx.Guild.Id, role, group);
await _service.AddAsync(ctx.Guild.Id, role.Id, group);
if (succ)
{
await Response()
.Confirm(strs.role_added(Format.Bold(role.Name), Format.Bold(group.ToString())))
.SendAsync();
}
else
await Response().Error(strs.role_in_list(Format.Bold(role.Name))).SendAsync();
await Response()
.Confirm(strs.role_added(Format.Bold(role.Name), Format.Bold(group.ToString())))
.SendAsync();
}
[Cmd]
@@ -59,9 +192,9 @@ public partial class Administration
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[Priority(0)]
public async Task Sargn(int group, [Leftover] string name = null)
public async Task SarGroupName(int group, [Leftover] string name = null)
{
var set = await _service.SetNameAsync(ctx.Guild.Id, group, name);
var set = await _service.SetGroupNameAsync(ctx.Guild.Id, group, name);
if (set)
{
@@ -70,19 +203,19 @@ public partial class Administration
.SendAsync();
}
else
{
await Response().Confirm(strs.group_name_removed(Format.Bold(group.ToString()))).SendAsync();
}
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
public async Task Rsar([Leftover] IRole role)
public async Task SarRemove([Leftover] IRole role)
{
var guser = (IGuildUser)ctx.User;
if (ctx.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= role.Position)
return;
var success = _service.RemoveSar(role.Guild.Id, role.Id);
var success = await _service.RemoveAsync(role.Guild.Id, role.Id);
if (!success)
await Response().Error(strs.self_assign_not).SendAsync();
else
@@ -91,59 +224,81 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task Lsar(int page = 1)
public async Task SarList(int page = 1)
{
if (--page < 0)
return;
var (exclusive, roles, groups) = _service.GetRoles(ctx.Guild);
var groups = await _service.GetSarsAsync(ctx.Guild.Id);
var gDict = groups.ToDictionary(x => x.Id, x => x);
await Response()
.Paginated()
.Items(roles.OrderBy(x => x.Model.Group).ToList())
.Items(groups.SelectMany(x => x.Roles).ToList())
.PageSize(20)
.CurrentPage(page)
.Page((items, _) =>
.Page(async (items, _) =>
{
var rolesStr = new StringBuilder();
var roleGroups = items
.GroupBy(x => x.Model.Group)
.GroupBy(x => x.SarGroupId)
.OrderBy(x => x.Key);
var eb = _sender.CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.self_assign_list(groups.Sum(x => x.Roles.Count))));
foreach (var kvp in roleGroups)
{
string groupNameText;
if (!groups.TryGetValue(kvp.Key, out var name))
groupNameText = Format.Bold(GetText(strs.self_assign_group(kvp.Key)));
else
groupNameText = Format.Bold($"{kvp.Key} - {name.TrimTo(25, true)}");
var group = gDict[kvp.Key];
rolesStr.AppendLine("\t\t\t\t ⟪" + groupNameText + "⟫");
foreach (var (model, role) in kvp.AsEnumerable())
var groupNameText = "";
if (!string.IsNullOrWhiteSpace(group.Name))
groupNameText += $" **{group.Name}**";
groupNameText = $"`{group.GroupNumber}` {groupNameText}";
var rolesStr = new StringBuilder();
if (group.IsExclusive)
{
if (role is null)
rolesStr.AppendLine(Format.Italics(GetText(strs.choose_one)));
}
if (group.RoleReq is ulong rrId)
{
var rr = ctx.Guild.GetRole(rrId);
if (rr is null)
{
await _service.SetGroupRoleReq(group.GuildId, group.GroupNumber, null);
}
else
{
// first character is invisible space
if (model.LevelRequirement == 0)
rolesStr.AppendLine(" " + role.Name);
else
rolesStr.AppendLine(" " + role.Name + $" (lvl {model.LevelRequirement}+)");
rolesStr.AppendLine(
Format.Italics(GetText(strs.requires_role(Format.Bold(rr.Name)))));
}
}
rolesStr.AppendLine();
foreach (var sar in kvp)
{
var roleName = (ctx.Guild.GetRole(sar.RoleId)?.Name ?? (sar.RoleId + " (deleted)"));
rolesStr.Append("- " + Format.Code(roleName));
if (sar.LevelReq > 0)
{
rolesStr.Append($" *[lvl {sar.LevelReq}+]*");
}
rolesStr.AppendLine();
}
eb.AddField(groupNameText, rolesStr, false);
}
return _sender.CreateEmbed()
.WithOkColor()
.WithTitle(Format.Bold(GetText(strs.self_assign_list(roles.Count()))))
.WithDescription(rolesStr.ToString())
.WithFooter(exclusive
? GetText(strs.self_assign_are_exclusive)
: GetText(strs.self_assign_are_not_exclusive));
return eb;
})
.SendAsync();
}
@@ -152,9 +307,9 @@ public partial class Administration
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task Togglexclsar()
public async Task SarExclusive(int groupNumber)
{
var areExclusive = _service.ToggleEsar(ctx.Guild.Id);
var areExclusive = await _service.SetGroupExclusivityAsync(ctx.Guild.Id, groupNumber);
if (areExclusive)
await Response().Confirm(strs.self_assign_excl).SendAsync();
else
@@ -165,12 +320,12 @@ public partial class Administration
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task RoleLevelReq(int level, [Leftover] IRole role)
public async Task SarRoleLevelReq(int level, [Leftover] IRole role)
{
if (level < 0)
return;
var succ = _service.SetLevelReq(ctx.Guild.Id, role, level);
var succ = await _service.SetRoleLevelReq(ctx.Guild.Id, role.Id, level);
if (!succ)
{
@@ -186,54 +341,35 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task Iam([Leftover] IRole role)
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task SarGroupRoleReq(int groupNumber, [Leftover] IRole role)
{
var guildUser = (IGuildUser)ctx.User;
var succ = await _service.SetGroupRoleReq(ctx.Guild.Id, groupNumber, role.Id);
var (result, autoDelete, extra) = await _service.Assign(guildUser, role);
IUserMessage msg;
if (result == SelfAssignedRolesService.AssignResult.ErrNotAssignable)
msg = await Response().Error(strs.self_assign_not).SendAsync();
else if (result == SelfAssignedRolesService.AssignResult.ErrLvlReq)
msg = await Response().Error(strs.self_assign_not_level(Format.Bold(extra.ToString()))).SendAsync();
else if (result == SelfAssignedRolesService.AssignResult.ErrAlreadyHave)
msg = await Response().Error(strs.self_assign_already(Format.Bold(role.Name))).SendAsync();
else if (result == SelfAssignedRolesService.AssignResult.ErrNotPerms)
msg = await Response().Error(strs.self_assign_perms).SendAsync();
else
msg = await Response().Confirm(strs.self_assign_success(Format.Bold(role.Name))).SendAsync();
if (autoDelete)
if (!succ)
{
msg.DeleteAfter(3);
ctx.Message.DeleteAfter(3);
await Response().Error(strs.sar_group_not_found).SendAsync();
return;
}
await Response()
.Confirm(strs.self_assign_group_role_req(
Format.Bold(groupNumber.ToString()),
Format.Bold(role.Name)))
.SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task Iamnot([Leftover] IRole role)
[UserPerm(GuildPerm.ManageRoles)]
public async Task SarGroupDelete(int groupNumber)
{
var guildUser = (IGuildUser)ctx.User;
var (result, autoDelete) = await _service.Remove(guildUser, role);
IUserMessage msg;
if (result == SelfAssignedRolesService.RemoveResult.ErrNotAssignable)
msg = await Response().Error(strs.self_assign_not).SendAsync();
else if (result == SelfAssignedRolesService.RemoveResult.ErrNotHave)
msg = await Response().Error(strs.self_assign_not_have(Format.Bold(role.Name))).SendAsync();
else if (result == SelfAssignedRolesService.RemoveResult.ErrNotPerms)
msg = await Response().Error(strs.self_assign_perms).SendAsync();
var succ = await _service.DeleteRoleGroup(ctx.Guild.Id, groupNumber);
if (succ)
await Response().Confirm(strs.sar_group_deleted(Format.Bold(groupNumber.ToString()))).SendAsync();
else
msg = await Response().Confirm(strs.self_assign_remove(Format.Bold(role.Name))).SendAsync();
if (autoDelete)
{
msg.DeleteAfter(3);
ctx.Message.DeleteAfter(3);
}
await Response().Error(strs.sar_group_not_found).SendAsync();
}
}
}

View File

@@ -1,233 +1,327 @@
#nullable disable
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Db.Models;
using NadekoBot.Modules.Xp.Services;
using OneOf;
using OneOf.Types;
using System.ComponentModel.DataAnnotations;
using System.Threading.Channels;
namespace NadekoBot.Modules.Administration.Services;
public class SelfAssignedRolesService : INService
public class SelfAssignedRolesService : INService, IReadyExecutor
{
public enum AssignResult
private readonly DbService _db;
private readonly DiscordSocketClient _client;
private readonly IBotCreds _creds;
private ConcurrentHashSet<ulong> _sarAds = new();
public SelfAssignedRolesService(DbService db, DiscordSocketClient client, IBotCreds creds)
{
Assigned, // successfully removed
ErrNotAssignable, // not assignable (error)
ErrAlreadyHave, // you already have that role (error)
ErrNotPerms, // bot doesn't have perms (error)
ErrLvlReq // you are not required level (error)
_db = db;
_client = client;
_creds = creds;
}
public enum RemoveResult
public async Task AddAsync(ulong guildId, ulong roleId, int groupNumber)
{
Removed, // successfully removed
ErrNotAssignable, // not assignable (error)
ErrNotHave, // you don't have a role you want to remove (error)
ErrNotPerms // bot doesn't have perms (error)
await using var ctx = _db.GetDbContext();
await ctx.GetTable<SarGroup>()
.InsertOrUpdateAsync(() => new()
{
GuildId = guildId,
GroupNumber = groupNumber,
IsExclusive = false
},
_ => new()
{
},
() => new()
{
GuildId = guildId,
GroupNumber = groupNumber
});
await ctx.GetTable<Sar>()
.InsertOrUpdateAsync(() => new()
{
RoleId = roleId,
LevelReq = 0,
GuildId = guildId,
SarGroupId = ctx.GetTable<SarGroup>()
.Where(x => x.GuildId == guildId && x.GroupNumber == groupNumber)
.Select(x => x.Id)
.First()
},
_ => new()
{
},
() => new()
{
RoleId = roleId,
});
}
public async Task<bool> RemoveAsync(ulong guildId, ulong roleId)
{
await using var ctx = _db.GetDbContext();
var deleted = await ctx.GetTable<Sar>()
.Where(x => x.RoleId == roleId && x.GuildId == guildId)
.DeleteAsync();
return deleted > 0;
}
public async Task<bool> SetGroupNameAsync(ulong guildId, int groupNumber, string? name)
{
await using var ctx = _db.GetDbContext();
var changes = await ctx.GetTable<SarGroup>()
.Where(x => x.GuildId == guildId && x.GroupNumber == groupNumber)
.UpdateAsync(x => new()
{
Name = name
});
return changes > 0;
}
public async Task<IReadOnlyCollection<SarGroup>> GetSarsAsync(ulong guildId)
{
await using var ctx = _db.GetDbContext();
var sgs = await ctx.GetTable<SarGroup>()
.Where(x => x.GuildId == guildId)
.LoadWith(x => x.Roles)
.ToListAsyncLinqToDB();
return sgs;
}
public async Task<bool> SetRoleLevelReq(ulong guildId, ulong roleId, int levelReq)
{
await using var ctx = _db.GetDbContext();
var changes = await ctx.GetTable<Sar>()
.Where(x => x.GuildId == guildId && x.RoleId == roleId)
.UpdateAsync(_ => new()
{
LevelReq = levelReq,
});
return changes > 0;
}
public async Task<bool> SetGroupRoleReq(ulong guildId, int groupNumber, ulong? roleId)
{
await using var ctx = _db.GetDbContext();
var changes = await ctx.GetTable<SarGroup>()
.Where(x => x.GuildId == guildId && x.GroupNumber == groupNumber)
.UpdateAsync(_ => new()
{
RoleReq = roleId
});
return changes > 0;
}
public async Task<bool> SetGroupExclusivityAsync(ulong guildId, int groupNumber)
{
await using var ctx = _db.GetDbContext();
var changes = await ctx.GetTable<SarGroup>()
.Where(x => x.GuildId == guildId && x.GroupNumber == groupNumber)
.UpdateWithOutputAsync(old => new()
{
IsExclusive = !old.IsExclusive
},
(o, n) => n.IsExclusive);
if (changes.Length == 0)
{
// todo group not found
return false;
}
return changes[0];
}
public async Task<SarGroup?> GetRoleGroup(ulong guildId, ulong roleId)
{
await using var ctx = _db.GetDbContext();
var group = await ctx.GetTable<SarGroup>()
.Where(x => x.Roles.Any(x => x.RoleId == roleId))
.LoadWith(x => x.Roles)
.FirstOrDefaultAsyncLinqToDB();
return group;
}
public async Task<bool> DeleteRoleGroup(ulong guildId, int groupNumber)
{
await using var ctx = _db.GetDbContext();
var deleted = await ctx.GetTable<SarGroup>()
.Where(x => x.GuildId == guildId && x.GroupNumber == groupNumber)
.DeleteAsync();
return deleted > 0;
}
public async Task<bool> ToggleAutoDelete(ulong guildId)
{
await using var ctx = _db.GetDbContext();
var delted = await ctx.GetTable<SarAutoDelete>()
.DeleteAsync(x => x.GuildId == guildId);
if (delted > 0)
{
_sarAds.TryRemove(guildId);
return false;
}
await ctx.GetTable<SarAutoDelete>()
.InsertOrUpdateAsync(() => new()
{
IsEnabled = true,
GuildId = guildId,
},
(_) => new()
{
IsEnabled = true
},
() => new()
{
GuildId = guildId
});
_sarAds.Add(guildId);
return true;
}
public bool GetAutoDelete(ulong guildId)
=> _sarAds.Contains(guildId);
public async Task OnReadyAsync()
{
await using var uow = _db.GetDbContext();
var guilds = await uow.GetTable<SarAutoDelete>()
.Where(x => x.IsEnabled && Linq2DbExpressions.GuildOnShard(x.GuildId, _creds.TotalShards, _client.ShardId))
.Select(x => x.GuildId)
.ToListAsyncLinqToDB();
_sarAds = new(guilds);
}
}
public sealed class SarAssignerService : INService, IReadyExecutor
{
private readonly XpService _xp;
private readonly DbService _db;
public SelfAssignedRolesService(DbService db)
=> _db = db;
private readonly Channel<SarAssignerDataItem> _channel =
Channel.CreateBounded<SarAssignerDataItem>(100);
public bool AddNew(ulong guildId, IRole role, int group)
public SarAssignerService(XpService xp, DbService db)
{
using var uow = _db.GetDbContext();
var roles = uow.Set<SelfAssignedRole>().GetFromGuild(guildId);
if (roles.Any(s => s.RoleId == role.Id && s.GuildId == role.Guild.Id))
return false;
uow.Set<SelfAssignedRole>().Add(new()
{
Group = group,
RoleId = role.Id,
GuildId = role.Guild.Id
});
uow.SaveChanges();
return true;
_xp = xp;
_db = db;
}
public bool ToggleAdSarm(ulong guildId)
public async Task OnReadyAsync()
{
bool newval;
using var uow = _db.GetDbContext();
var config = uow.GuildConfigsForId(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;
await using (var uow = _db.GetDbContext())
var reader = _channel.Reader;
while (true)
{
var stats = uow.GetOrCreateUserXpStats(guildUser.Guild.Id, guildUser.Id);
userLevelData = new(stats.Xp + stats.AwardedXp);
}
var item = await reader.ReadAsync();
var (autoDelete, exclusive, roles) = GetAdAndRoles(guildUser.Guild.Id);
var theRoleYouWant = roles.FirstOrDefault(r => r.RoleId == role.Id);
if (theRoleYouWant is null)
return (AssignResult.ErrNotAssignable, autoDelete, null);
if (theRoleYouWant.LevelRequirement > userLevelData.Level)
return (AssignResult.ErrLvlReq, autoDelete, theRoleYouWant.LevelRequirement);
if (guildUser.RoleIds.Contains(role.Id))
return (AssignResult.ErrAlreadyHave, 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)
try
{
var sameRole = guildUser.Guild.GetRole(roleId);
if (sameRole is not null)
var sar = item.Group.Roles.First(x => x.RoleId == item.RoleId);
if (item.User.RoleIds.Contains(item.RoleId))
{
try
{
await guildUser.RemoveRoleAsync(sameRole);
await Task.Delay(300);
}
catch
{
// ignored
}
item.CompletionTask.TrySetResult(new SarAlreadyHasRole());
continue;
}
if (item.Group.RoleReq is { } rid)
{
if (!item.User.RoleIds.Contains(rid))
{
item.CompletionTask.TrySetResult(new SarRoleRequirement(rid));
continue;
}
// passed
}
// check level requirement
if (sar.LevelReq > 0)
{
await using var ctx = _db.GetDbContext();
var xpStats = await ctx.GetTable<UserXpStats>().GetGuildUserXp(sar.GuildId, item.User.Id);
var lvlData = new LevelStats(xpStats?.Xp ?? 0);
if (lvlData.Level < sar.LevelReq)
{
item.CompletionTask.TrySetResult(new SarLevelRequirement(sar.LevelReq));
continue;
}
// passed
}
if (item.Group.IsExclusive)
{
var rolesToRemove = item.Group.Roles.Select(x => x.RoleId);
await item.User.RemoveRolesAsync(rolesToRemove);
}
await item.User.AddRoleAsync(item.RoleId);
item.CompletionTask.TrySetResult(new Success());
}
catch (Exception ex)
{
Log.Error(ex, "Unknown error ocurred in SAR runner: {Error}", ex.Message);
item.CompletionTask.TrySetResult(new Error());
}
}
try
{
await guildUser.AddRoleAsync(role);
}
catch (Exception ex)
{
return (AssignResult.ErrNotPerms, autoDelete, ex);
}
return (AssignResult.Assigned, autoDelete, null);
}
public async Task<bool> SetNameAsync(ulong guildId, int group, string name)
public async Task Add(SarAssignerDataItem item)
{
var set = false;
await using var uow = _db.GetDbContext();
var gc = uow.GuildConfigsForId(guildId, y => y.Include(x => x.SelfAssignableRoleGroupNames));
var toUpdate = gc.SelfAssignableRoleGroupNames.FirstOrDefault(x => x.Number == group);
if (string.IsNullOrWhiteSpace(name))
{
if (toUpdate is not null)
gc.SelfAssignableRoleGroupNames.Remove(toUpdate);
}
else if (toUpdate is null)
{
gc.SelfAssignableRoleGroupNames.Add(new()
{
Name = name,
Number = group
});
set = true;
}
else
{
toUpdate.Name = name;
set = true;
}
await uow.SaveChangesAsync();
return set;
await _channel.Writer.WriteAsync(item);
}
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) is null)
return (RemoveResult.ErrNotAssignable, autoDelete);
if (!guildUser.RoleIds.Contains(role.Id))
return (RemoveResult.ErrNotHave, autoDelete);
try
{
await guildUser.RemoveRoleAsync(role);
}
catch (Exception)
{
return (RemoveResult.ErrNotPerms, autoDelete);
}
public sealed class SarAssignerDataItem
{
public required SarGroup Group { get; init; }
public required IGuildUser User { get; init; }
public required ulong RoleId { get; init; }
public required TaskCompletionSource<SarAssignResult> CompletionTask { get; init; }
}
return (RemoveResult.Removed, autoDelete);
}
[GenerateOneOf]
public sealed partial class SarAssignResult
: OneOfBase<Success, Error, SarLevelRequirement, SarRoleRequirement, SarAlreadyHasRole, SarInsuffPerms>
{
}
public bool RemoveSar(ulong guildId, ulong roleId)
{
bool success;
using var uow = _db.GetDbContext();
success = uow.Set<SelfAssignedRole>().DeleteByGuildAndRoleId(guildId, roleId);
uow.SaveChanges();
return success;
}
public record class SarLevelRequirement(int Level);
public (bool AutoDelete, bool Exclusive, IReadOnlyCollection<SelfAssignedRole>) GetAdAndRoles(ulong guildId)
{
using var uow = _db.GetDbContext();
var gc = uow.GuildConfigsForId(guildId, set => set);
var autoDelete = gc.AutoDeleteSelfAssignedRoleMessages;
var exclusive = gc.ExclusiveSelfAssignedRoles;
var roles = uow.Set<SelfAssignedRole>().GetFromGuild(guildId);
public record class SarRoleRequirement(ulong RoleId);
return (autoDelete, exclusive, roles);
}
public record class SarAlreadyHasRole();
public bool SetLevelReq(ulong guildId, IRole role, int level)
{
using var uow = _db.GetDbContext();
var roles = uow.Set<SelfAssignedRole>().GetFromGuild(guildId);
var sar = roles.FirstOrDefault(x => x.RoleId == role.Id);
if (sar is not 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.GuildConfigsForId(guildId, set => set);
areExclusive = config.ExclusiveSelfAssignedRoles = !config.ExclusiveSelfAssignedRoles;
uow.SaveChanges();
return areExclusive;
}
public (bool Exclusive, IReadOnlyCollection<(SelfAssignedRole Model, IRole Role)> Roles, IDictionary<int, string>
GroupNames
) GetRoles(IGuild guild)
{
var exclusive = false;
IReadOnlyCollection<(SelfAssignedRole Model, IRole Role)> roles;
IDictionary<int, string> groupNames;
using (var uow = _db.GetDbContext())
{
var gc = uow.GuildConfigsForId(guild.Id, set => set.Include(x => x.SelfAssignableRoleGroupNames));
exclusive = gc.ExclusiveSelfAssignedRoles;
groupNames = gc.SelfAssignableRoleGroupNames.ToDictionary(x => x.Number, x => x.Name);
var roleModels = uow.Set<SelfAssignedRole>().GetFromGuild(guild.Id);
roles = roleModels.Select(x => (Model: x, Role: guild.GetRole(x.RoleId)))
.ToList();
uow.Set<SelfAssignedRole>().RemoveRange(roles.Where(x => x.Role is null).Select(x => x.Model).ToArray());
uow.SaveChanges();
}
return (exclusive, roles.Where(x => x.Role is not null).ToList(), groupNames);
}
}
public record class SarInsuffPerms();

View File

@@ -23,27 +23,6 @@ public partial class Administration
_mute = mute;
}
private async Task<bool> CheckRoleHierarchy(IGuildUser target)
{
var curUser = ((SocketGuild)ctx.Guild).CurrentUser;
var ownerId = ctx.Guild.OwnerId;
var modMaxRole = ((IGuildUser)ctx.User).GetRoles().Max(r => r.Position);
var targetMaxRole = target.GetRoles().Max(r => r.Position);
var botMaxRole = curUser.GetRoles().Max(r => r.Position);
// bot can't punish a user who is higher in the hierarchy. Discord will return 403
// moderator can be owner, in which case role hierarchy doesn't matter
// otherwise, moderator has to have a higher role
if (botMaxRole <= targetMaxRole
|| (ctx.User.Id != ownerId && targetMaxRole >= modMaxRole)
|| target.Id == ownerId)
{
await Response().Error(strs.hierarchy).SendAsync();
return false;
}
return true;
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]

View File

@@ -6,9 +6,9 @@ namespace NadekoBot.Modules.Gambling.Common.AnimalRacing;
public sealed class AnimalRace : IDisposable
{
public const double BASE_MULTIPLIER = 0.82;
public const double BASE_MULTIPLIER = 0.87;
public const double MAX_MULTIPLIER = 0.94;
public const double MULTI_PER_USER = 0.01;
public const double MULTI_PER_USER = 0.005;
public enum Phase
{

View File

@@ -154,16 +154,16 @@ public partial class Gambling : GamblingModule<GamblingService>
{
var password = _service.GeneratePassword();
var img = new Image<Rgba32>(70, 35);
var img = new Image<Rgba32>(60, 30);
var font = _fonts.NotoSans.CreateFont(30);
var font = _fonts.NotoSans.CreateFont(25);
var outlinePen = new SolidPen(Color.Black, 1f);
var strikeoutRun = new RichTextRun
{
Start = 0,
End = password.GetGraphemeCount(),
Font = font,
StrikeoutPen = new SolidPen(Color.White, 3),
StrikeoutPen = new SolidPen(Color.White, 4),
TextDecorations = TextDecorations.Strikeout
};
// draw password on the image

View File

@@ -0,0 +1,92 @@
// namespace NadekoBot.Modules.Gambling;
// public sealed class Loan
// {
// public int Id { get; set; }
// public ulong LenderId { get; set; }
// public string LenderName { get; set; }
// public ulong BorrowerId { get; set; }
// public string BorrowerName { get; set; }
// public long Amount { get; set; }
// public decimal Interest { get; set; }
// public DateTime DueDate { get; set; }
// public bool Repaid { get; set; }
// }
//
// public sealed class LoanService : INService
// {
// public async Task<IReadOnlyList<Loan>> GetLoans(ulong userId)
// {
// }
//
// public async Task<object> RepayAsync(object loandId)
// {
// }
// }
//
// public partial class Gambling
// {
// public partial class LoanCommands : NadekoModule<LoanService>
// {
// [Cmd]
// public async Task Loan(
// IUser lender,
// long amount,
// decimal interest = 0,
// TimeSpan dueIn = default)
// {
// var eb = _sender.CreateEmbed()
// .WithOkColor()
// .WithDescription("User 0 Requests a loan from User {1}")
// .AddField("Amount", amount, true)
// .AddField("Interest", (interest * 0.01m).ToString("P2"), true);
// }
//
// public Task Loans()
// => Loans(ctx.User);
//
// public async Task Loans([Leftover] IUser user)
// {
// var loans = await _service.GetLoans(user.Id);
//
// Response()
// .Paginated()
// .PageItems(loans)
// .Page((items, page) =>
// {
// var eb = _sender.CreateEmbed()
// .WithOkColor()
// .WithDescription("Current Loans");
//
// foreach (var item in items)
// {
// eb.AddField(new kwum(item.id).ToString(),
// $"""
// To: {item.LenderName}
// Amount: {}
// """,
// true);
// }
//
// return eb;
// });
// }
//
// [Cmd]
// public async Task Repay(kwum loanId)
// {
// var res = await _service.RepayAsync(loandId);
//
// if (res.TryPickT0(out var _, out var err))
// {
// }
// else
// {
// var errStr = err.Match(
// _ => "Not enough funds",
// _ => "Loan not found");
//
// await Response().Error(errStr).SendAsync();
// }
// }
// }
// }

View File

@@ -5,6 +5,47 @@ using JsonSerializer = System.Text.Json.JsonSerializer;
namespace NadekoBot.Modules.Searches.Common.StreamNotifications.Providers;
public sealed class YoutubeProvide : Provider
{
private readonly IGoogleApiService _api;
private readonly IHttpClientFactory _httpFactory;
public override FollowedStream.FType Platform
=> FollowedStream.FType.Youtube;
public YoutubeProvide(IGoogleApiService api, IHttpClientFactory httpFactory)
{
_api = api;
_httpFactory = httpFactory;
}
public override async Task<bool> IsValidUrl(string url)
{
await Task.Yield();
// todo implement
return url.Contains("youtube.com");
}
public override Task<StreamData?> GetStreamDataByUrlAsync(string url)
{
return default;
}
public override Task<StreamData?> GetStreamDataAsync(string login)
{
var client = _httpFactory.CreateClient();
client.GetAsync()
return default;
}
public override Task<IReadOnlyCollection<StreamData>> GetStreamDataAsync(List<string> usernames)
{
return default;
}
}
public sealed class TwitchHelixProvider : Provider
{
private readonly IHttpClientFactory _httpClientFactory;