implement admin commands

This commit is contained in:
dusk 2025-10-04 01:57:48 +00:00
parent 5198f7d83b
commit a268f75d32
No known key found for this signature in database
15 changed files with 263 additions and 287 deletions

View file

@ -1,5 +1,3 @@
using System.Text.RegularExpressions;
using Humanizer;
using Dapper;
using SqlKata;
@ -113,18 +111,10 @@ public class Admin
return eb.Build();
}
public async Task UpdateSystemId(Context ctx)
public async Task UpdateSystemId(Context ctx, PKSystem target, string newHid)
{
ctx.AssertBotAdmin();
var target = await ctx.MatchSystem();
if (target == null)
throw new PKError("Unknown system.");
var input = ctx.PopArgument();
if (!input.TryParseHid(out var newHid))
throw new PKError($"Invalid new system ID `{input}`.");
var existingSystem = await ctx.Repository.GetSystemByHid(newHid);
if (existingSystem != null)
throw new PKError($"Another system already exists with ID `{newHid}`.");
@ -138,18 +128,10 @@ public class Admin
await ctx.Reply($"{Emojis.Success} System ID updated (`{target.Hid}` -> `{newHid}`).");
}
public async Task UpdateMemberId(Context ctx)
public async Task UpdateMemberId(Context ctx, PKMember target, string newHid)
{
ctx.AssertBotAdmin();
var target = await ctx.MatchMember();
if (target == null)
throw new PKError("Unknown member.");
var input = ctx.PopArgument();
if (!input.TryParseHid(out var newHid))
throw new PKError($"Invalid new member ID `{input}`.");
var existingMember = await ctx.Repository.GetMemberByHid(newHid);
if (existingMember != null)
throw new PKError($"Another member already exists with ID `{newHid}`.");
@ -167,18 +149,10 @@ public class Admin
await ctx.Reply($"{Emojis.Success} Member ID updated (`{target.Hid}` -> `{newHid}`).");
}
public async Task UpdateGroupId(Context ctx)
public async Task UpdateGroupId(Context ctx, PKGroup target, string newHid)
{
ctx.AssertBotAdmin();
var target = await ctx.MatchGroup();
if (target == null)
throw new PKError("Unknown group.");
var input = ctx.PopArgument();
if (!input.TryParseHid(out var newHid))
throw new PKError($"Invalid new group ID `{input}`.");
var existingGroup = await ctx.Repository.GetGroupByHid(newHid);
if (existingGroup != null)
throw new PKError($"Another group already exists with ID `{newHid}`.");
@ -195,14 +169,10 @@ public class Admin
await ctx.Reply($"{Emojis.Success} Group ID updated (`{target.Hid}` -> `{newHid}`).");
}
public async Task RerollSystemId(Context ctx)
public async Task RerollSystemId(Context ctx, PKSystem target)
{
ctx.AssertBotAdmin();
var target = await ctx.MatchSystem();
if (target == null)
throw new PKError("Unknown system.");
await ctx.Reply(null, await CreateEmbed(ctx, target));
if (!await ctx.PromptYesNo($"Reroll system ID `{target.Hid}`?", "Reroll"))
@ -218,14 +188,10 @@ public class Admin
await ctx.Reply($"{Emojis.Success} System ID updated (`{target.Hid}` -> `{newHid}`).");
}
public async Task RerollMemberId(Context ctx)
public async Task RerollMemberId(Context ctx, PKMember target)
{
ctx.AssertBotAdmin();
var target = await ctx.MatchMember();
if (target == null)
throw new PKError("Unknown member.");
var system = await ctx.Repository.GetSystem(target.System);
await ctx.Reply(null, await CreateEmbed(ctx, system));
@ -245,14 +211,10 @@ public class Admin
await ctx.Reply($"{Emojis.Success} Member ID updated (`{target.Hid}` -> `{newHid}`).");
}
public async Task RerollGroupId(Context ctx)
public async Task RerollGroupId(Context ctx, PKGroup target)
{
ctx.AssertBotAdmin();
var target = await ctx.MatchGroup();
if (target == null)
throw new PKError("Unknown group.");
var system = await ctx.Repository.GetSystem(target.System);
await ctx.Reply(null, await CreateEmbed(ctx, system));
@ -271,27 +233,19 @@ public class Admin
await ctx.Reply($"{Emojis.Success} Group ID updated (`{target.Hid}` -> `{newHid}`).");
}
public async Task SystemMemberLimit(Context ctx)
public async Task SystemMemberLimit(Context ctx, PKSystem target, int? newLimit)
{
ctx.AssertBotAdmin();
var target = await ctx.MatchSystem();
if (target == null)
throw new PKError("Unknown system.");
var config = await ctx.Repository.GetSystemConfig(target.Id);
var currentLimit = config.MemberLimitOverride ?? Limits.MaxMemberCount;
if (!ctx.HasNext())
if (newLimit == null)
{
await ctx.Reply(null, await CreateEmbed(ctx, target));
return;
}
var newLimitStr = ctx.PopArgument().ToLower().Replace(",", null).Replace("k", "000");
if (!int.TryParse(newLimitStr, out var newLimit))
throw new PKError($"Couldn't parse `{newLimitStr}` as number.");
await ctx.Reply(null, await CreateEmbed(ctx, target));
if (!await ctx.PromptYesNo($"Update member limit from **{currentLimit}** to **{newLimit}**?", "Update"))
throw new PKError("Member limit change cancelled.");
@ -300,27 +254,19 @@ public class Admin
await ctx.Reply($"{Emojis.Success} Member limit updated.");
}
public async Task SystemGroupLimit(Context ctx)
public async Task SystemGroupLimit(Context ctx, PKSystem target, int? newLimit)
{
ctx.AssertBotAdmin();
var target = await ctx.MatchSystem();
if (target == null)
throw new PKError("Unknown system.");
var config = await ctx.Repository.GetSystemConfig(target.Id);
var currentLimit = config.GroupLimitOverride ?? Limits.MaxGroupCount;
if (!ctx.HasNext())
if (newLimit == null)
{
await ctx.Reply(null, await CreateEmbed(ctx, target));
return;
}
var newLimitStr = ctx.PopArgument().ToLower().Replace(",", null).Replace("k", "000");
if (!int.TryParse(newLimitStr, out var newLimit))
throw new PKError($"Couldn't parse `{newLimitStr}` as number.");
await ctx.Reply(null, await CreateEmbed(ctx, target));
if (!await ctx.PromptYesNo($"Update group limit from **{currentLimit}** to **{newLimit}**?", "Update"))
throw new PKError("Group limit change cancelled.");
@ -329,13 +275,10 @@ public class Admin
await ctx.Reply($"{Emojis.Success} Group limit updated.");
}
public async Task SystemRecover(Context ctx)
public async Task SystemRecover(Context ctx, string systemToken, User account, bool rerollToken)
{
ctx.AssertBotAdmin();
var rerollToken = ctx.MatchFlag("rt", "reroll-token");
var systemToken = ctx.PopArgument();
var systemId = await ctx.Database.Execute(conn => conn.QuerySingleOrDefaultAsync<SystemId?>(
"select id from systems where token = @token",
new { token = systemToken }
@ -344,10 +287,6 @@ public class Admin
if (systemId == null)
throw new PKError("Could not retrieve a system with that token.");
var account = await ctx.MatchUser();
if (account == null)
throw new PKError("You must pass an account to associate the system with (either ID or @mention).");
var existingAccount = await ctx.Repository.GetSystemByAccount(account.Id);
if (existingAccount != null)
throw Errors.AccountInOtherSystem(existingAccount, ctx.Config, ctx.DefaultPrefix);
@ -378,14 +317,10 @@ public class Admin
});
}
public async Task SystemDelete(Context ctx)
public async Task SystemDelete(Context ctx, PKSystem target)
{
ctx.AssertBotAdmin();
var target = await ctx.MatchSystem();
if (target == null)
throw new PKError("Unknown system.");
await ctx.Reply($"To delete the following system, reply with the system's UUID: `{target.Uuid.ToString()}`",
await CreateEmbed(ctx, target));
if (!await ctx.ConfirmWithReply(target.Uuid.ToString()))
@ -396,18 +331,11 @@ public class Admin
await ctx.Reply($"{Emojis.Success} System deletion succesful.");
}
public async Task AbuseLogCreate(Context ctx)
public async Task AbuseLogCreate(Context ctx, User account, bool denyBotUsage, string? description)
{
var denyBotUsage = ctx.MatchFlag("deny", "deny-bot-usage");
var account = await ctx.MatchUser();
if (account == null)
throw new PKError("You must pass an account to associate the abuse log with (either ID or @mention).");
ctx.AssertBotAdmin();
string? desc = null!;
if (ctx.HasNext(false))
desc = ctx.RemainderOrNull(false).NormalizeLineEndSpacing();
var abuseLog = await ctx.Repository.CreateAbuseLog(desc, denyBotUsage);
var abuseLog = await ctx.Repository.CreateAbuseLog(description, denyBotUsage);
await ctx.Repository.AddAbuseLogAccount(abuseLog.Id, account.Id);
await ctx.Reply(
@ -415,14 +343,49 @@ public class Admin
await CreateAbuseLogEmbed(ctx, abuseLog));
}
public async Task AbuseLogShow(Context ctx, AbuseLog abuseLog)
public async Task<AbuseLog?> GetAbuseLog(Context ctx, User? account, string? id)
{
ctx.AssertBotAdmin();
AbuseLog? abuseLog = null!;
if (account != null)
{
abuseLog = await ctx.Repository.GetAbuseLogByAccount(account.Id);
}
else
{
abuseLog = await ctx.Repository.GetAbuseLogByGuid(new Guid(id));
}
if (abuseLog == null)
{
await ctx.Reply($"{Emojis.Error} Could not find an existing abuse log entry for that query.");
return null;
}
return abuseLog;
}
public async Task AbuseLogShow(Context ctx, User? account, string? id)
{
ctx.AssertBotAdmin();
AbuseLog? abuseLog = await GetAbuseLog(ctx, account, id);
if (abuseLog == null)
return;
await ctx.Reply(null, await CreateAbuseLogEmbed(ctx, abuseLog));
}
public async Task AbuseLogFlagDeny(Context ctx, AbuseLog abuseLog)
public async Task AbuseLogFlagDeny(Context ctx, User? account, string? id, bool? value)
{
if (!ctx.HasNext())
ctx.AssertBotAdmin();
AbuseLog? abuseLog = await GetAbuseLog(ctx, account, id);
if (abuseLog == null)
return;
if (value == null)
{
await ctx.Reply(
$"Bot usage is currently {(abuseLog.DenyBotUsage ? "denied" : "allowed")} "
@ -430,27 +393,31 @@ public class Admin
}
else
{
var value = ctx.MatchToggle(true);
if (abuseLog.DenyBotUsage != value)
await ctx.Repository.UpdateAbuseLog(abuseLog.Id, new AbuseLogPatch { DenyBotUsage = value });
await ctx.Repository.UpdateAbuseLog(abuseLog.Id, new AbuseLogPatch { DenyBotUsage = value.Value });
await ctx.Reply(
$"Bot usage is now **{(value ? "denied" : "allowed")}** "
$"Bot usage is now **{(value.Value ? "denied" : "allowed")}** "
+ $"for accounts associated with abuse log `{abuseLog.Uuid}`.");
}
}
public async Task AbuseLogDescription(Context ctx, AbuseLog abuseLog)
public async Task AbuseLogDescription(Context ctx, User? account, string? id, string? description, bool clear)
{
if (ctx.MatchClear() && await ctx.ConfirmClear("this abuse log description"))
ctx.AssertBotAdmin();
AbuseLog? abuseLog = await GetAbuseLog(ctx, account, id);
if (abuseLog == null)
return;
if (clear && await ctx.ConfirmClear("this abuse log description"))
{
await ctx.Repository.UpdateAbuseLog(abuseLog.Id, new AbuseLogPatch { Description = null });
await ctx.Reply($"{Emojis.Success} Abuse log description cleared.");
}
else if (ctx.HasNext())
else if (description != null)
{
var desc = ctx.RemainderOrNull(false).NormalizeLineEndSpacing();
await ctx.Repository.UpdateAbuseLog(abuseLog.Id, new AbuseLogPatch { Description = desc });
await ctx.Repository.UpdateAbuseLog(abuseLog.Id, new AbuseLogPatch { Description = description });
await ctx.Reply($"{Emojis.Success} Abuse log description updated.");
}
else
@ -461,11 +428,13 @@ public class Admin
}
}
public async Task AbuseLogAddUser(Context ctx, AbuseLog abuseLog)
public async Task AbuseLogAddUser(Context ctx, User? accountToFind, string? id, User account)
{
var account = await ctx.MatchUser();
if (account == null)
throw new PKError("You must pass an account to associate the abuse log with (either ID or @mention).");
ctx.AssertBotAdmin();
AbuseLog? abuseLog = await GetAbuseLog(ctx, accountToFind, id);
if (abuseLog == null)
return;
await ctx.Repository.AddAbuseLogAccount(abuseLog.Id, account.Id);
await ctx.Reply(
@ -473,11 +442,13 @@ public class Admin
await CreateAbuseLogEmbed(ctx, abuseLog));
}
public async Task AbuseLogRemoveUser(Context ctx, AbuseLog abuseLog)
public async Task AbuseLogRemoveUser(Context ctx, User? accountToFind, string? id, User account)
{
var account = await ctx.MatchUser();
if (account == null)
throw new PKError("You must pass an account to remove from the abuse log (either ID or @mention).");
ctx.AssertBotAdmin();
AbuseLog? abuseLog = await GetAbuseLog(ctx, accountToFind, id);
if (abuseLog == null)
return;
await ctx.Repository.UpdateAccount(account.Id, new()
{
@ -489,8 +460,14 @@ public class Admin
await CreateAbuseLogEmbed(ctx, abuseLog));
}
public async Task AbuseLogDelete(Context ctx, AbuseLog abuseLog)
public async Task AbuseLogDelete(Context ctx, User? account, string? id)
{
ctx.AssertBotAdmin();
AbuseLog? abuseLog = await GetAbuseLog(ctx, account, id);
if (abuseLog == null)
return;
if (!await ctx.PromptYesNo($"Really delete abuse log entry `{abuseLog.Uuid}`?", "Delete", matchFlag: false))
{
await ctx.Reply($"{Emojis.Error} Deletion cancelled.");
@ -501,17 +478,10 @@ public class Admin
await ctx.Reply($"{Emojis.Success} Successfully deleted abuse log entry.");
}
public async Task SendAdminMessage(Context ctx)
public async Task SendAdminMessage(Context ctx, User account, string content)
{
ctx.AssertBotAdmin();
var account = await ctx.MatchUser();
if (account == null)
throw new PKError("You must pass an account to send an admin message to (either ID or @mention).");
if (!ctx.HasNext())
throw new PKError("You must provide a message to send.");
var content = ctx.RemainderOrNull(false).NormalizeLineEndSpacing();
var messageContent = $"## [Admin Message]\n\n{content}\n\nWe cannot read replies sent to this DM. If you wish to contact the staff team, please join the support server (<https://discord.gg/PczBt78>) or send us an email at <legal@pluralkit.me>.";
try

View file

@ -58,16 +58,14 @@ public class ProxiedMessage
_redisService = redisService;
}
public async Task ReproxyMessage(Context ctx, ulong? messageId)
public async Task ReproxyMessage(Context ctx, ulong? messageId, PKMember target)
{
var (msg, systemId) = await GetMessageToEdit(ctx, messageId, ReproxyTimeout, true);
if (ctx.System.Id != systemId)
throw new PKError("Can't reproxy a message sent by a different system.");
// Get target member ID
var target = await ctx.MatchMember(restrictToSystem: ctx.System.Id);
if (target == null)
if (target == null || target.System != ctx.System.Id)
throw new PKError("Could not find a member to reproxy the message with.");
// Fetch members and get the ProxyMember for `target`