mirror of
https://github.com/PluralKit/PluralKit.git
synced 2026-02-04 04:56:49 +00:00
feat(bot): port message info embeds to cv2
This commit is contained in:
parent
14f11bd1e9
commit
83f2d33c3d
7 changed files with 312 additions and 31 deletions
|
|
@ -11,6 +11,7 @@ public record MessageComponent
|
||||||
public string? Url { get; init; }
|
public string? Url { get; init; }
|
||||||
public bool? Disabled { get; init; }
|
public bool? Disabled { get; init; }
|
||||||
public uint? AccentColor { get; init; }
|
public uint? AccentColor { get; init; }
|
||||||
|
public int? Spacing { get; init; }
|
||||||
public ComponentMedia? Media { get; init; }
|
public ComponentMedia? Media { get; init; }
|
||||||
public ComponentMediaItem[]? Items { get; init; }
|
public ComponentMediaItem[]? Items { get; init; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,11 +43,10 @@ public class ApplicationCommandProxiedMessage
|
||||||
if (channel == null)
|
if (channel == null)
|
||||||
showContent = false;
|
showContent = false;
|
||||||
|
|
||||||
var embeds = new List<Embed>();
|
var components = new List<MessageComponent>();
|
||||||
|
|
||||||
var guild = await _cache.GetGuild(ctx.GuildId);
|
var guild = await _cache.GetGuild(ctx.GuildId);
|
||||||
if (msg.Member != null)
|
if (msg.Member != null)
|
||||||
embeds.Add(await _embeds.CreateMemberEmbed(
|
components.AddRange(await _embeds.CreateMemberMessageComponents(
|
||||||
msg.System,
|
msg.System,
|
||||||
msg.Member,
|
msg.Member,
|
||||||
guild,
|
guild,
|
||||||
|
|
@ -55,10 +54,12 @@ public class ApplicationCommandProxiedMessage
|
||||||
LookupContext.ByNonOwner,
|
LookupContext.ByNonOwner,
|
||||||
DateTimeZone.Utc
|
DateTimeZone.Utc
|
||||||
));
|
));
|
||||||
|
components.Add(new MessageComponent()
|
||||||
embeds.Add(await _embeds.CreateMessageInfoEmbed(msg, showContent, ctx.Config));
|
{
|
||||||
|
Type = ComponentType.Separator
|
||||||
await ctx.Reply(embeds: embeds.ToArray());
|
});
|
||||||
|
components.AddRange(await _embeds.CreateMessageInfoMessageComponents(msg, showContent, ctx.Config));
|
||||||
|
await ctx.Reply(components: components.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task QueryCommandMessage(InteractionContext ctx)
|
private async Task QueryCommandMessage(InteractionContext ctx)
|
||||||
|
|
@ -68,11 +69,7 @@ public class ApplicationCommandProxiedMessage
|
||||||
if (msg == null)
|
if (msg == null)
|
||||||
throw Errors.MessageNotFound(messageId);
|
throw Errors.MessageNotFound(messageId);
|
||||||
|
|
||||||
var embeds = new List<Embed>();
|
await ctx.Reply(components: await _embeds.CreateCommandMessageInfoMessageComponents(msg, true));
|
||||||
|
|
||||||
embeds.Add(await _embeds.CreateCommandMessageInfoEmbed(msg, true));
|
|
||||||
|
|
||||||
await ctx.Reply(embeds: embeds.ToArray());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteMessage(InteractionContext ctx)
|
public async Task DeleteMessage(InteractionContext ctx)
|
||||||
|
|
|
||||||
|
|
@ -426,6 +426,8 @@ public class ProxiedMessage
|
||||||
if (ctx.Match("author") || ctx.MatchFlag("author"))
|
if (ctx.Match("author") || ctx.MatchFlag("author"))
|
||||||
{
|
{
|
||||||
var user = await _rest.GetUser(message.Message.Sender);
|
var user = await _rest.GetUser(message.Message.Sender);
|
||||||
|
if (ctx.MatchFlag("show-embed", "se"))
|
||||||
|
{
|
||||||
var eb = new EmbedBuilder()
|
var eb = new EmbedBuilder()
|
||||||
.Author(new Embed.EmbedAuthor(
|
.Author(new Embed.EmbedAuthor(
|
||||||
user != null
|
user != null
|
||||||
|
|
@ -440,7 +442,17 @@ public class ProxiedMessage
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await ctx.Reply(components: await _embeds.CreateAuthorMessageComponents(user, message));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.MatchFlag("show-embed", "se"))
|
||||||
|
{
|
||||||
await ctx.Reply(embed: await _embeds.CreateMessageInfoEmbed(message, showContent, ctx.Config));
|
await ctx.Reply(embed: await _embeds.CreateMessageInfoEmbed(message, showContent, ctx.Config));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ctx.Reply(components: await _embeds.CreateMessageInfoMessageComponents(message, showContent, ctx.Config));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task GetCommandMessage(Context ctx, ulong messageId, bool isDelete)
|
private async Task GetCommandMessage(Context ctx, ulong messageId, bool isDelete)
|
||||||
|
|
@ -472,6 +484,11 @@ public class ProxiedMessage
|
||||||
else if (!await ctx.CheckPermissionsInGuildChannel(channel, PermissionSet.ViewChannel))
|
else if (!await ctx.CheckPermissionsInGuildChannel(channel, PermissionSet.ViewChannel))
|
||||||
showContent = false;
|
showContent = false;
|
||||||
|
|
||||||
|
if (ctx.MatchFlag("show-embed", "se"))
|
||||||
|
{
|
||||||
await ctx.Reply(embed: await _embeds.CreateCommandMessageInfoEmbed(msg, showContent));
|
await ctx.Reply(embed: await _embeds.CreateCommandMessageInfoEmbed(msg, showContent));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await ctx.Reply(components: await _embeds.CreateCommandMessageInfoMessageComponents(msg, showContent));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -186,10 +186,9 @@ public class ReactionAdded: IEventHandler<MessageReactionAddEvent>
|
||||||
{
|
{
|
||||||
var dm = await _dmCache.GetOrCreateDmChannel(evt.UserId);
|
var dm = await _dmCache.GetOrCreateDmChannel(evt.UserId);
|
||||||
|
|
||||||
var embeds = new List<Embed>();
|
var components = new List<MessageComponent>();
|
||||||
|
|
||||||
if (msg.Member != null)
|
if (msg.Member != null)
|
||||||
embeds.Add(await _embeds.CreateMemberEmbed(
|
components.AddRange(await _embeds.CreateMemberMessageComponents(
|
||||||
msg.System,
|
msg.System,
|
||||||
msg.Member,
|
msg.Member,
|
||||||
guild,
|
guild,
|
||||||
|
|
@ -197,10 +196,12 @@ public class ReactionAdded: IEventHandler<MessageReactionAddEvent>
|
||||||
LookupContext.ByNonOwner,
|
LookupContext.ByNonOwner,
|
||||||
DateTimeZone.Utc
|
DateTimeZone.Utc
|
||||||
));
|
));
|
||||||
|
components.Add(new MessageComponent()
|
||||||
embeds.Add(await _embeds.CreateMessageInfoEmbed(msg, true, config));
|
{
|
||||||
|
Type = ComponentType.Separator
|
||||||
await _rest.CreateMessage(dm, new MessageRequest { Embeds = embeds.ToArray() });
|
});
|
||||||
|
components.AddRange(await _embeds.CreateMessageInfoMessageComponents(msg, true, config));
|
||||||
|
await _rest.CreateMessage(dm, new MessageRequest { Components = components.ToArray(), Flags = Message.MessageFlags.IsComponentsV2 });
|
||||||
}
|
}
|
||||||
catch (ForbiddenException) { } // No permissions to DM, can't check for this :(
|
catch (ForbiddenException) { } // No permissions to DM, can't check for this :(
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -766,6 +766,158 @@ public class EmbedService
|
||||||
.Build();
|
.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<MessageComponent[]> CreateMessageInfoMessageComponents(FullMessage msg, bool showContent, SystemConfig? ccfg = null)
|
||||||
|
{
|
||||||
|
var channel = await _cache.GetOrFetchChannel(_rest, msg.Message.Guild ?? 0, msg.Message.Channel);
|
||||||
|
var ctx = LookupContext.ByNonOwner;
|
||||||
|
|
||||||
|
var serverMsg = await _rest.GetMessageOrNull(msg.Message.Channel, msg.Message.Mid);
|
||||||
|
|
||||||
|
// Need this whole dance to handle cases where:
|
||||||
|
// - the user is deleted (userInfo == null)
|
||||||
|
// - the bot's no longer in the server we're querying (channel == null)
|
||||||
|
// - the member is no longer in the server we're querying (memberInfo == null)
|
||||||
|
// TODO: optimize ordering here a bit with new cache impl; and figure what happens if bot leaves server -> channel still cached -> hits this bit and 401s?
|
||||||
|
GuildMemberPartial memberInfo = null;
|
||||||
|
User userInfo = null;
|
||||||
|
if (channel != null)
|
||||||
|
{
|
||||||
|
GuildMember member = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
member = await _rest.GetGuildMember(channel.GuildId!.Value, msg.Message.Sender);
|
||||||
|
}
|
||||||
|
catch (ForbiddenException)
|
||||||
|
{
|
||||||
|
// no permission, couldn't fetch, oh well
|
||||||
|
}
|
||||||
|
|
||||||
|
if (member != null)
|
||||||
|
// Don't do an extra request if we already have this info from the member lookup
|
||||||
|
userInfo = member.User;
|
||||||
|
memberInfo = member;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userInfo == null)
|
||||||
|
userInfo = await _cache.GetOrFetchUser(_rest, msg.Message.Sender);
|
||||||
|
|
||||||
|
// Calculate string displayed under "Sent by"
|
||||||
|
string userStr;
|
||||||
|
if (showContent && memberInfo != null && memberInfo.Nick != null)
|
||||||
|
userStr = $"**\n Username:** {userInfo.NameAndMention()}\n** Nickname:** {memberInfo.Nick}";
|
||||||
|
else if (userInfo != null) userStr = userInfo.NameAndMention();
|
||||||
|
else userStr = $"*(deleted user {msg.Message.Sender})*";
|
||||||
|
|
||||||
|
var content = serverMsg?.Content?.NormalizeLineEndSpacing();
|
||||||
|
if (content == null || !showContent)
|
||||||
|
content = "*(message contents deleted or inaccessible)*";
|
||||||
|
|
||||||
|
var systemStr = msg.System == null
|
||||||
|
? "*(deleted or unknown system)*"
|
||||||
|
: msg.System.NameFor(ctx) != null ? $"{msg.System.NameFor(ctx)} (`{msg.System.DisplayHid(ccfg)}`)" : $"`{msg.System.DisplayHid(ccfg)}`";
|
||||||
|
var memberStr = msg.Member == null
|
||||||
|
? "*(deleted member)*"
|
||||||
|
: $"{msg.Member.NameFor(ctx)} (`{msg.Member.DisplayHid(ccfg)}`)";
|
||||||
|
|
||||||
|
var roles = memberInfo?.Roles?.ToList();
|
||||||
|
var rolesContent = "";
|
||||||
|
if (roles != null && roles.Count > 0 && showContent)
|
||||||
|
{
|
||||||
|
var guild = await _cache.GetGuild(channel.GuildId!.Value);
|
||||||
|
var rolesString = string.Join(", ", (roles
|
||||||
|
.Select(id =>
|
||||||
|
{
|
||||||
|
var role = Array.Find(guild.Roles, r => r.Id == id);
|
||||||
|
if (role != null)
|
||||||
|
return role;
|
||||||
|
return new Role { Name = "*(unknown role)*", Position = 0 };
|
||||||
|
}))
|
||||||
|
.OrderByDescending(role => role.Position)
|
||||||
|
.Select(role => role.Name));
|
||||||
|
rolesContent = $"**Account Roles ({roles.Count})**\n{rolesString}";
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageComponent authorData = new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = $"**System:** {systemStr}\n**Member:** {memberStr}\n**Sent by:** {userStr}\n\n{rolesContent}"
|
||||||
|
};
|
||||||
|
|
||||||
|
var avatarURL = msg.Member?.AvatarFor(ctx).TryGetCleanCdnUrl();
|
||||||
|
MessageComponent header = avatarURL == "" ? authorData : new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Section,
|
||||||
|
Components = [authorData],
|
||||||
|
Accessory = new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Thumbnail,
|
||||||
|
Media = new ComponentMedia()
|
||||||
|
{
|
||||||
|
Url = avatarURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
List<MessageComponent> body = [
|
||||||
|
new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Separator,
|
||||||
|
Spacing = 2
|
||||||
|
}
|
||||||
|
];
|
||||||
|
if (content != "")
|
||||||
|
{
|
||||||
|
body.Add(new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = content
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showContent)
|
||||||
|
{
|
||||||
|
if (serverMsg != null)
|
||||||
|
{
|
||||||
|
var media = new List<ComponentMediaItem>();
|
||||||
|
foreach (Message.Attachment attachment in serverMsg?.Attachments)
|
||||||
|
{
|
||||||
|
var url = attachment.Url;
|
||||||
|
if (url != null && url != "")
|
||||||
|
media.Add(new ComponentMediaItem()
|
||||||
|
{
|
||||||
|
Media = new ComponentMedia()
|
||||||
|
{
|
||||||
|
Url = url
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (media.Count > 0)
|
||||||
|
body.Add(new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.MediaGallery,
|
||||||
|
Items = media.ToArray()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageComponent footer = new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = $"-# Original Message ID: {msg.Message.OriginalMid} · <t:{DiscordUtils.SnowflakeToTimestamp(msg.Message.Mid)}:f>"
|
||||||
|
};
|
||||||
|
|
||||||
|
return [
|
||||||
|
new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Container,
|
||||||
|
Components = [
|
||||||
|
header,
|
||||||
|
..body
|
||||||
|
]
|
||||||
|
},
|
||||||
|
footer
|
||||||
|
];
|
||||||
|
}
|
||||||
public async Task<Embed> CreateMessageInfoEmbed(FullMessage msg, bool showContent, SystemConfig? ccfg = null)
|
public async Task<Embed> CreateMessageInfoEmbed(FullMessage msg, bool showContent, SystemConfig? ccfg = null)
|
||||||
{
|
{
|
||||||
var channel = await _cache.GetOrFetchChannel(_rest, msg.Message.Guild ?? 0, msg.Message.Channel);
|
var channel = await _cache.GetOrFetchChannel(_rest, msg.Message.Guild ?? 0, msg.Message.Channel);
|
||||||
|
|
@ -852,6 +1004,106 @@ public class EmbedService
|
||||||
return eb.Build();
|
return eb.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<MessageComponent[]> CreateAuthorMessageComponents(User? user, FullMessage msg)
|
||||||
|
{
|
||||||
|
MessageComponent authorInfo;
|
||||||
|
var author = user != null
|
||||||
|
? $"{user.Username}#{user.Discriminator}"
|
||||||
|
: $"Deleted user ${msg.Message.Sender}";
|
||||||
|
var avatarUrl = user?.AvatarUrl();
|
||||||
|
var authorString = $"{author}\n**ID: **`{msg.Message.Sender.ToString()}`";
|
||||||
|
if (user != null && avatarUrl != "")
|
||||||
|
{
|
||||||
|
authorInfo = new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Section,
|
||||||
|
Components = [
|
||||||
|
new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = authorString
|
||||||
|
}
|
||||||
|
],
|
||||||
|
Accessory = new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Thumbnail,
|
||||||
|
Media = new ComponentMedia()
|
||||||
|
{
|
||||||
|
Url = avatarUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
authorInfo = new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = authorString
|
||||||
|
};
|
||||||
|
}
|
||||||
|
MessageComponent container = new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Container,
|
||||||
|
Components = [
|
||||||
|
authorInfo,
|
||||||
|
]
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
[
|
||||||
|
new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = user != null ? $"{user.Mention()} ({user.Id})" : $"*(deleted user {msg.Message.Sender})*"
|
||||||
|
},
|
||||||
|
container
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<MessageComponent[]> CreateCommandMessageInfoMessageComponents(Core.CommandMessage msg, bool showContent)
|
||||||
|
{
|
||||||
|
var content = "*(command message deleted or inaccessible)*";
|
||||||
|
if (showContent)
|
||||||
|
{
|
||||||
|
var discordMessage = await _rest.GetMessageOrNull(msg.Channel, msg.OriginalMid);
|
||||||
|
if (discordMessage != null)
|
||||||
|
content = discordMessage.Content;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MessageComponent> body = [
|
||||||
|
new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = $"### Command response message\n**Original message:** https://discord.com/channels/{msg.Guild}/{msg.Channel}/{msg.OriginalMid}\n**Sent By:** <@{msg.Sender}>"
|
||||||
|
},
|
||||||
|
new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Separator,
|
||||||
|
},
|
||||||
|
new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = content
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
MessageComponent footer = new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = $"-# Original Message ID: {msg.OriginalMid} · <t:{DiscordUtils.SnowflakeToTimestamp(msg.OriginalMid)}:f>"
|
||||||
|
};
|
||||||
|
|
||||||
|
return [
|
||||||
|
new MessageComponent(){
|
||||||
|
Type = ComponentType.Container,
|
||||||
|
Components = [
|
||||||
|
..body
|
||||||
|
]
|
||||||
|
},
|
||||||
|
footer
|
||||||
|
];
|
||||||
|
}
|
||||||
public async Task<Embed> CreateCommandMessageInfoEmbed(Core.CommandMessage msg, bool showContent)
|
public async Task<Embed> CreateCommandMessageInfoEmbed(Core.CommandMessage msg, bool showContent)
|
||||||
{
|
{
|
||||||
var content = "*(command message deleted or inaccessible)*";
|
var content = "*(command message deleted or inaccessible)*";
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,9 @@ public static class DiscordUtils
|
||||||
public static Instant SnowflakeToInstant(ulong snowflake) =>
|
public static Instant SnowflakeToInstant(ulong snowflake) =>
|
||||||
Instant.FromUtc(2015, 1, 1, 0, 0, 0) + Duration.FromMilliseconds(snowflake >> 22);
|
Instant.FromUtc(2015, 1, 1, 0, 0, 0) + Duration.FromMilliseconds(snowflake >> 22);
|
||||||
|
|
||||||
|
public static ulong SnowflakeToTimestamp(ulong snowflake) =>
|
||||||
|
((ulong)Instant.FromUtc(2015, 1, 1, 0, 0, 0).ToUnixTimeMilliseconds() + (snowflake >> 22)) / 1000;
|
||||||
|
|
||||||
public static ulong InstantToSnowflake(Instant time) =>
|
public static ulong InstantToSnowflake(Instant time) =>
|
||||||
(ulong)(time - Instant.FromUtc(2015, 1, 1, 0, 0, 0)).TotalMilliseconds << 22;
|
(ulong)(time - Instant.FromUtc(2015, 1, 1, 0, 0, 0)).TotalMilliseconds << 22;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,16 @@ public class InteractionContext
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task Reply(MessageComponent[] components = null)
|
||||||
|
{
|
||||||
|
await Respond(InteractionResponse.ResponseType.ChannelMessageWithSource,
|
||||||
|
new InteractionApplicationCommandCallbackData
|
||||||
|
{
|
||||||
|
Components = components,
|
||||||
|
Flags = Message.MessageFlags.Ephemeral | Message.MessageFlags.IsComponentsV2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public async Task Defer()
|
public async Task Defer()
|
||||||
{
|
{
|
||||||
await Respond(InteractionResponse.ResponseType.DeferredChannelMessageWithSource,
|
await Respond(InteractionResponse.ResponseType.DeferredChannelMessageWithSource,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue