feat: add pk;reproxy
(#447)
This commit is contained in:
parent
fe69b1b320
commit
ee17fcb11f
@ -86,6 +86,7 @@ public partial class CommandTree
|
|||||||
public static Command Explain = new Command("explain", "explain", "Explains the basics of systems and proxying");
|
public static Command Explain = new Command("explain", "explain", "Explains the basics of systems and proxying");
|
||||||
public static Command Message = new Command("message", "message <id|link> [delete|author]", "Looks up a proxied message");
|
public static Command Message = new Command("message", "message <id|link> [delete|author]", "Looks up a proxied message");
|
||||||
public static Command MessageEdit = new Command("edit", "edit [link] <text>", "Edit a previously proxied message");
|
public static Command MessageEdit = new Command("edit", "edit [link] <text>", "Edit a previously proxied message");
|
||||||
|
public static Command MessageReproxy = new Command("reproxy", "reproxy [link] <member>", "Reproxy a previously proxied message using a different member");
|
||||||
public static Command ProxyCheck = new Command("debug proxy", "debug proxy [link|reply]", "Checks why your message has not been proxied");
|
public static Command ProxyCheck = new Command("debug proxy", "debug proxy [link|reply]", "Checks why your message has not been proxied");
|
||||||
public static Command LogChannel = new Command("log channel", "log channel <channel>", "Designates a channel to post proxied messages to");
|
public static Command LogChannel = new Command("log channel", "log channel <channel>", "Designates a channel to post proxied messages to");
|
||||||
public static Command LogChannelClear = new Command("log channel", "log channel -clear", "Clears the currently set log channel");
|
public static Command LogChannelClear = new Command("log channel", "log channel -clear", "Clears the currently set log channel");
|
||||||
|
@ -48,6 +48,8 @@ public partial class CommandTree
|
|||||||
return ctx.Execute<ProxiedMessage>(Message, m => m.GetMessage(ctx));
|
return ctx.Execute<ProxiedMessage>(Message, m => m.GetMessage(ctx));
|
||||||
if (ctx.Match("edit", "e"))
|
if (ctx.Match("edit", "e"))
|
||||||
return ctx.Execute<ProxiedMessage>(MessageEdit, m => m.EditMessage(ctx));
|
return ctx.Execute<ProxiedMessage>(MessageEdit, m => m.EditMessage(ctx));
|
||||||
|
if (ctx.Match("reproxy", "rp"))
|
||||||
|
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));
|
return ctx.Execute<ServerConfig>(LogChannel, m => m.SetLogChannel(ctx));
|
||||||
|
@ -13,6 +13,8 @@ using Myriad.Types;
|
|||||||
|
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
|
|
||||||
|
using App.Metrics;
|
||||||
|
|
||||||
using PluralKit.Core;
|
using PluralKit.Core;
|
||||||
|
|
||||||
namespace PluralKit.Bot;
|
namespace PluralKit.Bot;
|
||||||
@ -20,30 +22,70 @@ namespace PluralKit.Bot;
|
|||||||
public class ProxiedMessage
|
public class ProxiedMessage
|
||||||
{
|
{
|
||||||
private static readonly Duration EditTimeout = Duration.FromMinutes(10);
|
private static readonly Duration EditTimeout = Duration.FromMinutes(10);
|
||||||
|
private static readonly Duration ReproxyTimeout = Duration.FromMinutes(1);
|
||||||
|
|
||||||
// private readonly IDiscordCache _cache;
|
// private readonly IDiscordCache _cache;
|
||||||
private readonly IClock _clock;
|
private readonly ModelRepository _repo;
|
||||||
|
private readonly IMetrics _metrics;
|
||||||
|
|
||||||
private readonly EmbedService _embeds;
|
private readonly EmbedService _embeds;
|
||||||
private readonly LogChannelService _logChannel;
|
private readonly LogChannelService _logChannel;
|
||||||
private readonly DiscordApiClient _rest;
|
private readonly DiscordApiClient _rest;
|
||||||
private readonly WebhookExecutorService _webhookExecutor;
|
private readonly WebhookExecutorService _webhookExecutor;
|
||||||
|
private readonly ProxyService _proxy;
|
||||||
|
|
||||||
public ProxiedMessage(EmbedService embeds, IClock clock,
|
public ProxiedMessage(EmbedService embeds,
|
||||||
DiscordApiClient rest,
|
DiscordApiClient rest, IMetrics metrics, ModelRepository repo, ProxyService proxy,
|
||||||
WebhookExecutorService webhookExecutor, LogChannelService logChannel, IDiscordCache cache)
|
WebhookExecutorService webhookExecutor, LogChannelService logChannel, IDiscordCache cache)
|
||||||
{
|
{
|
||||||
_embeds = embeds;
|
_embeds = embeds;
|
||||||
_clock = clock;
|
|
||||||
_rest = rest;
|
_rest = rest;
|
||||||
_webhookExecutor = webhookExecutor;
|
_webhookExecutor = webhookExecutor;
|
||||||
|
_repo = repo;
|
||||||
_logChannel = logChannel;
|
_logChannel = logChannel;
|
||||||
// _cache = cache;
|
// _cache = cache;
|
||||||
|
_metrics = metrics;
|
||||||
|
_proxy = proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ReproxyMessage(Context ctx)
|
||||||
|
{
|
||||||
|
var msg = await GetMessageToEdit(ctx, ReproxyTimeout, true);
|
||||||
|
|
||||||
|
if (ctx.System.Id != msg.System?.Id)
|
||||||
|
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)
|
||||||
|
throw new PKError("Could not find a member to reproxy the message with.");
|
||||||
|
|
||||||
|
// Fetch members and get the ProxyMember for `target`
|
||||||
|
List <ProxyMember> members;
|
||||||
|
using (_metrics.Measure.Timer.Time(BotMetrics.ProxyMembersQueryTime))
|
||||||
|
members = (await _repo.GetProxyMembers(ctx.Author.Id, msg.Message.Guild!.Value)).ToList();
|
||||||
|
var match = members.Find(x => x.Id == target.Id);
|
||||||
|
if (match == null)
|
||||||
|
throw new PKError("Could not find a member to reproxy the message with.");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _proxy.ExecuteReproxy(ctx.Message, msg.Message, match);
|
||||||
|
|
||||||
|
if (ctx.Guild == null)
|
||||||
|
await _rest.CreateReaction(ctx.Channel.Id, ctx.Message.Id, new Emoji { Name = Emojis.Success });
|
||||||
|
if ((await ctx.BotPermissions).HasFlag(PermissionSet.ManageMessages))
|
||||||
|
await _rest.DeleteMessage(ctx.Channel.Id, ctx.Message.Id);
|
||||||
|
}
|
||||||
|
catch (NotFoundException)
|
||||||
|
{
|
||||||
|
throw new PKError("Could not reproxy message.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task EditMessage(Context ctx)
|
public async Task EditMessage(Context ctx)
|
||||||
{
|
{
|
||||||
var msg = await GetMessageToEdit(ctx);
|
var msg = await GetMessageToEdit(ctx, EditTimeout, false);
|
||||||
|
|
||||||
if (ctx.System.Id != msg.System?.Id)
|
if (ctx.System.Id != msg.System?.Id)
|
||||||
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.");
|
||||||
@ -93,8 +135,11 @@ public class ProxiedMessage
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<FullMessage> GetMessageToEdit(Context ctx)
|
private async Task<FullMessage> GetMessageToEdit(Context ctx, Duration timeout, bool isReproxy)
|
||||||
{
|
{
|
||||||
|
var editType = isReproxy ? "reproxy" : "edit";
|
||||||
|
var editTypeAction = isReproxy ? "reproxied" : "edited";
|
||||||
|
|
||||||
// todo: is it correct to get a connection here?
|
// todo: is it correct to get a connection here?
|
||||||
await using var conn = await ctx.Database.Obtain();
|
await using var conn = await ctx.Database.Obtain();
|
||||||
FullMessage? msg = null;
|
FullMessage? msg = null;
|
||||||
@ -110,15 +155,15 @@ public class ProxiedMessage
|
|||||||
if (msg == null)
|
if (msg == null)
|
||||||
{
|
{
|
||||||
if (ctx.Guild == null)
|
if (ctx.Guild == null)
|
||||||
throw new PKSyntaxError("You must use a message link to edit messages in DMs.");
|
throw new PKSyntaxError($"You must use a message link to {editType} messages in DMs.");
|
||||||
|
|
||||||
var recent = await FindRecentMessage(ctx);
|
var recent = await FindRecentMessage(ctx, timeout);
|
||||||
if (recent == null)
|
if (recent == null)
|
||||||
throw new PKSyntaxError("Could not find a recent message to edit.");
|
throw new PKSyntaxError($"Could not find a recent message to {editType}.");
|
||||||
|
|
||||||
msg = await ctx.Repository.GetMessage(conn, recent.Mid);
|
msg = await ctx.Repository.GetMessage(conn, recent.Mid);
|
||||||
if (msg == null)
|
if (msg == null)
|
||||||
throw new PKSyntaxError("Could not find a recent message to edit.");
|
throw new PKSyntaxError($"Could not find a recent message to {editType}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.Message.Channel != ctx.Channel.Id)
|
if (msg.Message.Channel != ctx.Channel.Id)
|
||||||
@ -136,17 +181,21 @@ public class ProxiedMessage
|
|||||||
throw new PKError(error);
|
throw new PKError(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var msgTimestamp = DiscordUtils.SnowflakeToInstant(msg.Message.Mid);
|
||||||
|
if (isReproxy && SystemClock.Instance.GetCurrentInstant() - msgTimestamp > timeout)
|
||||||
|
throw new PKError($"The message is too old to be {editTypeAction}.");
|
||||||
|
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<PKMessage?> FindRecentMessage(Context ctx)
|
private async Task<PKMessage?> FindRecentMessage(Context ctx, Duration timeout)
|
||||||
{
|
{
|
||||||
var lastMessage = await ctx.Repository.GetLastMessage(ctx.Guild.Id, ctx.Channel.Id, ctx.Author.Id);
|
var lastMessage = await ctx.Repository.GetLastMessage(ctx.Guild.Id, ctx.Channel.Id, ctx.Author.Id);
|
||||||
if (lastMessage == null)
|
if (lastMessage == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var timestamp = DiscordUtils.SnowflakeToInstant(lastMessage.Mid);
|
var timestamp = DiscordUtils.SnowflakeToInstant(lastMessage.Mid);
|
||||||
if (_clock.GetCurrentInstant() - timestamp > EditTimeout)
|
if (SystemClock.Instance.GetCurrentInstant() - timestamp > timeout)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return lastMessage;
|
return lastMessage;
|
||||||
|
@ -188,6 +188,60 @@ public class ProxyService
|
|||||||
await HandleProxyExecutedActions(ctx, autoproxySettings, trigger, proxyMessage, match);
|
await HandleProxyExecutedActions(ctx, autoproxySettings, trigger, proxyMessage, match);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task ExecuteReproxy(Message trigger, PKMessage msg, ProxyMember member)
|
||||||
|
{
|
||||||
|
var originalMsg = await _rest.GetMessageOrNull(msg.Channel, msg.Mid);
|
||||||
|
if (originalMsg == null)
|
||||||
|
throw new PKError("Could not reproxy message.");
|
||||||
|
|
||||||
|
// Get a MessageContext for the original message
|
||||||
|
MessageContext ctx =
|
||||||
|
await _repo.GetMessageContext(msg.Sender, msg.Guild!.Value, msg.Channel);
|
||||||
|
|
||||||
|
// Make sure proxying is enabled here
|
||||||
|
if (ctx.InBlacklist)
|
||||||
|
throw new ProxyChecksFailedException(
|
||||||
|
"Proxying was disabled in this channel by a server administrator (via the proxy blacklist).");
|
||||||
|
|
||||||
|
var match = new ProxyMatch
|
||||||
|
{
|
||||||
|
Member = member,
|
||||||
|
};
|
||||||
|
|
||||||
|
var messageChannel = await _rest.GetChannelOrNull(msg.Channel!);
|
||||||
|
var rootChannel = await _rest.GetChannelOrNull(messageChannel.IsThread() ? messageChannel.ParentId!.Value : messageChannel.Id);
|
||||||
|
var threadId = messageChannel.IsThread() ? messageChannel.Id : (ulong?)null;
|
||||||
|
var guild = await _rest.GetGuildOrNull(msg.Guild!.Value);
|
||||||
|
|
||||||
|
// Grab user permissions
|
||||||
|
var senderPermissions = PermissionExtensions.PermissionsFor(guild, rootChannel, trigger.Author.Id, null);
|
||||||
|
var allowEveryone = senderPermissions.HasFlag(PermissionSet.MentionEveryone);
|
||||||
|
|
||||||
|
// Make sure user has permissions to send messages
|
||||||
|
if (!senderPermissions.HasFlag(PermissionSet.SendMessages))
|
||||||
|
throw new PKError("You don't have permission to send messages in the channel that message is in.");
|
||||||
|
|
||||||
|
// Send the reproxied webhook
|
||||||
|
var proxyMessage = await _webhookExecutor.ExecuteWebhook(new ProxyRequest
|
||||||
|
{
|
||||||
|
GuildId = guild.Id,
|
||||||
|
ChannelId = rootChannel.Id,
|
||||||
|
ThreadId = threadId,
|
||||||
|
Name = match.Member.ProxyName(ctx),
|
||||||
|
AvatarUrl = AvatarUtils.TryRewriteCdnUrl(match.Member.ProxyAvatar(ctx)),
|
||||||
|
Content = originalMsg.Content!,
|
||||||
|
Attachments = originalMsg.Attachments!,
|
||||||
|
FileSizeLimit = guild.FileSizeLimit(),
|
||||||
|
Embeds = originalMsg.Embeds!.ToArray(),
|
||||||
|
Stickers = originalMsg.StickerItems!,
|
||||||
|
AllowEveryone = allowEveryone
|
||||||
|
});
|
||||||
|
|
||||||
|
var autoproxySettings = await _repo.GetAutoproxySettings(ctx.SystemId.Value, msg.Guild!.Value, null);
|
||||||
|
await HandleProxyExecutedActions(ctx, autoproxySettings, trigger, proxyMessage, match, deletePrevious: false);
|
||||||
|
await _rest.DeleteMessage(originalMsg.ChannelId!, originalMsg.Id!);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<(string?, string?)> FetchReferencedMessageAuthorInfo(Message trigger, Message referenced)
|
private async Task<(string?, string?)> FetchReferencedMessageAuthorInfo(Message trigger, Message referenced)
|
||||||
{
|
{
|
||||||
if (referenced.WebhookId != null)
|
if (referenced.WebhookId != null)
|
||||||
@ -308,7 +362,8 @@ public class ProxyService
|
|||||||
=> message.Content.StartsWith(@"\\") || message.Content.StartsWith("\\\u200b\\");
|
=> message.Content.StartsWith(@"\\") || message.Content.StartsWith("\\\u200b\\");
|
||||||
|
|
||||||
private async Task HandleProxyExecutedActions(MessageContext ctx, AutoproxySettings autoproxySettings,
|
private async Task HandleProxyExecutedActions(MessageContext ctx, AutoproxySettings autoproxySettings,
|
||||||
Message triggerMessage, Message proxyMessage, ProxyMatch match)
|
Message triggerMessage, Message proxyMessage, ProxyMatch match,
|
||||||
|
bool deletePrevious = true)
|
||||||
{
|
{
|
||||||
var sentMessage = new PKMessage
|
var sentMessage = new PKMessage
|
||||||
{
|
{
|
||||||
@ -338,6 +393,9 @@ public class ProxyService
|
|||||||
|
|
||||||
async Task DeleteProxyTriggerMessage()
|
async Task DeleteProxyTriggerMessage()
|
||||||
{
|
{
|
||||||
|
if (!deletePrevious)
|
||||||
|
return;
|
||||||
|
|
||||||
// Wait a second or so before deleting the original message
|
// Wait a second or so before deleting the original message
|
||||||
await Task.Delay(MessageDeletionDelay);
|
await Task.Delay(MessageDeletionDelay);
|
||||||
try
|
try
|
||||||
|
@ -134,6 +134,7 @@ Some arguments indicate the use of specific Discord features. These include:
|
|||||||
- `pk;debug permissions [server id]` - [Checks the given server's permission setup](/staff/permissions/#permission-checker-command) to check if it's compatible with PluralKit.
|
- `pk;debug permissions [server id]` - [Checks the given server's permission setup](/staff/permissions/#permission-checker-command) to check if it's compatible with PluralKit.
|
||||||
- `pk;debug proxying <message link|reply>` - Checks why your message has not been proxied.
|
- `pk;debug proxying <message link|reply>` - Checks why your message has not been proxied.
|
||||||
- `pk;edit [message link|reply] <new content>` - Edits a proxied message. Without an explicit message target, will target the last message proxied by your system in the current channel. **Does not support message IDs!**
|
- `pk;edit [message link|reply] <new content>` - Edits a proxied message. Without an explicit message target, will target the last message proxied by your system in the current channel. **Does not support message IDs!**
|
||||||
|
- `pk;reproxy [message link|reply] <member name|ID>` - Reproxies a message using a different member. Without an explicit message target, will target the last message proxied by your system in the current channel.
|
||||||
- `pk;link <account>` - Links your system to a different account.
|
- `pk;link <account>` - Links your system to a different account.
|
||||||
- `pk;unlink [account]` - Unlinks an account from your system.
|
- `pk;unlink [account]` - Unlinks an account from your system.
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user