mirror of
https://github.com/PluralKit/PluralKit.git
synced 2026-02-04 04:56:49 +00:00
feat(bot): ComponentsV2 system/member/group cards
Some checks are pending
Build and push Docker image / .net docker build (push) Waiting to run
.net checks / run .net tests (push) Waiting to run
.net checks / dotnet-format (push) Waiting to run
Build and push Rust service Docker images / rust docker build (push) Waiting to run
rust checks / cargo fmt (push) Waiting to run
Some checks are pending
Build and push Docker image / .net docker build (push) Waiting to run
.net checks / run .net tests (push) Waiting to run
.net checks / dotnet-format (push) Waiting to run
Build and push Rust service Docker images / rust docker build (push) Waiting to run
rust checks / cargo fmt (push) Waiting to run
This commit is contained in:
parent
695d1debf2
commit
fbf51b41c1
20 changed files with 600 additions and 18 deletions
|
|
@ -17,7 +17,7 @@ reqwest = { version = "0.12.7" , default-features = false, features = ["rustls-t
|
||||||
sentry = { version = "0.36.0", default-features = false, features = ["backtrace", "contexts", "panic", "debug-images", "reqwest", "rustls"] } # replace native-tls with rustls
|
sentry = { version = "0.36.0", default-features = false, features = ["backtrace", "contexts", "panic", "debug-images", "reqwest", "rustls"] } # replace native-tls with rustls
|
||||||
serde = { version = "1.0.196", features = ["derive"] }
|
serde = { version = "1.0.196", features = ["derive"] }
|
||||||
serde_json = "1.0.117"
|
serde_json = "1.0.117"
|
||||||
sqlx = { version = "0.8.2", features = ["runtime-tokio", "postgres", "time", "macros", "uuid"] }
|
sqlx = { version = "0.8.2", features = ["runtime-tokio", "postgres", "time", "chrono", "macros", "uuid"] }
|
||||||
tokio = { version = "1.36.0", features = ["full"] }
|
tokio = { version = "1.36.0", features = ["full"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3.16", features = ["env-filter", "json"] }
|
tracing-subscriber = { version = "0.3.16", features = ["env-filter", "json"] }
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ public record MessageRequest
|
||||||
public bool Tts { get; set; }
|
public bool Tts { get; set; }
|
||||||
public AllowedMentions? AllowedMentions { get; set; }
|
public AllowedMentions? AllowedMentions { get; set; }
|
||||||
public Embed[]? Embeds { get; set; }
|
public Embed[]? Embeds { get; set; }
|
||||||
|
public Message.MessageFlags Flags { get; set; }
|
||||||
public MessageComponent[]? Components { get; set; }
|
public MessageComponent[]? Components { get; set; }
|
||||||
public Message.Reference? MessageReference { get; set; }
|
public Message.Reference? MessageReference { get; set; }
|
||||||
}
|
}
|
||||||
13
Myriad/Types/Component/ComponentMedia.cs
Normal file
13
Myriad/Types/Component/ComponentMedia.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
namespace Myriad.Types;
|
||||||
|
|
||||||
|
public record ComponentMedia
|
||||||
|
{
|
||||||
|
public string? Url { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ComponentMediaItem
|
||||||
|
{
|
||||||
|
public ComponentMedia Media { get; init; }
|
||||||
|
public string? Description { get; init; }
|
||||||
|
public bool Spoiler { get; init; } = false;
|
||||||
|
}
|
||||||
|
|
@ -3,5 +3,12 @@ namespace Myriad.Types;
|
||||||
public enum ComponentType
|
public enum ComponentType
|
||||||
{
|
{
|
||||||
ActionRow = 1,
|
ActionRow = 1,
|
||||||
Button = 2
|
Button = 2,
|
||||||
|
StringSelect = 3,
|
||||||
|
Section = 9,
|
||||||
|
Text = 10,
|
||||||
|
Thumbnail = 11,
|
||||||
|
MediaGallery = 12,
|
||||||
|
Separator = 14,
|
||||||
|
Container = 17,
|
||||||
}
|
}
|
||||||
|
|
@ -5,9 +5,15 @@ public record MessageComponent
|
||||||
public ComponentType Type { get; init; }
|
public ComponentType Type { get; init; }
|
||||||
public ButtonStyle? Style { get; set; }
|
public ButtonStyle? Style { get; set; }
|
||||||
public string? Label { get; init; }
|
public string? Label { get; init; }
|
||||||
|
public string? Content { get; init; }
|
||||||
public Emoji? Emoji { get; init; }
|
public Emoji? Emoji { get; init; }
|
||||||
public string? CustomId { get; init; }
|
public string? CustomId { get; init; }
|
||||||
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 ComponentMedia? Media { get; init; }
|
||||||
|
public ComponentMediaItem[]? Items { get; init; }
|
||||||
|
|
||||||
|
public MessageComponent? Accessory { get; init; }
|
||||||
public MessageComponent[]? Components { get; init; }
|
public MessageComponent[]? Components { get; init; }
|
||||||
}
|
}
|
||||||
|
|
@ -17,6 +17,7 @@ public record Message
|
||||||
Ephemeral = 1 << 6,
|
Ephemeral = 1 << 6,
|
||||||
SuppressNotifications = 1 << 12,
|
SuppressNotifications = 1 << 12,
|
||||||
VoiceMessage = 1 << 13,
|
VoiceMessage = 1 << 13,
|
||||||
|
IsComponentsV2 = 1 << 15,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum MessageType
|
public enum MessageType
|
||||||
|
|
@ -73,8 +74,6 @@ public record Message
|
||||||
|
|
||||||
public MessagePoll? Poll { get; init; }
|
public MessagePoll? Poll { get; init; }
|
||||||
|
|
||||||
// public MessageComponent[]? Components { get; init; }
|
|
||||||
|
|
||||||
public record Reference(ulong? GuildId, ulong? ChannelId, ulong? MessageId);
|
public record Reference(ulong? GuildId, ulong? ChannelId, ulong? MessageId);
|
||||||
|
|
||||||
public record MessageActivity(int Type, string PartyId);
|
public record MessageActivity(int Type, string PartyId);
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,8 @@ public class ApplicationCommandProxiedMessage
|
||||||
if (member == null || !(await _cache.PermissionsForMemberInChannel(ctx.GuildId, ctx.ChannelId, member)).HasFlag(requiredPerms))
|
if (member == null || !(await _cache.PermissionsForMemberInChannel(ctx.GuildId, ctx.ChannelId, member)).HasFlag(requiredPerms))
|
||||||
{
|
{
|
||||||
throw new PKError("You do not have permission to send messages in this channel.");
|
throw new PKError("You do not have permission to send messages in this channel.");
|
||||||
};
|
}
|
||||||
|
;
|
||||||
|
|
||||||
var config = await _repo.GetSystemConfig(msg.System.Id);
|
var config = await _repo.GetSystemConfig(msg.System.Id);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -592,6 +592,8 @@ public partial class CommandTree
|
||||||
return ctx.Execute<Config>(null, m => m.HidDisplayCaps(ctx));
|
return ctx.Execute<Config>(null, m => m.HidDisplayCaps(ctx));
|
||||||
if (ctx.MatchMultiple(new[] { "pad" }, new[] { "id", "ids" }) || ctx.MatchMultiple(new[] { "id" }, new[] { "pad", "padding" }) || ctx.Match("idpad", "padid", "padids"))
|
if (ctx.MatchMultiple(new[] { "pad" }, new[] { "id", "ids" }) || ctx.MatchMultiple(new[] { "id" }, new[] { "pad", "padding" }) || ctx.Match("idpad", "padid", "padids"))
|
||||||
return ctx.Execute<Config>(null, m => m.HidListPadding(ctx));
|
return ctx.Execute<Config>(null, m => m.HidListPadding(ctx));
|
||||||
|
if (ctx.MatchMultiple(new[] { "show" }, new[] { "color", "colour", "colors", "colours" }) || ctx.Match("showcolor", "showcolour", "showcolors", "showcolours", "colorcode", "colorhex"))
|
||||||
|
return ctx.Execute<Config>(null, m => m.CardShowColorHex(ctx));
|
||||||
if (ctx.MatchMultiple(new[] { "name" }, new[] { "format" }) || ctx.Match("nameformat", "nf"))
|
if (ctx.MatchMultiple(new[] { "name" }, new[] { "format" }) || ctx.Match("nameformat", "nf"))
|
||||||
return ctx.Execute<Config>(null, m => m.NameFormat(ctx));
|
return ctx.Execute<Config>(null, m => m.NameFormat(ctx));
|
||||||
if (ctx.MatchMultiple(new[] { "member", "group" }, new[] { "limit" }) || ctx.Match("limit"))
|
if (ctx.MatchMultiple(new[] { "member", "group" }, new[] { "limit" }) || ctx.Match("limit"))
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,41 @@ public class Context
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Message> Reply(MessageComponent[] components = null, AllowedMentions? mentions = null, MultipartFile[]? files = null)
|
||||||
|
{
|
||||||
|
var botPerms = await BotPermissions;
|
||||||
|
|
||||||
|
if (!botPerms.HasFlag(PermissionSet.SendMessages))
|
||||||
|
// Will be "swallowed" during the error handler anyway, this message is never shown.
|
||||||
|
throw new PKError("PluralKit does not have permission to send messages in this channel.");
|
||||||
|
|
||||||
|
if (files != null && !botPerms.HasFlag(PermissionSet.AttachFiles))
|
||||||
|
throw new PKError("PluralKit does not have permission to attach files in this channel. Please ensure I have the **Attach Files** permission enabled.");
|
||||||
|
|
||||||
|
var msg = await Rest.CreateMessage(Channel.Id, new MessageRequest
|
||||||
|
{
|
||||||
|
Components = components,
|
||||||
|
Flags = Message.MessageFlags.IsComponentsV2,
|
||||||
|
|
||||||
|
// Default to an empty allowed mentions object instead of null (which means no mentions allowed)
|
||||||
|
AllowedMentions = mentions ?? new AllowedMentions()
|
||||||
|
}, files: files);
|
||||||
|
|
||||||
|
// store log of sent message, so it can be queried or deleted later
|
||||||
|
// skip DMs as DM messages can always be deleted
|
||||||
|
if (Guild != null)
|
||||||
|
await Repository.AddCommandMessage(new Core.CommandMessage
|
||||||
|
{
|
||||||
|
Mid = msg.Id,
|
||||||
|
Guild = Guild!.Id,
|
||||||
|
Channel = Channel.Id,
|
||||||
|
Sender = Author.Id,
|
||||||
|
OriginalMid = Message.Id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task Execute<T>(Command? commandDef, Func<T, Task> handler, bool deprecated = false)
|
public async Task Execute<T>(Command? commandDef, Func<T, Task> handler, bool deprecated = false)
|
||||||
{
|
{
|
||||||
_currentCommand = commandDef;
|
_currentCommand = commandDef;
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,13 @@ public class Config
|
||||||
"off"
|
"off"
|
||||||
));
|
));
|
||||||
|
|
||||||
|
items.Add(new(
|
||||||
|
"show color",
|
||||||
|
"Whether to show color codes in system/member/group cards",
|
||||||
|
EnabledDisabled(ctx.Config.CardShowColorHex),
|
||||||
|
"disabled"
|
||||||
|
));
|
||||||
|
|
||||||
items.Add(new(
|
items.Add(new(
|
||||||
"Proxy Switch",
|
"Proxy Switch",
|
||||||
"Switching behavior when proxy tags are used",
|
"Switching behavior when proxy tags are used",
|
||||||
|
|
@ -570,6 +577,20 @@ public class Config
|
||||||
else throw new PKError(badInputError);
|
else throw new PKError(badInputError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task CardShowColorHex(Context ctx)
|
||||||
|
{
|
||||||
|
if (!ctx.HasNext())
|
||||||
|
{
|
||||||
|
var msg = $"Showing color codes on system/member/group cards is currently **{EnabledDisabled(ctx.Config.CardShowColorHex)}**.";
|
||||||
|
await ctx.Reply(msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newVal = ctx.MatchToggle(false);
|
||||||
|
await ctx.Repository.UpdateSystemConfig(ctx.System.Id, new() { CardShowColorHex = newVal });
|
||||||
|
await ctx.Reply($"Showing color codes on system/member/group cards is now {EnabledDisabled(newVal)}.");
|
||||||
|
}
|
||||||
|
|
||||||
public async Task ProxySwitch(Context ctx)
|
public async Task ProxySwitch(Context ctx)
|
||||||
{
|
{
|
||||||
if (!ctx.HasNext())
|
if (!ctx.HasNext())
|
||||||
|
|
|
||||||
|
|
@ -520,7 +520,13 @@ public class Groups
|
||||||
public async Task ShowGroupCard(Context ctx, PKGroup target)
|
public async Task ShowGroupCard(Context ctx, PKGroup target)
|
||||||
{
|
{
|
||||||
var system = await GetGroupSystem(ctx, target);
|
var system = await GetGroupSystem(ctx, target);
|
||||||
await ctx.Reply(embed: await _embeds.CreateGroupEmbed(ctx, system, target));
|
if (ctx.MatchFlag("show-embed", "se"))
|
||||||
|
{
|
||||||
|
await ctx.Reply(text: EmbedService.LEGACY_EMBED_WARNING, embed: await _embeds.CreateGroupEmbed(ctx, system, target));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ctx.Reply(components: await _embeds.CreateGroupMessageComponents(ctx, system, target));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task GroupPrivacy(Context ctx, PKGroup target, PrivacyLevel? newValueFromCommand)
|
public async Task GroupPrivacy(Context ctx, PKGroup target, PrivacyLevel? newValueFromCommand)
|
||||||
|
|
|
||||||
|
|
@ -122,8 +122,16 @@ public class Member
|
||||||
public async Task ViewMember(Context ctx, PKMember target)
|
public async Task ViewMember(Context ctx, PKMember target)
|
||||||
{
|
{
|
||||||
var system = await ctx.Repository.GetSystem(target.System);
|
var system = await ctx.Repository.GetSystem(target.System);
|
||||||
|
if (ctx.MatchFlag("show-embed", "se"))
|
||||||
|
{
|
||||||
|
await ctx.Reply(
|
||||||
|
text: EmbedService.LEGACY_EMBED_WARNING,
|
||||||
|
embed: await _embeds.CreateMemberEmbed(system, target, ctx.Guild, ctx.Config, ctx.LookupContextFor(system.Id), ctx.Zone));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await ctx.Reply(
|
await ctx.Reply(
|
||||||
embed: await _embeds.CreateMemberEmbed(system, target, ctx.Guild, ctx.Config, ctx.LookupContextFor(system.Id), ctx.Zone));
|
components: await _embeds.CreateMemberMessageComponents(system, target, ctx.Guild, ctx.Config, ctx.LookupContextFor(system.Id), ctx.Zone));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Soulscream(Context ctx, PKMember target)
|
public async Task Soulscream(Context ctx, PKMember target)
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,17 @@ public class Random
|
||||||
"This system has no members!");
|
"This system has no members!");
|
||||||
|
|
||||||
var randInt = randGen.Next(members.Count);
|
var randInt = randGen.Next(members.Count);
|
||||||
await ctx.Reply(embed: await _embeds.CreateMemberEmbed(target, members[randInt], ctx.Guild,
|
|
||||||
ctx.Config, ctx.LookupContextFor(target.Id), ctx.Zone));
|
if (ctx.MatchFlag("show-embed", "se"))
|
||||||
|
{
|
||||||
|
await ctx.Reply(
|
||||||
|
text: EmbedService.LEGACY_EMBED_WARNING,
|
||||||
|
embed: await _embeds.CreateMemberEmbed(target, members[randInt], ctx.Guild, ctx.Config, ctx.LookupContextFor(target.Id), ctx.Zone));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ctx.Reply(
|
||||||
|
components: await _embeds.CreateMemberMessageComponents(target, members[randInt], ctx.Guild, ctx.Config, ctx.LookupContextFor(target.Id), ctx.Zone));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Group(Context ctx, PKSystem target)
|
public async Task Group(Context ctx, PKSystem target)
|
||||||
|
|
@ -60,7 +69,17 @@ public class Random
|
||||||
$"This system has no groups!");
|
$"This system has no groups!");
|
||||||
|
|
||||||
var randInt = randGen.Next(groups.Count());
|
var randInt = randGen.Next(groups.Count());
|
||||||
await ctx.Reply(embed: await _embeds.CreateGroupEmbed(ctx, target, groups.ToArray()[randInt]));
|
|
||||||
|
if (ctx.MatchFlag("show-embed", "se"))
|
||||||
|
{
|
||||||
|
await ctx.Reply(
|
||||||
|
text: EmbedService.LEGACY_EMBED_WARNING,
|
||||||
|
embed: await _embeds.CreateGroupEmbed(ctx, target, groups.ToArray()[randInt]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ctx.Reply(
|
||||||
|
components: await _embeds.CreateGroupMessageComponents(ctx, target, groups.ToArray()[randInt]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task GroupMember(Context ctx, PKGroup group)
|
public async Task GroupMember(Context ctx, PKGroup group)
|
||||||
|
|
@ -92,7 +111,16 @@ public class Random
|
||||||
system = await ctx.Repository.GetSystem(group.System);
|
system = await ctx.Repository.GetSystem(group.System);
|
||||||
|
|
||||||
var randInt = randGen.Next(ms.Count);
|
var randInt = randGen.Next(ms.Count);
|
||||||
await ctx.Reply(embed: await _embeds.CreateMemberEmbed(system, ms[randInt], ctx.Guild,
|
|
||||||
ctx.Config, ctx.LookupContextFor(group.System), ctx.Zone));
|
if (ctx.MatchFlag("show-embed", "se"))
|
||||||
|
{
|
||||||
|
await ctx.Reply(
|
||||||
|
text: EmbedService.LEGACY_EMBED_WARNING,
|
||||||
|
embed: await _embeds.CreateMemberEmbed(system, ms[randInt], ctx.Guild, ctx.Config, ctx.LookupContextFor(system.Id), ctx.Zone));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ctx.Reply(
|
||||||
|
components: await _embeds.CreateMemberMessageComponents(system, ms[randInt], ctx.Guild, ctx.Config, ctx.LookupContextFor(system.Id), ctx.Zone));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -17,8 +17,13 @@ public class System
|
||||||
public async Task Query(Context ctx, PKSystem system)
|
public async Task Query(Context ctx, PKSystem system)
|
||||||
{
|
{
|
||||||
if (system == null) throw Errors.NoSystemError(ctx.DefaultPrefix);
|
if (system == null) throw Errors.NoSystemError(ctx.DefaultPrefix);
|
||||||
|
if (ctx.MatchFlag("show-embed", "se"))
|
||||||
|
{
|
||||||
|
await ctx.Reply(text: EmbedService.LEGACY_EMBED_WARNING, embed: await _embeds.CreateSystemEmbed(ctx, system, ctx.LookupContextFor(system.Id)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await ctx.Reply(embed: await _embeds.CreateSystemEmbed(ctx, system, ctx.LookupContextFor(system.Id)));
|
await ctx.Reply(components: await _embeds.CreateSystemMessageComponents(ctx, system, ctx.LookupContextFor(system.Id)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task New(Context ctx)
|
public async Task New(Context ctx)
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ public class InteractionCreated: IEventHandler<InteractionCreateEvent>
|
||||||
// got some unhandled command, log and ignore
|
// got some unhandled command, log and ignore
|
||||||
_logger.Warning(@"Unhandled ApplicationCommand interaction: {EventId} {CommandName}", evt.Id, evt.Data?.Name);
|
_logger.Warning(@"Unhandled ApplicationCommand interaction: {EventId} {CommandName}", evt.Id, evt.Data?.Name);
|
||||||
break;
|
break;
|
||||||
};
|
}
|
||||||
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -15,17 +15,21 @@ namespace PluralKit.Bot;
|
||||||
|
|
||||||
public class EmbedService
|
public class EmbedService
|
||||||
{
|
{
|
||||||
|
public const string LEGACY_EMBED_WARNING = "\u26A0\uFE0F The \"legacy\" embeds for system/member/group cards are deprecated, and will be removed in future.";
|
||||||
|
|
||||||
private readonly IDiscordCache _cache;
|
private readonly IDiscordCache _cache;
|
||||||
private readonly IDatabase _db;
|
private readonly IDatabase _db;
|
||||||
private readonly ModelRepository _repo;
|
private readonly ModelRepository _repo;
|
||||||
private readonly DiscordApiClient _rest;
|
private readonly DiscordApiClient _rest;
|
||||||
|
private readonly CoreConfig _coreConfig;
|
||||||
|
|
||||||
public EmbedService(IDatabase db, ModelRepository repo, IDiscordCache cache, DiscordApiClient rest)
|
public EmbedService(IDatabase db, ModelRepository repo, IDiscordCache cache, DiscordApiClient rest, CoreConfig coreConfig)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_repo = repo;
|
_repo = repo;
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
_rest = rest;
|
_rest = rest;
|
||||||
|
_coreConfig = coreConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<(ulong Id, User? User)[]> GetUsers(IEnumerable<ulong> ids)
|
private Task<(ulong Id, User? User)[]> GetUsers(IEnumerable<ulong> ids)
|
||||||
|
|
@ -39,6 +43,169 @@ public class EmbedService
|
||||||
return Task.WhenAll(ids.Select(Inner));
|
return Task.WhenAll(ids.Select(Inner));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<MessageComponent[]> CreateSystemMessageComponents(Context cctx, PKSystem system, LookupContext ctx)
|
||||||
|
{
|
||||||
|
// Fetch/render info for all accounts simultaneously
|
||||||
|
var accounts = await _repo.GetSystemAccounts(system.Id);
|
||||||
|
var users = (await GetUsers(accounts)).Select(x => x.User?.NameAndMention() ?? $"(deleted account {x.Id})");
|
||||||
|
var linkedAccounts = new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = "**Linked accounts:**\n" + string.Join("\n", users).Truncate(1000),
|
||||||
|
};
|
||||||
|
|
||||||
|
var countctx = LookupContext.ByNonOwner;
|
||||||
|
if (cctx.MatchFlag("a", "all"))
|
||||||
|
{
|
||||||
|
if (system.Id == cctx.System.Id)
|
||||||
|
countctx = LookupContext.ByOwner;
|
||||||
|
else
|
||||||
|
throw Errors.LookupNotAllowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
var memberCount = await _repo.GetSystemMemberCount(system.Id, countctx == LookupContext.ByOwner ? null : PrivacyLevel.Public);
|
||||||
|
var guildSettings = cctx.Guild != null ? await _repo.GetSystemGuild(cctx.Guild.Id, system.Id) : null;
|
||||||
|
|
||||||
|
var avatar = system.AvatarFor(ctx);
|
||||||
|
var headerText = "";
|
||||||
|
|
||||||
|
if (system.PronounPrivacy.CanAccess(ctx) && system.Pronouns != null)
|
||||||
|
headerText += $"\n**Pronouns:** {system.Pronouns}";
|
||||||
|
|
||||||
|
if (system.Tag != null)
|
||||||
|
headerText += $"\n**Tag:** {system.Tag.EscapeMarkdown()}";
|
||||||
|
|
||||||
|
if (cctx.Config.CardShowColorHex && !system.Color.EmptyOrNull())
|
||||||
|
headerText += $"\n**Color:** #{system.Color}";
|
||||||
|
|
||||||
|
if (cctx.Guild != null)
|
||||||
|
{
|
||||||
|
if (guildSettings.Tag != null && guildSettings.TagEnabled)
|
||||||
|
headerText += $"\n**Tag (in server '{cctx.Guild.Name}'):** {guildSettings.Tag.EscapeMarkdown()}";
|
||||||
|
if (!guildSettings.TagEnabled)
|
||||||
|
headerText += $"\n**Tag (in server '{cctx.Guild.Name}'):** *(tag is disabled in this server)*";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (system.MemberListPrivacy.CanAccess(ctx))
|
||||||
|
{
|
||||||
|
headerText += $"\n**Members:** {memberCount}";
|
||||||
|
if (system.Id == cctx.System.Id)
|
||||||
|
if (memberCount > 0)
|
||||||
|
headerText += $" (see `{cctx.DefaultPrefix}system list`)";
|
||||||
|
else
|
||||||
|
headerText += $" (add one with `{cctx.DefaultPrefix}member new`!)";
|
||||||
|
else if (memberCount > 0)
|
||||||
|
headerText += $" (see `{cctx.DefaultPrefix}system {system.DisplayHid(cctx.Config)} list`)";
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MessageComponent> switchComponent = [];
|
||||||
|
var latestSwitch = await _repo.GetLatestSwitch(system.Id);
|
||||||
|
if (latestSwitch != null && system.FrontPrivacy.CanAccess(ctx))
|
||||||
|
{
|
||||||
|
var switchMembers =
|
||||||
|
await _db.Execute(conn => _repo.GetSwitchMembers(conn, latestSwitch.Id)).ToListAsync();
|
||||||
|
if (switchMembers.Count > 0)
|
||||||
|
{
|
||||||
|
var memberStr = string.Join(", ", switchMembers.Select(m => m.NameFor(ctx)));
|
||||||
|
if (memberStr.Length > 200)
|
||||||
|
memberStr = $"(too many to show, see `{cctx.DefaultPrefix}system {system.DisplayHid(cctx.Config)} fronters`)";
|
||||||
|
|
||||||
|
switchComponent.Add(new()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = $"**{"Current fronter".ToQuantity(switchMembers.Count, ShowQuantityAs.None)}:** {memberStr}",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MessageComponent> descComponents = [];
|
||||||
|
if (system.DescriptionFor(ctx) is { } desc)
|
||||||
|
{
|
||||||
|
descComponents.Add(new()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Separator,
|
||||||
|
});
|
||||||
|
|
||||||
|
descComponents.Add(new()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = desc.NormalizeLineEndSpacing().Truncate(1024),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (system.BannerPrivacy.CanAccess(ctx) && !string.IsNullOrWhiteSpace(system.BannerImage))
|
||||||
|
descComponents.Add(new()
|
||||||
|
{
|
||||||
|
Type = ComponentType.MediaGallery,
|
||||||
|
Items = [new() { Media = new() { Url = system.BannerImage } }],
|
||||||
|
});
|
||||||
|
|
||||||
|
var systemName = (cctx.Guild != null && guildSettings?.DisplayName != null) ? guildSettings?.DisplayName! : system.NameFor(ctx);
|
||||||
|
var premiumText = ""; // TODO(iris): "\n\U0001F31F *PluralKit Premium supporter!*";
|
||||||
|
List<MessageComponent> header = [
|
||||||
|
new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = $"### {systemName ?? $"`{system.DisplayHid(cctx.Config)}`"}{premiumText}",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(headerText))
|
||||||
|
header.Add(new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = headerText,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (cctx.Guild != null)
|
||||||
|
{
|
||||||
|
var guildAvatar = guildSettings.AvatarUrl.TryGetCleanCdnUrl();
|
||||||
|
if (!string.IsNullOrWhiteSpace(guildAvatar))
|
||||||
|
avatar = guildAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(avatar))
|
||||||
|
header = [
|
||||||
|
new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Section,
|
||||||
|
Components = [.. header],
|
||||||
|
Accessory = new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Thumbnail,
|
||||||
|
Media = new() { Url = avatar },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Container,
|
||||||
|
AccentColor = system.Color?.ToDiscordColor(),
|
||||||
|
Components = [ ..header, ..switchComponent, linkedAccounts, ..descComponents ],
|
||||||
|
},
|
||||||
|
new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Section,
|
||||||
|
Components = [
|
||||||
|
new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = $"-# System ID: `{system.DisplayHid(cctx.Config)}`\n-# Created: {system.Created.FormatZoned(cctx.Zone)}",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Accessory = new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Button,
|
||||||
|
Style = ButtonStyle.Link,
|
||||||
|
Label = "View on dashboard",
|
||||||
|
Url = $"{_coreConfig.DashboardBaseUrl}/profile/s/{system.Hid}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<Embed> CreateSystemEmbed(Context cctx, PKSystem system, LookupContext ctx)
|
public async Task<Embed> CreateSystemEmbed(Context cctx, PKSystem system, LookupContext ctx)
|
||||||
{
|
{
|
||||||
// Fetch/render info for all accounts simultaneously
|
// Fetch/render info for all accounts simultaneously
|
||||||
|
|
@ -61,7 +228,7 @@ public class EmbedService
|
||||||
.Footer(new Embed.EmbedFooter(
|
.Footer(new Embed.EmbedFooter(
|
||||||
$"System ID: {system.DisplayHid(cctx.Config)} | Created on {system.Created.FormatZoned(cctx.Zone)}"))
|
$"System ID: {system.DisplayHid(cctx.Config)} | Created on {system.Created.FormatZoned(cctx.Zone)}"))
|
||||||
.Color(system.Color?.ToDiscordColor())
|
.Color(system.Color?.ToDiscordColor())
|
||||||
.Url($"https://dash.pluralkit.me/profile/s/{system.Hid}");
|
.Url($"{_coreConfig.DashboardBaseUrl}/profile/s/{system.Hid}");
|
||||||
|
|
||||||
var avatar = system.AvatarFor(ctx);
|
var avatar = system.AvatarFor(ctx);
|
||||||
if (avatar != null)
|
if (avatar != null)
|
||||||
|
|
@ -164,6 +331,158 @@ public class EmbedService
|
||||||
return embed.Build();
|
return embed.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<MessageComponent[]> CreateMemberMessageComponents(PKSystem system, PKMember member, Guild guild, SystemConfig? ccfg, LookupContext ctx, DateTimeZone zone)
|
||||||
|
{
|
||||||
|
var name = member.NameFor(ctx);
|
||||||
|
var systemGuildSettings = guild != null ? await _repo.GetSystemGuild(guild.Id, system.Id) : null;
|
||||||
|
var systemName = (guild != null && systemGuildSettings?.DisplayName != null) ? systemGuildSettings?.DisplayName! : system.NameFor(ctx);
|
||||||
|
|
||||||
|
var guildSettings = guild != null ? await _repo.GetMemberGuild(guild.Id, member.Id) : null;
|
||||||
|
var guildDisplayName = guildSettings?.DisplayName;
|
||||||
|
var webhook_avatar = guildSettings?.AvatarUrl ?? member.WebhookAvatarFor(ctx) ?? member.AvatarFor(ctx);
|
||||||
|
var avatar = guildSettings?.AvatarUrl ?? member.AvatarFor(ctx);
|
||||||
|
|
||||||
|
var groups = await _repo.GetMemberGroups(member.Id)
|
||||||
|
.Where(g => g.Visibility.CanAccess(ctx))
|
||||||
|
.OrderBy(g => g.Name, StringComparer.InvariantCultureIgnoreCase)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var headerText = "";
|
||||||
|
if (member.MemberVisibility == PrivacyLevel.Private)
|
||||||
|
headerText += "*(this member is hidden)*\n";
|
||||||
|
if (guildSettings?.AvatarUrl != null)
|
||||||
|
if (member.AvatarFor(ctx) != null)
|
||||||
|
headerText +=
|
||||||
|
$"*(this member has a server-specific avatar set; [click here]({member.AvatarUrl.TryGetCleanCdnUrl()}) to see the global avatar)*\n";
|
||||||
|
else
|
||||||
|
headerText += "*(this member has a server-specific avatar set)*\n";
|
||||||
|
|
||||||
|
if (!member.DisplayName.EmptyOrNull() && member.NamePrivacy.CanAccess(ctx))
|
||||||
|
headerText += $"\n**Display name:** {member.DisplayName.Truncate(1024)}";
|
||||||
|
if (guild != null && guildDisplayName != null)
|
||||||
|
headerText += $"\n**Server nickname (for '{guild.Name}'):** {guildDisplayName.Truncate(1024)}";
|
||||||
|
if (ccfg.CardShowColorHex && !member.Color.EmptyOrNull())
|
||||||
|
headerText += $"\n**Color:** #{member.Color}";
|
||||||
|
if (member.PronounsFor(ctx) is { } pronouns && !string.IsNullOrWhiteSpace(pronouns))
|
||||||
|
headerText += $"\n**Pronouns:** {pronouns}";
|
||||||
|
if (member.BirthdayFor(ctx) != null)
|
||||||
|
headerText += $"\n**Birthday:** {member.BirthdayString}";
|
||||||
|
if (member.MessageCountFor(ctx) is { } count && count > 0)
|
||||||
|
headerText += $"\n**Message count:** {member.MessageCount}";
|
||||||
|
|
||||||
|
List<MessageComponent> extraData = [];
|
||||||
|
if (member.HasProxyTags && member.ProxyPrivacy.CanAccess(ctx))
|
||||||
|
{
|
||||||
|
extraData.Add(new MessageComponent
|
||||||
|
{
|
||||||
|
Type = ComponentType.Separator,
|
||||||
|
});
|
||||||
|
|
||||||
|
extraData.Add(new MessageComponent
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = $"**Proxy tags:**\n{member.ProxyTagsString("\n").Truncate(1024)}",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groups.Count > 0)
|
||||||
|
{
|
||||||
|
// More than 5 groups show in "compact" format without ID
|
||||||
|
var content = groups.Count > 5
|
||||||
|
? string.Join(", ", groups.Select(g => g.DisplayName ?? g.Name))
|
||||||
|
: string.Join("\n", groups.Select(g => $"[`{g.DisplayHid(ccfg, isList: true)}`] **{g.DisplayName ?? g.Name}**"));
|
||||||
|
|
||||||
|
extraData.Add(new MessageComponent
|
||||||
|
{
|
||||||
|
Type = ComponentType.Separator,
|
||||||
|
});
|
||||||
|
|
||||||
|
extraData.Add(new MessageComponent
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = $"**Groups ({groups.Count}):**\n{content.Truncate(1000)}",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MessageComponent> descComponents = [];
|
||||||
|
if (member.DescriptionFor(ctx) is { } desc)
|
||||||
|
{
|
||||||
|
descComponents.Add(new()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Separator,
|
||||||
|
});
|
||||||
|
|
||||||
|
descComponents.Add(new()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = desc.NormalizeLineEndSpacing().Truncate(1024),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (member.BannerPrivacy.CanAccess(ctx) && !string.IsNullOrWhiteSpace(member.BannerImage))
|
||||||
|
descComponents.Add(new()
|
||||||
|
{
|
||||||
|
Type = ComponentType.MediaGallery,
|
||||||
|
Items = [new() { Media = new() { Url = member.BannerImage } }],
|
||||||
|
});
|
||||||
|
|
||||||
|
List<MessageComponent> header = [
|
||||||
|
new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = $"### {name}{(systemName != null ? $" ({systemName})" : "")}",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(headerText))
|
||||||
|
header.Add(new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = headerText,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(avatar))
|
||||||
|
header = [
|
||||||
|
new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Section,
|
||||||
|
Components = [.. header],
|
||||||
|
Accessory = new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Thumbnail,
|
||||||
|
Media = new() { Url = avatar },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Container,
|
||||||
|
AccentColor = member.Color?.ToDiscordColor(),
|
||||||
|
Components = [ ..header, ..extraData, ..descComponents ],
|
||||||
|
},
|
||||||
|
new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Section,
|
||||||
|
Components = [
|
||||||
|
new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = $"-# System ID: `{system.DisplayHid(ccfg)}` \u2219 Member ID: `{member.DisplayHid(ccfg)}`{(member.MetadataPrivacy.CanAccess(ctx) ? $"\n-# Created: {member.Created.FormatZoned(zone)}" : "")}",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Accessory = new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Button,
|
||||||
|
Style = ButtonStyle.Link,
|
||||||
|
Label = "View on dashboard",
|
||||||
|
Url = $"{_coreConfig.DashboardBaseUrl}/profile/m/{member.Hid}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<Embed> CreateMemberEmbed(PKSystem system, PKMember member, Guild guild, SystemConfig? ccfg, LookupContext ctx, DateTimeZone zone)
|
public async Task<Embed> CreateMemberEmbed(PKSystem system, PKMember member, Guild guild, SystemConfig? ccfg, LookupContext ctx, DateTimeZone zone)
|
||||||
{
|
{
|
||||||
// string FormatTimestamp(Instant timestamp) => DateTimeFormats.ZonedDateTimeFormat.Format(timestamp.InZone(system.Zone));
|
// string FormatTimestamp(Instant timestamp) => DateTimeFormats.ZonedDateTimeFormat.Format(timestamp.InZone(system.Zone));
|
||||||
|
|
@ -188,7 +507,7 @@ public class EmbedService
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
var eb = new EmbedBuilder()
|
var eb = new EmbedBuilder()
|
||||||
.Author(new Embed.EmbedAuthor(name, IconUrl: webhook_avatar.TryGetCleanCdnUrl(), Url: $"https://dash.pluralkit.me/profile/m/{member.Hid}"))
|
.Author(new Embed.EmbedAuthor(name, IconUrl: webhook_avatar.TryGetCleanCdnUrl(), Url: $"{_coreConfig.DashboardBaseUrl}/profile/m/{member.Hid}"))
|
||||||
// .WithColor(member.ColorPrivacy.CanAccess(ctx) ? color : null)
|
// .WithColor(member.ColorPrivacy.CanAccess(ctx) ? color : null)
|
||||||
.Color(member.Color?.ToDiscordColor())
|
.Color(member.Color?.ToDiscordColor())
|
||||||
.Footer(new Embed.EmbedFooter(
|
.Footer(new Embed.EmbedFooter(
|
||||||
|
|
@ -241,6 +560,119 @@ public class EmbedService
|
||||||
return eb.Build();
|
return eb.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<MessageComponent[]> CreateGroupMessageComponents(Context ctx, PKSystem system, PKGroup target)
|
||||||
|
{
|
||||||
|
var pctx = ctx.LookupContextFor(system.Id);
|
||||||
|
var name = target.NameFor(ctx);
|
||||||
|
var systemGuildSettings = ctx.Guild != null ? await _repo.GetSystemGuild(ctx.Guild.Id, system.Id) : null;
|
||||||
|
var systemName = (ctx.Guild != null && systemGuildSettings?.DisplayName != null) ? systemGuildSettings?.DisplayName! : system.NameFor(ctx);
|
||||||
|
|
||||||
|
var countctx = LookupContext.ByNonOwner;
|
||||||
|
if (ctx.MatchFlag("a", "all"))
|
||||||
|
{
|
||||||
|
if (system.Id == ctx.System.Id)
|
||||||
|
countctx = LookupContext.ByOwner;
|
||||||
|
else
|
||||||
|
throw Errors.LookupNotAllowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
var memberCount = await _repo.GetGroupMemberCount(target.Id, countctx == LookupContext.ByOwner ? null : PrivacyLevel.Public);
|
||||||
|
var headerText = "";
|
||||||
|
|
||||||
|
if (target.NamePrivacy.CanAccess(pctx) && target.DisplayName != null)
|
||||||
|
headerText += $"\n**Display name:** {target.DisplayName}";
|
||||||
|
|
||||||
|
if (ctx.Config.CardShowColorHex && !target.Color.EmptyOrNull())
|
||||||
|
headerText += $"\n**Color:** #{target.Color}";
|
||||||
|
|
||||||
|
if (target.ListPrivacy.CanAccess(pctx))
|
||||||
|
{
|
||||||
|
headerText += $"\n**Members:** {memberCount}";
|
||||||
|
if (system.Id == ctx.System.Id && memberCount == 0)
|
||||||
|
headerText += $" (add one with `{ctx.DefaultPrefix}group {target.Reference(ctx)} add <member>`!)";
|
||||||
|
else if (memberCount > 0)
|
||||||
|
headerText += $" (see `{ctx.DefaultPrefix}group {target.Reference(ctx)} list`)";
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MessageComponent> descComponents = [];
|
||||||
|
if (target.DescriptionFor(pctx) is { } desc)
|
||||||
|
{
|
||||||
|
descComponents.Add(new()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Separator,
|
||||||
|
});
|
||||||
|
|
||||||
|
descComponents.Add(new()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = desc.NormalizeLineEndSpacing().Truncate(1024),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.BannerPrivacy.CanAccess(pctx) && !string.IsNullOrWhiteSpace(target.BannerImage))
|
||||||
|
descComponents.Add(new()
|
||||||
|
{
|
||||||
|
Type = ComponentType.MediaGallery,
|
||||||
|
Items = [new() { Media = new() { Url = target.BannerImage } }],
|
||||||
|
});
|
||||||
|
|
||||||
|
List<MessageComponent> header = [
|
||||||
|
new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = $"### {name}{(systemName != null ? $" ({systemName})" : "")}",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(headerText))
|
||||||
|
header.Add(new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = headerText,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (target.IconFor(pctx) is { } icon)
|
||||||
|
header = [
|
||||||
|
new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Section,
|
||||||
|
Components = [.. header],
|
||||||
|
Accessory = new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Thumbnail,
|
||||||
|
Media = new() { Url = icon.TryGetCleanCdnUrl() },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Container,
|
||||||
|
AccentColor = target.Color?.ToDiscordColor(),
|
||||||
|
Components = [ ..header, ..descComponents ],
|
||||||
|
},
|
||||||
|
new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Section,
|
||||||
|
Components = [
|
||||||
|
new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Text,
|
||||||
|
Content = $"-# System ID: `{system.DisplayHid(ctx.Config)}` \u2219 Group ID: `{target.DisplayHid(ctx.Config)}`{(target.MetadataPrivacy.CanAccess(pctx) ? $"\n-# Created: {target.Created.FormatZoned(ctx.Zone)}" : "")}",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Accessory = new MessageComponent()
|
||||||
|
{
|
||||||
|
Type = ComponentType.Button,
|
||||||
|
Style = ButtonStyle.Link,
|
||||||
|
Label = "View on dashboard",
|
||||||
|
Url = $"{_coreConfig.DashboardBaseUrl}/profile/g/{target.Hid}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<Embed> CreateGroupEmbed(Context ctx, PKSystem system, PKGroup target)
|
public async Task<Embed> CreateGroupEmbed(Context ctx, PKSystem system, PKGroup target)
|
||||||
{
|
{
|
||||||
var pctx = ctx.LookupContextFor(system.Id);
|
var pctx = ctx.LookupContextFor(system.Id);
|
||||||
|
|
@ -266,7 +698,7 @@ public class EmbedService
|
||||||
nameField = $"{nameField}";
|
nameField = $"{nameField}";
|
||||||
|
|
||||||
var eb = new EmbedBuilder()
|
var eb = new EmbedBuilder()
|
||||||
.Author(new Embed.EmbedAuthor(nameField, IconUrl: target.IconFor(pctx), Url: $"https://dash.pluralkit.me/profile/g/{target.Hid}"))
|
.Author(new Embed.EmbedAuthor(nameField, IconUrl: target.IconFor(pctx), Url: $"{_coreConfig.DashboardBaseUrl}/profile/g/{target.Hid}"))
|
||||||
.Color(target.Color?.ToDiscordColor());
|
.Color(target.Color?.ToDiscordColor());
|
||||||
|
|
||||||
eb.Footer(new Embed.EmbedFooter($"System ID: {system.DisplayHid(ctx.Config)} | Group ID: {target.DisplayHid(ctx.Config)}{(target.MetadataPrivacy.CanAccess(pctx) ? $" | Created on {target.Created.FormatZoned(ctx.Zone)}" : "")}"));
|
eb.Footer(new Embed.EmbedFooter($"System ID: {system.DisplayHid(ctx.Config)} | Group ID: {target.DisplayHid(ctx.Config)}{(target.MetadataPrivacy.CanAccess(pctx) ? $" | Created on {target.Created.FormatZoned(ctx.Zone)}" : "")}"));
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ public class CoreConfig
|
||||||
public string? SeqLogUrl { get; set; }
|
public string? SeqLogUrl { get; set; }
|
||||||
public string? DispatchProxyUrl { get; set; }
|
public string? DispatchProxyUrl { get; set; }
|
||||||
public string? DispatchProxyToken { get; set; }
|
public string? DispatchProxyToken { get; set; }
|
||||||
|
public string DashboardBaseUrl { get; set; } = "https://dash.pluralkit.local";
|
||||||
|
|
||||||
public LogEventLevel ConsoleLogLevel { get; set; } = LogEventLevel.Debug;
|
public LogEventLevel ConsoleLogLevel { get; set; } = LogEventLevel.Debug;
|
||||||
public LogEventLevel ElasticLogLevel { get; set; } = LogEventLevel.Information;
|
public LogEventLevel ElasticLogLevel { get; set; } = LogEventLevel.Information;
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ public class SystemConfigPatch: PatchObject
|
||||||
public Partial<bool> ProxyErrorMessageEnabled { get; set; }
|
public Partial<bool> ProxyErrorMessageEnabled { get; set; }
|
||||||
public Partial<bool> HidDisplaySplit { get; set; }
|
public Partial<bool> HidDisplaySplit { get; set; }
|
||||||
public Partial<bool> HidDisplayCaps { get; set; }
|
public Partial<bool> HidDisplayCaps { get; set; }
|
||||||
|
public Partial<bool> CardShowColorHex { get; set; }
|
||||||
public Partial<string?> NameFormat { get; set; }
|
public Partial<string?> NameFormat { get; set; }
|
||||||
public Partial<SystemConfig.HidPadFormat> HidListPadding { get; set; }
|
public Partial<SystemConfig.HidPadFormat> HidListPadding { get; set; }
|
||||||
public Partial<SystemConfig.ProxySwitchAction> ProxySwitch { get; set; }
|
public Partial<SystemConfig.ProxySwitchAction> ProxySwitch { get; set; }
|
||||||
|
|
@ -41,6 +42,7 @@ public class SystemConfigPatch: PatchObject
|
||||||
.With("hid_display_split", HidDisplaySplit)
|
.With("hid_display_split", HidDisplaySplit)
|
||||||
.With("hid_display_caps", HidDisplayCaps)
|
.With("hid_display_caps", HidDisplayCaps)
|
||||||
.With("hid_list_padding", HidListPadding)
|
.With("hid_list_padding", HidListPadding)
|
||||||
|
.With("card_show_color_hex", CardShowColorHex)
|
||||||
.With("proxy_switch", ProxySwitch)
|
.With("proxy_switch", ProxySwitch)
|
||||||
.With("name_format", NameFormat)
|
.With("name_format", NameFormat)
|
||||||
);
|
);
|
||||||
|
|
@ -107,6 +109,9 @@ public class SystemConfigPatch: PatchObject
|
||||||
if (HidListPadding.IsPresent)
|
if (HidListPadding.IsPresent)
|
||||||
o.Add("hid_list_padding", HidListPadding.Value.ToUserString());
|
o.Add("hid_list_padding", HidListPadding.Value.ToUserString());
|
||||||
|
|
||||||
|
if (CardShowColorHex.IsPresent)
|
||||||
|
o.Add("card_show_color_hex", CardShowColorHex.Value);
|
||||||
|
|
||||||
if (ProxySwitch.IsPresent)
|
if (ProxySwitch.IsPresent)
|
||||||
o.Add("proxy_switch", ProxySwitch.Value.ToUserString());
|
o.Add("proxy_switch", ProxySwitch.Value.ToUserString());
|
||||||
|
|
||||||
|
|
@ -150,6 +155,9 @@ public class SystemConfigPatch: PatchObject
|
||||||
if (o.ContainsKey("hid_display_caps"))
|
if (o.ContainsKey("hid_display_caps"))
|
||||||
patch.HidDisplayCaps = o.Value<bool>("hid_display_caps");
|
patch.HidDisplayCaps = o.Value<bool>("hid_display_caps");
|
||||||
|
|
||||||
|
if (o.ContainsKey("card_show_color_hex"))
|
||||||
|
patch.CardShowColorHex = o.Value<bool>("card_show_color_hex");
|
||||||
|
|
||||||
if (o.ContainsKey("proxy_switch"))
|
if (o.ContainsKey("proxy_switch"))
|
||||||
patch.ProxySwitch = o.Value<string>("proxy_switch") switch
|
patch.ProxySwitch = o.Value<string>("proxy_switch") switch
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ public class SystemConfig
|
||||||
public bool ProxyErrorMessageEnabled { get; }
|
public bool ProxyErrorMessageEnabled { get; }
|
||||||
public bool HidDisplaySplit { get; }
|
public bool HidDisplaySplit { get; }
|
||||||
public bool HidDisplayCaps { get; }
|
public bool HidDisplayCaps { get; }
|
||||||
|
public bool CardShowColorHex { get; }
|
||||||
public HidPadFormat HidListPadding { get; }
|
public HidPadFormat HidListPadding { get; }
|
||||||
public ProxySwitchAction ProxySwitch { get; }
|
public ProxySwitchAction ProxySwitch { get; }
|
||||||
public string NameFormat { get; }
|
public string NameFormat { get; }
|
||||||
|
|
@ -60,6 +61,7 @@ public static class SystemConfigExt
|
||||||
o.Add("hid_display_split", cfg.HidDisplaySplit);
|
o.Add("hid_display_split", cfg.HidDisplaySplit);
|
||||||
o.Add("hid_display_caps", cfg.HidDisplayCaps);
|
o.Add("hid_display_caps", cfg.HidDisplayCaps);
|
||||||
o.Add("hid_list_padding", cfg.HidListPadding.ToUserString());
|
o.Add("hid_list_padding", cfg.HidListPadding.ToUserString());
|
||||||
|
o.Add("card_show_color_hex", cfg.CardShowColorHex);
|
||||||
o.Add("proxy_switch", cfg.ProxySwitch.ToUserString());
|
o.Add("proxy_switch", cfg.ProxySwitch.ToUserString());
|
||||||
o.Add("name_format", cfg.NameFormat);
|
o.Add("name_format", cfg.NameFormat);
|
||||||
|
|
||||||
|
|
|
||||||
6
crates/migrate/data/migrations/53.sql
Normal file
6
crates/migrate/data/migrations/53.sql
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
-- database version 53
|
||||||
|
-- add toggle for showing color codes on cv2 cards
|
||||||
|
|
||||||
|
alter table system_config add column card_show_color_hex bool default false;
|
||||||
|
|
||||||
|
update info set schema_version = 53;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue