mirror of
https://github.com/PluralKit/PluralKit.git
synced 2026-02-13 17:20:14 +00:00
implement proxied message and permcheck commands
This commit is contained in:
parent
2b304457cc
commit
e4f38c76a9
19 changed files with 233 additions and 155 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -664,6 +664,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"ordermap",
|
"ordermap",
|
||||||
|
"regex",
|
||||||
"smol_str",
|
"smol_str",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ public partial class CommandTree
|
||||||
{
|
{
|
||||||
return command switch
|
return command switch
|
||||||
{
|
{
|
||||||
|
Commands.Explain => ctx.Execute<Help>(Explain, m => m.Explain(ctx)),
|
||||||
Commands.Help(_, var flags) => ctx.Execute<Help>(Help, m => m.HelpRoot(ctx, flags.show_embed)),
|
Commands.Help(_, var flags) => ctx.Execute<Help>(Help, m => m.HelpRoot(ctx, flags.show_embed)),
|
||||||
Commands.HelpCommands => ctx.Reply(
|
Commands.HelpCommands => ctx.Reply(
|
||||||
"For the list of commands, see the website: <https://pluralkit.me/commands>"),
|
"For the list of commands, see the website: <https://pluralkit.me/commands>"),
|
||||||
|
|
@ -236,6 +237,14 @@ public partial class CommandTree
|
||||||
Commands.AutoproxyLatch => ctx.Execute<Autoproxy>(AutoproxySet, m => m.SetAutoproxyMode(ctx, new Autoproxy.Mode.Latch())),
|
Commands.AutoproxyLatch => ctx.Execute<Autoproxy>(AutoproxySet, m => m.SetAutoproxyMode(ctx, new Autoproxy.Mode.Latch())),
|
||||||
Commands.AutoproxyFront => ctx.Execute<Autoproxy>(AutoproxySet, m => m.SetAutoproxyMode(ctx, new Autoproxy.Mode.Front())),
|
Commands.AutoproxyFront => ctx.Execute<Autoproxy>(AutoproxySet, m => m.SetAutoproxyMode(ctx, new Autoproxy.Mode.Front())),
|
||||||
Commands.AutoproxyMember(var param, _) => ctx.Execute<Autoproxy>(AutoproxySet, m => m.SetAutoproxyMode(ctx, new Autoproxy.Mode.Member(param.target))),
|
Commands.AutoproxyMember(var param, _) => ctx.Execute<Autoproxy>(AutoproxySet, m => m.SetAutoproxyMode(ctx, new Autoproxy.Mode.Member(param.target))),
|
||||||
|
Commands.PermcheckChannel(var param, _) => ctx.Execute<Checks>(PermCheck, m => m.PermCheckChannel(ctx, param.target)),
|
||||||
|
Commands.PermcheckGuild(var param, _) => ctx.Execute<Checks>(PermCheck, m => m.PermCheckGuild(ctx, param.target)),
|
||||||
|
Commands.MessageProxyCheck(var param, _) => ctx.Execute<Checks>(ProxyCheck, m => m.MessageProxyCheck(ctx, param.target)),
|
||||||
|
Commands.MessageInfo(var param, var flags) => ctx.Execute<ProxiedMessage>(Message, m => m.GetMessage(ctx, param.target.MessageId, flags.GetReplyFormat(), flags.delete, flags.author)),
|
||||||
|
Commands.MessageAuthor(var param, var flags) => ctx.Execute<ProxiedMessage>(Message, m => m.GetMessage(ctx, param.target.MessageId, flags.GetReplyFormat(), false, true)),
|
||||||
|
Commands.MessageDelete(var param, var flags) => ctx.Execute<ProxiedMessage>(Message, m => m.GetMessage(ctx, param.target.MessageId, flags.GetReplyFormat(), true, false)),
|
||||||
|
Commands.MessageEdit(var param, var flags) => ctx.Execute<ProxiedMessage>(MessageEdit, m => m.EditMessage(ctx, param.target.MessageId, param.new_content, flags.regex, flags.mutate_space, flags.append, flags.prepend, flags.clear_embeds, flags.clear_attachments)),
|
||||||
|
Commands.MessageReproxy(var param, _) => ctx.Execute<ProxiedMessage>(MessageReproxy, m => m.ReproxyMessage(ctx, param.target.MessageId)),
|
||||||
_ =>
|
_ =>
|
||||||
// this should only ever occur when deving if commands are not implemented...
|
// this should only ever occur when deving if commands are not implemented...
|
||||||
ctx.Reply(
|
ctx.Reply(
|
||||||
|
|
@ -251,16 +260,6 @@ public partial class CommandTree
|
||||||
return ctx.Execute<ImportExport>(Import, m => m.Import(ctx));
|
return ctx.Execute<ImportExport>(Import, m => m.Import(ctx));
|
||||||
if (ctx.Match("export"))
|
if (ctx.Match("export"))
|
||||||
return ctx.Execute<ImportExport>(Export, m => m.Export(ctx));
|
return ctx.Execute<ImportExport>(Export, m => m.Export(ctx));
|
||||||
if (ctx.Match("explain"))
|
|
||||||
return ctx.Execute<Help>(Explain, m => m.Explain(ctx));
|
|
||||||
if (ctx.Match("message", "msg", "messageinfo"))
|
|
||||||
return ctx.Execute<ProxiedMessage>(Message, m => m.GetMessage(ctx));
|
|
||||||
if (ctx.Match("edit", "e"))
|
|
||||||
return ctx.Execute<ProxiedMessage>(MessageEdit, m => m.EditMessage(ctx, false));
|
|
||||||
if (ctx.Match("x"))
|
|
||||||
return ctx.Execute<ProxiedMessage>(MessageEdit, m => m.EditMessage(ctx, true));
|
|
||||||
if (ctx.Match("reproxy", "rp", "crimes", "crime"))
|
|
||||||
return ctx.Execute<ProxiedMessage>(MessageReproxy, m => m.ReproxyMessage(ctx));
|
|
||||||
if (ctx.Match("log"))
|
if (ctx.Match("log"))
|
||||||
if (ctx.Match("channel"))
|
if (ctx.Match("channel"))
|
||||||
return ctx.Execute<ServerConfig>(LogChannel, m => m.SetLogChannel(ctx), true);
|
return ctx.Execute<ServerConfig>(LogChannel, m => m.SetLogChannel(ctx), true);
|
||||||
|
|
@ -283,17 +282,8 @@ public partial class CommandTree
|
||||||
return ctx.Execute<ServerConfig>(BlacklistShow, m => m.ShowProxyBlacklisted(ctx), true);
|
return ctx.Execute<ServerConfig>(BlacklistShow, m => m.ShowProxyBlacklisted(ctx), true);
|
||||||
else
|
else
|
||||||
return ctx.Reply($"{Emojis.Warn} Blacklist commands have moved to `{ctx.DefaultPrefix}serverconfig`.");
|
return ctx.Reply($"{Emojis.Warn} Blacklist commands have moved to `{ctx.DefaultPrefix}serverconfig`.");
|
||||||
if (ctx.Match("proxy"))
|
|
||||||
if (ctx.Match("debug"))
|
|
||||||
return ctx.Execute<Checks>(ProxyCheck, m => m.MessageProxyCheck(ctx));
|
|
||||||
if (ctx.Match("invite")) return ctx.Execute<Misc>(Invite, m => m.Invite(ctx));
|
if (ctx.Match("invite")) return ctx.Execute<Misc>(Invite, m => m.Invite(ctx));
|
||||||
if (ctx.Match("stats", "status")) return ctx.Execute<Misc>(null, m => m.Stats(ctx));
|
if (ctx.Match("stats", "status")) return ctx.Execute<Misc>(null, m => m.Stats(ctx));
|
||||||
if (ctx.Match("permcheck"))
|
|
||||||
return ctx.Execute<Checks>(PermCheck, m => m.PermCheckGuild(ctx));
|
|
||||||
if (ctx.Match("proxycheck"))
|
|
||||||
return ctx.Execute<Checks>(ProxyCheck, m => m.MessageProxyCheck(ctx));
|
|
||||||
if (ctx.Match("debug"))
|
|
||||||
return HandleDebugCommand(ctx);
|
|
||||||
if (ctx.Match("admin"))
|
if (ctx.Match("admin"))
|
||||||
return HandleAdminCommand(ctx);
|
return HandleAdminCommand(ctx);
|
||||||
if (ctx.Match("dashboard", "dash"))
|
if (ctx.Match("dashboard", "dash"))
|
||||||
|
|
@ -372,26 +362,6 @@ public partial class CommandTree
|
||||||
await ctx.Reply($"{Emojis.Error} Unknown command.");
|
await ctx.Reply($"{Emojis.Error} Unknown command.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleDebugCommand(Context ctx)
|
|
||||||
{
|
|
||||||
var availableCommandsStr = "Available debug targets: `permissions`, `proxying`";
|
|
||||||
|
|
||||||
if (ctx.Match("permissions", "perms", "permcheck"))
|
|
||||||
if (ctx.Match("channel", "ch"))
|
|
||||||
await ctx.Execute<Checks>(PermCheck, m => m.PermCheckChannel(ctx));
|
|
||||||
else
|
|
||||||
await ctx.Execute<Checks>(PermCheck, m => m.PermCheckGuild(ctx));
|
|
||||||
else if (ctx.Match("channel"))
|
|
||||||
await ctx.Execute<Checks>(PermCheck, m => m.PermCheckChannel(ctx));
|
|
||||||
else if (ctx.Match("proxy", "proxying", "proxycheck"))
|
|
||||||
await ctx.Execute<Checks>(ProxyCheck, m => m.MessageProxyCheck(ctx));
|
|
||||||
else if (!ctx.HasNext())
|
|
||||||
await ctx.Reply($"{Emojis.Error} You need to pass a command. {availableCommandsStr}");
|
|
||||||
else
|
|
||||||
await ctx.Reply(
|
|
||||||
$"{Emojis.Error} Unknown debug command {ctx.PeekArgument().AsCode()}. {availableCommandsStr}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task CommandHelpRoot(Context ctx)
|
private async Task CommandHelpRoot(Context ctx)
|
||||||
{
|
{
|
||||||
if (!ctx.HasNext())
|
if (!ctx.HasNext())
|
||||||
|
|
|
||||||
|
|
@ -106,25 +106,24 @@ public static class ContextArgumentsExt
|
||||||
else return null;
|
else return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static (ulong? messageId, ulong? channelId) MatchMessage(this Context ctx, bool parseRawMessageId)
|
public static (ulong? messageId, ulong? channelId) GetRepliedTo(this Context ctx)
|
||||||
{
|
{
|
||||||
if (ctx.Message.Type == Message.MessageType.Reply && ctx.Message.MessageReference?.MessageId != null)
|
if (ctx.Message.Type == Message.MessageType.Reply && ctx.Message.MessageReference?.MessageId != null)
|
||||||
return (ctx.Message.MessageReference.MessageId, ctx.Message.MessageReference.ChannelId);
|
return (ctx.Message.MessageReference.MessageId, ctx.Message.MessageReference.ChannelId);
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
var word = ctx.PeekArgument();
|
public static (ulong? messageId, ulong? channelId) ParseMessage(this Context ctx, string maybeMessageRef, bool parseRawMessageId)
|
||||||
if (word == null)
|
{
|
||||||
return (null, null);
|
if (parseRawMessageId && ulong.TryParse(maybeMessageRef, out var mid))
|
||||||
|
|
||||||
if (parseRawMessageId && ulong.TryParse(word, out var mid))
|
|
||||||
return (mid, null);
|
return (mid, null);
|
||||||
|
|
||||||
var match = Regex.Match(word, "https://(?:\\w+.)?discord(?:app)?.com/channels/\\d+/(\\d+)/(\\d+)");
|
var match = Regex.Match(maybeMessageRef, "https://(?:\\w+.)?discord(?:app)?.com/channels/\\d+/(\\d+)/(\\d+)");
|
||||||
if (!match.Success)
|
if (!match.Success)
|
||||||
return (null, null);
|
return (null, null);
|
||||||
|
|
||||||
var channelId = ulong.Parse(match.Groups[1].Value);
|
var channelId = ulong.Parse(match.Groups[1].Value);
|
||||||
var messageId = ulong.Parse(match.Groups[2].Value);
|
var messageId = ulong.Parse(match.Groups[2].Value);
|
||||||
ctx.PopArgument();
|
|
||||||
return (messageId, channelId);
|
return (messageId, channelId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -213,24 +213,4 @@ public static class ContextEntityArgumentsExt
|
||||||
ctx.PopArgument();
|
ctx.PopArgument();
|
||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<Guild> ParseGuild(this Context ctx, string input)
|
|
||||||
{
|
|
||||||
if (!ulong.TryParse(input, out var id))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return await ctx.Rest.GetGuildOrNull(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<Guild> MatchGuild(this Context ctx)
|
|
||||||
{
|
|
||||||
if (!ulong.TryParse(ctx.PeekArgument(), out var id))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var guild = await ctx.Rest.GetGuildOrNull(id);
|
|
||||||
if (guild != null)
|
|
||||||
ctx.PopArgument();
|
|
||||||
|
|
||||||
return guild;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -100,6 +100,22 @@ public static class ContextParametersExt
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<Myriad.Types.Message.Reference?> ParamResolveMessage(this Context ctx, string param_name)
|
||||||
|
{
|
||||||
|
return await ctx.Parameters.ResolveParameter(
|
||||||
|
ctx, param_name,
|
||||||
|
param => (param as Parameter.MessageRef)?.message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<Myriad.Types.Channel?> ParamResolveChannel(this Context ctx, string param_name)
|
||||||
|
{
|
||||||
|
return await ctx.Parameters.ResolveParameter(
|
||||||
|
ctx, param_name,
|
||||||
|
param => (param as Parameter.ChannelRef)?.channel
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task<Myriad.Types.Guild?> ParamResolveGuild(this Context ctx, string param_name)
|
public static async Task<Myriad.Types.Guild?> ParamResolveGuild(this Context ctx, string param_name)
|
||||||
{
|
{
|
||||||
return await ctx.Parameters.ResolveParameter(
|
return await ctx.Parameters.ResolveParameter(
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ public abstract record Parameter()
|
||||||
public record GroupRef(PKGroup group): Parameter;
|
public record GroupRef(PKGroup group): Parameter;
|
||||||
public record GroupRefs(List<PKGroup> groups): Parameter;
|
public record GroupRefs(List<PKGroup> groups): Parameter;
|
||||||
public record SystemRef(PKSystem system): Parameter;
|
public record SystemRef(PKSystem system): Parameter;
|
||||||
|
public record MessageRef(Message.Reference message): Parameter;
|
||||||
|
public record ChannelRef(Channel channel): Parameter;
|
||||||
public record GuildRef(Guild guild): Parameter;
|
public record GuildRef(Guild guild): Parameter;
|
||||||
public record MemberPrivacyTarget(MemberPrivacySubject target): Parameter;
|
public record MemberPrivacyTarget(MemberPrivacySubject target): Parameter;
|
||||||
public record GroupPrivacyTarget(GroupPrivacySubject target): Parameter;
|
public record GroupPrivacyTarget(GroupPrivacySubject target): Parameter;
|
||||||
|
|
@ -118,8 +120,12 @@ public class Parameters
|
||||||
return new Parameter.Opaque(opaque.raw);
|
return new Parameter.Opaque(opaque.raw);
|
||||||
case uniffi.commands.Parameter.Avatar avatar:
|
case uniffi.commands.Parameter.Avatar avatar:
|
||||||
return new Parameter.Avatar(await ctx.GetUserPfp(avatar.avatar) ?? ctx.ParseImage(avatar.avatar));
|
return new Parameter.Avatar(await ctx.GetUserPfp(avatar.avatar) ?? ctx.ParseImage(avatar.avatar));
|
||||||
case uniffi.commands.Parameter.GuildRef guildRef:
|
case uniffi.commands.Parameter.MessageRef(var guildId, var channelId, var messageId):
|
||||||
return new Parameter.GuildRef(await ctx.ParseGuild(guildRef.guild) ?? throw new PKError($"Guild {guildRef.guild} not found"));
|
return new Parameter.MessageRef(new Message.Reference(guildId, channelId, messageId));
|
||||||
|
case uniffi.commands.Parameter.ChannelRef(var channelId):
|
||||||
|
return new Parameter.ChannelRef(await ctx.Rest.GetChannelOrNull(channelId) ?? throw new PKError($"Channel {channelId} not found"));
|
||||||
|
case uniffi.commands.Parameter.GuildRef(var guildId):
|
||||||
|
return new Parameter.GuildRef(await ctx.Rest.GetGuildOrNull(guildId) ?? throw new PKError($"Guild {guildId} not found"));
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,10 @@ public class Autoproxy
|
||||||
|
|
||||||
public abstract record Mode()
|
public abstract record Mode()
|
||||||
{
|
{
|
||||||
public record Off() : Mode;
|
public record Off(): Mode;
|
||||||
public record Latch() : Mode;
|
public record Latch(): Mode;
|
||||||
public record Front() : Mode;
|
public record Front(): Mode;
|
||||||
public record Member(PKMember member) : Mode;
|
public record Member(PKMember member): Mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Autoproxy(IClock clock)
|
public Autoproxy(IClock clock)
|
||||||
|
|
|
||||||
|
|
@ -36,37 +36,11 @@ public class Checks
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task PermCheckGuild(Context ctx)
|
public async Task PermCheckGuild(Context ctx, Guild guild)
|
||||||
{
|
{
|
||||||
Guild guild;
|
var senderGuildUser = await _rest.GetGuildMember(guild.Id, ctx.Author.Id);
|
||||||
GuildMemberPartial senderGuildUser = null;
|
if (senderGuildUser == null)
|
||||||
|
throw Errors.GuildNotFound(guild.Id);
|
||||||
if (ctx.Guild != null && !ctx.HasNext())
|
|
||||||
{
|
|
||||||
guild = ctx.Guild;
|
|
||||||
senderGuildUser = ctx.Member;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var guildIdStr = ctx.RemainderOrNull() ??
|
|
||||||
throw new PKSyntaxError("You must pass a server ID or run this command in a server.");
|
|
||||||
if (!ulong.TryParse(guildIdStr, out var guildId))
|
|
||||||
throw new PKSyntaxError($"Could not parse {guildIdStr.AsCode()} as an ID.");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
guild = await _rest.GetGuild(guildId);
|
|
||||||
}
|
|
||||||
catch (ForbiddenException)
|
|
||||||
{
|
|
||||||
throw Errors.GuildNotFound(guildId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (guild != null)
|
|
||||||
senderGuildUser = await _rest.GetGuildMember(guildId, ctx.Author.Id);
|
|
||||||
if (guild == null || senderGuildUser == null)
|
|
||||||
throw Errors.GuildNotFound(guildId);
|
|
||||||
}
|
|
||||||
|
|
||||||
var guildMember = await _rest.GetGuildMember(guild.Id, _botConfig.ClientId);
|
var guildMember = await _rest.GetGuildMember(guild.Id, _botConfig.ClientId);
|
||||||
|
|
||||||
|
|
@ -135,17 +109,13 @@ public class Checks
|
||||||
await ctx.Reply(embed: eb.Build());
|
await ctx.Reply(embed: eb.Build());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task PermCheckChannel(Context ctx)
|
public async Task PermCheckChannel(Context ctx, Channel channel)
|
||||||
{
|
{
|
||||||
if (!ctx.HasNext())
|
|
||||||
throw new PKSyntaxError("You need to specify a channel.");
|
|
||||||
|
|
||||||
var error = "Channel not found or you do not have permissions to access it.";
|
var error = "Channel not found or you do not have permissions to access it.";
|
||||||
|
|
||||||
// todo: this breaks if channel is not in cache and bot does not have View Channel permissions
|
// todo: this breaks if channel is not in cache and bot does not have View Channel permissions
|
||||||
// with new cache it breaks if channel is not in current guild
|
// with new cache it breaks if channel is not in current guild
|
||||||
var channel = await ctx.MatchChannel();
|
if (channel.GuildId == null)
|
||||||
if (channel == null || channel.GuildId == null)
|
|
||||||
throw new PKError(error);
|
throw new PKError(error);
|
||||||
|
|
||||||
var guild = await _rest.GetGuildOrNull(channel.GuildId.Value);
|
var guild = await _rest.GetGuildOrNull(channel.GuildId.Value);
|
||||||
|
|
@ -189,15 +159,17 @@ public class Checks
|
||||||
await ctx.Reply(embed: eb.Build());
|
await ctx.Reply(embed: eb.Build());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task MessageProxyCheck(Context ctx)
|
public async Task MessageProxyCheck(Context ctx, Message.Reference? messageReference)
|
||||||
{
|
{
|
||||||
if (!ctx.HasNext() && ctx.Message.MessageReference == null)
|
if (messageReference == null && ctx.Message.MessageReference == null)
|
||||||
throw new PKSyntaxError("You need to specify a message.");
|
throw new PKSyntaxError("You need to specify a message.");
|
||||||
|
|
||||||
var failedToGetMessage =
|
var failedToGetMessage =
|
||||||
"Could not find a valid message to check, was not able to fetch the message, or the message was not sent by you.";
|
"Could not find a valid message to check, was not able to fetch the message, or the message was not sent by you.";
|
||||||
|
|
||||||
var (messageId, channelId) = ctx.MatchMessage(false);
|
var (messageId, channelId) = ctx.GetRepliedTo();
|
||||||
|
if (messageReference != null)
|
||||||
|
(messageId, channelId) = (messageReference.MessageId, messageReference.ChannelId);
|
||||||
if (messageId == null || channelId == null)
|
if (messageId == null || channelId == null)
|
||||||
throw new PKError(failedToGetMessage);
|
throw new PKError(failedToGetMessage);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,9 +58,9 @@ public class ProxiedMessage
|
||||||
_redisService = redisService;
|
_redisService = redisService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ReproxyMessage(Context ctx)
|
public async Task ReproxyMessage(Context ctx, ulong? messageId)
|
||||||
{
|
{
|
||||||
var (msg, systemId) = await GetMessageToEdit(ctx, ReproxyTimeout, true);
|
var (msg, systemId) = await GetMessageToEdit(ctx, messageId, ReproxyTimeout, true);
|
||||||
|
|
||||||
if (ctx.System.Id != systemId)
|
if (ctx.System.Id != systemId)
|
||||||
throw new PKError("Can't reproxy a message sent by a different system.");
|
throw new PKError("Can't reproxy a message sent by a different system.");
|
||||||
|
|
@ -93,9 +93,9 @@ public class ProxiedMessage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task EditMessage(Context ctx, bool useRegex)
|
public async Task EditMessage(Context ctx, ulong? messageId, string newContent, bool useRegex, bool mutateSpace, bool append, bool prepend, bool clearEmbeds, bool clearAttachments)
|
||||||
{
|
{
|
||||||
var (msg, systemId) = await GetMessageToEdit(ctx, EditTimeout, false);
|
var (msg, systemId) = await GetMessageToEdit(ctx, messageId, EditTimeout, false);
|
||||||
|
|
||||||
if (ctx.System.Id != systemId)
|
if (ctx.System.Id != systemId)
|
||||||
throw new PKError("Can't edit a message sent by a different system.");
|
throw new PKError("Can't edit a message sent by a different system.");
|
||||||
|
|
@ -104,21 +104,10 @@ public class ProxiedMessage
|
||||||
if (originalMsg == null)
|
if (originalMsg == null)
|
||||||
throw new PKError("Could not edit message.");
|
throw new PKError("Could not edit message.");
|
||||||
|
|
||||||
// Regex flag
|
|
||||||
useRegex = useRegex || ctx.MatchFlag("regex", "x");
|
|
||||||
|
|
||||||
// Check if we should append or prepend
|
|
||||||
var mutateSpace = ctx.MatchFlag("nospace", "ns") ? "" : " ";
|
|
||||||
var append = ctx.MatchFlag("append", "a");
|
|
||||||
var prepend = ctx.MatchFlag("prepend", "p");
|
|
||||||
|
|
||||||
// Grab the original message content and new message content
|
// Grab the original message content and new message content
|
||||||
var originalContent = originalMsg.Content;
|
var originalContent = originalMsg.Content;
|
||||||
var newContent = ctx.RemainderOrNull()?.NormalizeLineEndSpacing();
|
|
||||||
|
|
||||||
// Should we clear embeds?
|
// Should we clear embeds?
|
||||||
var clearEmbeds = ctx.MatchFlag("clear-embed", "ce");
|
|
||||||
var clearAttachments = ctx.MatchFlag("clear-attachments", "ca");
|
|
||||||
if ((clearEmbeds || clearAttachments) && newContent == null)
|
if ((clearEmbeds || clearAttachments) && newContent == null)
|
||||||
newContent = originalMsg.Content!;
|
newContent = originalMsg.Content!;
|
||||||
|
|
||||||
|
|
@ -249,14 +238,13 @@ public class ProxiedMessage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<(PKMessage, SystemId)> GetMessageToEdit(Context ctx, Duration timeout, bool isReproxy)
|
private async Task<(PKMessage, SystemId)> GetMessageToEdit(Context ctx, ulong? referencedMessage, Duration timeout, bool isReproxy)
|
||||||
{
|
{
|
||||||
var editType = isReproxy ? "reproxy" : "edit";
|
var editType = isReproxy ? "reproxy" : "edit";
|
||||||
var editTypeAction = isReproxy ? "reproxied" : "edited";
|
var editTypeAction = isReproxy ? "reproxied" : "edited";
|
||||||
|
|
||||||
PKMessage? msg = null;
|
PKMessage? msg = null;
|
||||||
|
|
||||||
var (referencedMessage, _) = ctx.MatchMessage(false);
|
|
||||||
if (referencedMessage != null)
|
if (referencedMessage != null)
|
||||||
{
|
{
|
||||||
await using var conn = await ctx.Database.Obtain();
|
await using var conn = await ctx.Database.Obtain();
|
||||||
|
|
@ -332,9 +320,8 @@ public class ProxiedMessage
|
||||||
return lastMessage;
|
return lastMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task GetMessage(Context ctx)
|
public async Task GetMessage(Context ctx, ulong? messageId, ReplyFormat format, bool isDelete, bool author)
|
||||||
{
|
{
|
||||||
var (messageId, _) = ctx.MatchMessage(true);
|
|
||||||
if (messageId == null)
|
if (messageId == null)
|
||||||
{
|
{
|
||||||
if (!ctx.HasNext())
|
if (!ctx.HasNext())
|
||||||
|
|
@ -342,8 +329,6 @@ public class ProxiedMessage
|
||||||
throw new PKSyntaxError($"Could not parse {ctx.PeekArgument().AsCode()} as a message ID or link.");
|
throw new PKSyntaxError($"Could not parse {ctx.PeekArgument().AsCode()} as a message ID or link.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var isDelete = ctx.Match("delete") || ctx.MatchFlag("delete");
|
|
||||||
|
|
||||||
var message = await ctx.Repository.GetFullMessage(messageId.Value);
|
var message = await ctx.Repository.GetFullMessage(messageId.Value);
|
||||||
if (message == null)
|
if (message == null)
|
||||||
{
|
{
|
||||||
|
|
@ -360,8 +345,6 @@ public class ProxiedMessage
|
||||||
else if (!await ctx.CheckPermissionsInGuildChannel(channel, PermissionSet.ViewChannel))
|
else if (!await ctx.CheckPermissionsInGuildChannel(channel, PermissionSet.ViewChannel))
|
||||||
showContent = false;
|
showContent = false;
|
||||||
|
|
||||||
var format = ctx.MatchFormat();
|
|
||||||
|
|
||||||
if (format != ReplyFormat.Standard)
|
if (format != ReplyFormat.Standard)
|
||||||
{
|
{
|
||||||
var discordMessage = await _rest.GetMessageOrNull(message.Message.Channel, message.Message.Mid);
|
var discordMessage = await _rest.GetMessageOrNull(message.Message.Channel, message.Message.Mid);
|
||||||
|
|
@ -423,7 +406,7 @@ public class ProxiedMessage
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx.Match("author") || ctx.MatchFlag("author"))
|
if (author)
|
||||||
{
|
{
|
||||||
var user = await _rest.GetUser(message.Message.Sender);
|
var user = await _rest.GetUser(message.Message.Sender);
|
||||||
var eb = new EmbedBuilder()
|
var eb = new EmbedBuilder()
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
|
|
||||||
|
|
@ -1 +1,16 @@
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn debug() -> (&'static str, [&'static str; 1]) {
|
||||||
|
("debug", ["dbg"])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cmds() -> impl Iterator<Item = Command> {
|
||||||
|
let debug = debug();
|
||||||
|
let perms = ("permissions", ["perms", "permcheck"]);
|
||||||
|
[
|
||||||
|
command!(debug, perms, ("channel", ["ch"]), ChannelRef => "permcheck_channel"),
|
||||||
|
command!(debug, perms, ("guild", ["g"]), GuildRef => "permcheck_guild"),
|
||||||
|
command!(debug, ("proxy", ["proxying", "proxycheck"]), MessageRef => "message_proxy_check"),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use super::*;
|
||||||
pub fn cmds() -> impl Iterator<Item = Command> {
|
pub fn cmds() -> impl Iterator<Item = Command> {
|
||||||
let help = ("help", ["h"]);
|
let help = ("help", ["h"]);
|
||||||
[
|
[
|
||||||
|
command!("explain" => "explain"),
|
||||||
command!(help => "help")
|
command!(help => "help")
|
||||||
.flag(("foo", OpaqueString)) // todo: just for testing
|
.flag(("foo", OpaqueString)) // todo: just for testing
|
||||||
.help("Shows the help command"),
|
.help("Shows the help command"),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
pub mod admin;
|
pub mod admin;
|
||||||
pub mod api;
|
pub mod api;
|
||||||
pub mod autoproxy;
|
pub mod autoproxy;
|
||||||
pub mod checks;
|
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod dashboard;
|
pub mod dashboard;
|
||||||
|
|
@ -33,6 +32,8 @@ pub fn all() -> impl Iterator<Item = Command> {
|
||||||
.chain(random::cmds())
|
.chain(random::cmds())
|
||||||
.chain(api::cmds())
|
.chain(api::cmds())
|
||||||
.chain(autoproxy::cmds())
|
.chain(autoproxy::cmds())
|
||||||
|
.chain(debug::cmds())
|
||||||
|
.chain(message::cmds())
|
||||||
.map(|cmd| {
|
.map(|cmd| {
|
||||||
cmd.hidden_flag(("plaintext", ["pt"]))
|
cmd.hidden_flag(("plaintext", ["pt"]))
|
||||||
.hidden_flag(("raw", ["r"]))
|
.hidden_flag(("raw", ["r"]))
|
||||||
|
|
|
||||||
|
|
@ -1 +1,32 @@
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn cmds() -> impl Iterator<Item = Command> {
|
||||||
|
let message = tokens!(("message", ["msg", "messageinfo"]), MessageRef);
|
||||||
|
|
||||||
|
let edit = tokens!(("edit", ["e"]), ("new_content", OpaqueStringRemainder));
|
||||||
|
let apply_edit = |cmd: Command| {
|
||||||
|
cmd.flag(("append", ["a"]))
|
||||||
|
.flag(("prepend", ["p"]))
|
||||||
|
.flag(("regex", ["r"]))
|
||||||
|
.flag(("mutate-space", ["ms"]))
|
||||||
|
.flag(("clear-embeds", ["ce"]))
|
||||||
|
.flag(("clear-attachments", ["ca"]))
|
||||||
|
.help("Edits a proxied message")
|
||||||
|
};
|
||||||
|
|
||||||
|
[
|
||||||
|
command!(message => "message_info")
|
||||||
|
.flag(("delete", ["d"]))
|
||||||
|
.flag(("author", ["a"]))
|
||||||
|
.help("Shows information about a proxied message"),
|
||||||
|
command!(message, ("author", ["sender"]) => "message_author")
|
||||||
|
.help("Shows the author of a proxied message"),
|
||||||
|
command!(message, ("delete", ["del"]) => "message_delete")
|
||||||
|
.help("Deletes a proxied message"),
|
||||||
|
apply_edit(command!(message, edit => "message_edit")),
|
||||||
|
apply_edit(command!(edit => "message_edit")),
|
||||||
|
command!(("reproxy", ["rp", "crimes", "crime"]), MessageRef => "message_reproxy")
|
||||||
|
.help("Reproxies a message with a different member"),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,4 @@ edition = "2024"
|
||||||
lazy_static = { workspace = true }
|
lazy_static = { workspace = true }
|
||||||
smol_str = "0.3.2"
|
smol_str = "0.3.2"
|
||||||
ordermap = "0.5"
|
ordermap = "0.5"
|
||||||
|
regex = "1"
|
||||||
|
|
@ -15,7 +15,9 @@ pub enum ParameterValue {
|
||||||
GroupRef(String),
|
GroupRef(String),
|
||||||
GroupRefs(Vec<String>),
|
GroupRefs(Vec<String>),
|
||||||
SystemRef(String),
|
SystemRef(String),
|
||||||
GuildRef(String),
|
MessageRef(Option<u64>, Option<u64>, u64),
|
||||||
|
ChannelRef(u64),
|
||||||
|
GuildRef(u64),
|
||||||
MemberPrivacyTarget(String),
|
MemberPrivacyTarget(String),
|
||||||
GroupPrivacyTarget(String),
|
GroupPrivacyTarget(String),
|
||||||
SystemPrivacyTarget(String),
|
SystemPrivacyTarget(String),
|
||||||
|
|
@ -54,6 +56,8 @@ impl Display for Parameter {
|
||||||
ParameterKind::GroupRef => write!(f, "<target group>"),
|
ParameterKind::GroupRef => write!(f, "<target group>"),
|
||||||
ParameterKind::GroupRefs => write!(f, "<group 1> <group 2> <group 3>..."),
|
ParameterKind::GroupRefs => write!(f, "<group 1> <group 2> <group 3>..."),
|
||||||
ParameterKind::SystemRef => write!(f, "<target system>"),
|
ParameterKind::SystemRef => write!(f, "<target system>"),
|
||||||
|
ParameterKind::MessageRef => write!(f, "<target message>"),
|
||||||
|
ParameterKind::ChannelRef => write!(f, "<target channel>"),
|
||||||
ParameterKind::GuildRef => write!(f, "<target guild>"),
|
ParameterKind::GuildRef => write!(f, "<target guild>"),
|
||||||
ParameterKind::MemberPrivacyTarget => write!(f, "<privacy target>"),
|
ParameterKind::MemberPrivacyTarget => write!(f, "<privacy target>"),
|
||||||
ParameterKind::GroupPrivacyTarget => write!(f, "<privacy target>"),
|
ParameterKind::GroupPrivacyTarget => write!(f, "<privacy target>"),
|
||||||
|
|
@ -92,6 +96,8 @@ pub enum ParameterKind {
|
||||||
GroupRef,
|
GroupRef,
|
||||||
GroupRefs,
|
GroupRefs,
|
||||||
SystemRef,
|
SystemRef,
|
||||||
|
MessageRef,
|
||||||
|
ChannelRef,
|
||||||
GuildRef,
|
GuildRef,
|
||||||
MemberPrivacyTarget,
|
MemberPrivacyTarget,
|
||||||
GroupPrivacyTarget,
|
GroupPrivacyTarget,
|
||||||
|
|
@ -111,6 +117,8 @@ impl ParameterKind {
|
||||||
ParameterKind::GroupRef => "target",
|
ParameterKind::GroupRef => "target",
|
||||||
ParameterKind::GroupRefs => "targets",
|
ParameterKind::GroupRefs => "targets",
|
||||||
ParameterKind::SystemRef => "target",
|
ParameterKind::SystemRef => "target",
|
||||||
|
ParameterKind::MessageRef => "target",
|
||||||
|
ParameterKind::ChannelRef => "target",
|
||||||
ParameterKind::GuildRef => "target",
|
ParameterKind::GuildRef => "target",
|
||||||
ParameterKind::MemberPrivacyTarget => "member_privacy_target",
|
ParameterKind::MemberPrivacyTarget => "member_privacy_target",
|
||||||
ParameterKind::GroupPrivacyTarget => "group_privacy_target",
|
ParameterKind::GroupPrivacyTarget => "group_privacy_target",
|
||||||
|
|
@ -157,7 +165,56 @@ impl ParameterKind {
|
||||||
Toggle::from_str(input).map(|t| ParameterValue::Toggle(t.into()))
|
Toggle::from_str(input).map(|t| ParameterValue::Toggle(t.into()))
|
||||||
}
|
}
|
||||||
ParameterKind::Avatar => Ok(ParameterValue::Avatar(input.into())),
|
ParameterKind::Avatar => Ok(ParameterValue::Avatar(input.into())),
|
||||||
ParameterKind::GuildRef => Ok(ParameterValue::GuildRef(input.into())),
|
ParameterKind::MessageRef => {
|
||||||
|
if let Ok(message_id) = input.parse::<u64>() {
|
||||||
|
return Ok(ParameterValue::MessageRef(None, None, message_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
static RE: std::sync::LazyLock<regex::Regex> = std::sync::LazyLock::new(|| {
|
||||||
|
regex::Regex::new(
|
||||||
|
r"https://(?:\w+\.)?discord(?:app)?\.com/channels/(\d+)/(\d+)/(\d+)",
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(captures) = RE.captures(input) {
|
||||||
|
let guild_id = captures
|
||||||
|
.get(1)
|
||||||
|
.and_then(|m| m.as_str().parse::<u64>().ok())
|
||||||
|
.ok_or_else(|| SmolStr::new("invalid guild ID in message link"))?;
|
||||||
|
let channel_id = captures
|
||||||
|
.get(2)
|
||||||
|
.and_then(|m| m.as_str().parse::<u64>().ok())
|
||||||
|
.ok_or_else(|| SmolStr::new("invalid channel ID in message link"))?;
|
||||||
|
let message_id = captures
|
||||||
|
.get(3)
|
||||||
|
.and_then(|m| m.as_str().parse::<u64>().ok())
|
||||||
|
.ok_or_else(|| SmolStr::new("invalid message ID in message link"))?;
|
||||||
|
|
||||||
|
Ok(ParameterValue::MessageRef(
|
||||||
|
Some(guild_id),
|
||||||
|
Some(channel_id),
|
||||||
|
message_id,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Err(SmolStr::new("invalid message reference"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParameterKind::ChannelRef => {
|
||||||
|
let mut text = input;
|
||||||
|
|
||||||
|
if text.len() > 3 && text.starts_with("<#") && text.ends_with('>') {
|
||||||
|
text = &text[2..text.len() - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
text.parse::<u64>()
|
||||||
|
.map(ParameterValue::ChannelRef)
|
||||||
|
.map_err(|_| SmolStr::new("invalid channel ID"))
|
||||||
|
}
|
||||||
|
ParameterKind::GuildRef => input
|
||||||
|
.parse::<u64>()
|
||||||
|
.map(ParameterValue::GuildRef)
|
||||||
|
.map_err(|_| SmolStr::new("invalid guild ID")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -266,6 +266,8 @@ fn get_param_ty(kind: ParameterKind) -> &'static str {
|
||||||
ParameterKind::PrivacyLevel => "PrivacyLevel",
|
ParameterKind::PrivacyLevel => "PrivacyLevel",
|
||||||
ParameterKind::Toggle => "bool",
|
ParameterKind::Toggle => "bool",
|
||||||
ParameterKind::Avatar => "ParsedImage",
|
ParameterKind::Avatar => "ParsedImage",
|
||||||
|
ParameterKind::MessageRef => "Message.Reference",
|
||||||
|
ParameterKind::ChannelRef => "Channel",
|
||||||
ParameterKind::GuildRef => "Guild",
|
ParameterKind::GuildRef => "Guild",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -284,6 +286,8 @@ fn get_param_param_ty(kind: ParameterKind) -> &'static str {
|
||||||
ParameterKind::PrivacyLevel => "PrivacyLevel",
|
ParameterKind::PrivacyLevel => "PrivacyLevel",
|
||||||
ParameterKind::Toggle => "Toggle",
|
ParameterKind::Toggle => "Toggle",
|
||||||
ParameterKind::Avatar => "Avatar",
|
ParameterKind::Avatar => "Avatar",
|
||||||
|
ParameterKind::MessageRef => "Message",
|
||||||
|
ParameterKind::ChannelRef => "Channel",
|
||||||
ParameterKind::GuildRef => "Guild",
|
ParameterKind::GuildRef => "Guild",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,9 @@ interface Parameter {
|
||||||
GroupRef(string group);
|
GroupRef(string group);
|
||||||
GroupRefs(sequence<string> groups);
|
GroupRefs(sequence<string> groups);
|
||||||
SystemRef(string system);
|
SystemRef(string system);
|
||||||
GuildRef(string guild);
|
MessageRef(u64? guild_id, u64? channel_id, u64 message_id);
|
||||||
|
ChannelRef(u64 channel_id);
|
||||||
|
GuildRef(u64 guild_id);
|
||||||
MemberPrivacyTarget(string target);
|
MemberPrivacyTarget(string target);
|
||||||
GroupPrivacyTarget(string target);
|
GroupPrivacyTarget(string target);
|
||||||
SystemPrivacyTarget(string target);
|
SystemPrivacyTarget(string target);
|
||||||
|
|
|
||||||
|
|
@ -22,19 +22,53 @@ pub enum CommandResult {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Parameter {
|
pub enum Parameter {
|
||||||
MemberRef { member: String },
|
MemberRef {
|
||||||
MemberRefs { members: Vec<String> },
|
member: String,
|
||||||
GroupRef { group: String },
|
},
|
||||||
GroupRefs { groups: Vec<String> },
|
MemberRefs {
|
||||||
SystemRef { system: String },
|
members: Vec<String>,
|
||||||
GuildRef { guild: String },
|
},
|
||||||
MemberPrivacyTarget { target: String },
|
GroupRef {
|
||||||
GroupPrivacyTarget { target: String },
|
group: String,
|
||||||
SystemPrivacyTarget { target: String },
|
},
|
||||||
PrivacyLevel { level: String },
|
GroupRefs {
|
||||||
OpaqueString { raw: String },
|
groups: Vec<String>,
|
||||||
Toggle { toggle: bool },
|
},
|
||||||
Avatar { avatar: String },
|
SystemRef {
|
||||||
|
system: String,
|
||||||
|
},
|
||||||
|
MessageRef {
|
||||||
|
guild_id: Option<u64>,
|
||||||
|
channel_id: Option<u64>,
|
||||||
|
message_id: u64,
|
||||||
|
},
|
||||||
|
ChannelRef {
|
||||||
|
channel_id: u64,
|
||||||
|
},
|
||||||
|
GuildRef {
|
||||||
|
guild_id: u64,
|
||||||
|
},
|
||||||
|
MemberPrivacyTarget {
|
||||||
|
target: String,
|
||||||
|
},
|
||||||
|
GroupPrivacyTarget {
|
||||||
|
target: String,
|
||||||
|
},
|
||||||
|
SystemPrivacyTarget {
|
||||||
|
target: String,
|
||||||
|
},
|
||||||
|
PrivacyLevel {
|
||||||
|
level: String,
|
||||||
|
},
|
||||||
|
OpaqueString {
|
||||||
|
raw: String,
|
||||||
|
},
|
||||||
|
Toggle {
|
||||||
|
toggle: bool,
|
||||||
|
},
|
||||||
|
Avatar {
|
||||||
|
avatar: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ParameterValue> for Parameter {
|
impl From<ParameterValue> for Parameter {
|
||||||
|
|
@ -52,7 +86,13 @@ impl From<ParameterValue> for Parameter {
|
||||||
ParameterValue::OpaqueString(raw) => Self::OpaqueString { raw },
|
ParameterValue::OpaqueString(raw) => Self::OpaqueString { raw },
|
||||||
ParameterValue::Toggle(toggle) => Self::Toggle { toggle },
|
ParameterValue::Toggle(toggle) => Self::Toggle { toggle },
|
||||||
ParameterValue::Avatar(avatar) => Self::Avatar { avatar },
|
ParameterValue::Avatar(avatar) => Self::Avatar { avatar },
|
||||||
ParameterValue::GuildRef(guild) => Self::GuildRef { guild },
|
ParameterValue::MessageRef(guild_id, channel_id, message_id) => Self::MessageRef {
|
||||||
|
guild_id,
|
||||||
|
channel_id,
|
||||||
|
message_id,
|
||||||
|
},
|
||||||
|
ParameterValue::ChannelRef(channel_id) => Self::ChannelRef { channel_id },
|
||||||
|
ParameterValue::GuildRef(guild_id) => Self::GuildRef { guild_id },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue