mirror of
https://github.com/PluralKit/PluralKit.git
synced 2026-02-15 18:20:11 +00:00
Compare commits
11 commits
9495b95afa
...
981546647a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
981546647a | ||
|
|
83f866384a | ||
|
|
0983179240 | ||
|
|
49ce00e675 | ||
|
|
83f2d33c3d | ||
|
|
14f11bd1e9 | ||
|
|
39179f8e3a | ||
|
|
24361d9d2b | ||
|
|
9c99a0bc02 | ||
|
|
ebf8a40369 | ||
|
|
8bca02032f |
17 changed files with 658 additions and 73 deletions
|
|
@ -11,6 +11,7 @@ public record MessageComponent
|
|||
public string? Url { get; init; }
|
||||
public bool? Disabled { get; init; }
|
||||
public uint? AccentColor { get; init; }
|
||||
public int? Spacing { get; init; }
|
||||
public ComponentMedia? Media { get; init; }
|
||||
public ComponentMediaItem[]? Items { get; init; }
|
||||
|
||||
|
|
|
|||
|
|
@ -43,11 +43,10 @@ public class ApplicationCommandProxiedMessage
|
|||
if (channel == null)
|
||||
showContent = false;
|
||||
|
||||
var embeds = new List<Embed>();
|
||||
|
||||
var components = new List<MessageComponent>();
|
||||
var guild = await _cache.GetGuild(ctx.GuildId);
|
||||
if (msg.Member != null)
|
||||
embeds.Add(await _embeds.CreateMemberEmbed(
|
||||
components.AddRange(await _embeds.CreateMemberMessageComponents(
|
||||
msg.System,
|
||||
msg.Member,
|
||||
guild,
|
||||
|
|
@ -55,10 +54,12 @@ public class ApplicationCommandProxiedMessage
|
|||
LookupContext.ByNonOwner,
|
||||
DateTimeZone.Utc
|
||||
));
|
||||
|
||||
embeds.Add(await _embeds.CreateMessageInfoEmbed(msg, showContent, ctx.Config));
|
||||
|
||||
await ctx.Reply(embeds: embeds.ToArray());
|
||||
components.Add(new MessageComponent()
|
||||
{
|
||||
Type = ComponentType.Separator
|
||||
});
|
||||
components.AddRange(await _embeds.CreateMessageInfoMessageComponents(msg, showContent, ctx.Config));
|
||||
await ctx.Reply(components: components.ToArray());
|
||||
}
|
||||
|
||||
private async Task QueryCommandMessage(InteractionContext ctx)
|
||||
|
|
@ -68,11 +69,7 @@ public class ApplicationCommandProxiedMessage
|
|||
if (msg == null)
|
||||
throw Errors.MessageNotFound(messageId);
|
||||
|
||||
var embeds = new List<Embed>();
|
||||
|
||||
embeds.Add(await _embeds.CreateCommandMessageInfoEmbed(msg, true));
|
||||
|
||||
await ctx.Reply(embeds: embeds.ToArray());
|
||||
await ctx.Reply(components: await _embeds.CreateCommandMessageInfoMessageComponents(msg, true));
|
||||
}
|
||||
|
||||
public async Task DeleteMessage(InteractionContext ctx)
|
||||
|
|
|
|||
|
|
@ -181,6 +181,8 @@ public partial class CommandTree
|
|||
await ctx.Execute<Admin>(Admin, a => a.SystemRecover(ctx));
|
||||
else if (ctx.Match("sd", "systemdelete"))
|
||||
await ctx.Execute<Admin>(Admin, a => a.SystemDelete(ctx));
|
||||
else if (ctx.Match("sendmsg", "sendmessage"))
|
||||
await ctx.Execute<Admin>(Admin, a => a.SendAdminMessage(ctx));
|
||||
else if (ctx.Match("al", "abuselog"))
|
||||
await HandleAdminAbuseLogCommand(ctx);
|
||||
else
|
||||
|
|
@ -594,6 +596,8 @@ public partial class CommandTree
|
|||
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[] { "fronter", "front" }, new[] { "list" }, new[] { "format" }) || ctx.Match("fronterlistformat", "frontlistformat", "flf"))
|
||||
return ctx.Execute<Config>(null, m => m.FronterListFormat(ctx));
|
||||
if (ctx.MatchMultiple(new[] { "name" }, new[] { "format" }) || ctx.Match("nameformat", "nf"))
|
||||
return ctx.Execute<Config>(null, m => m.NameFormat(ctx));
|
||||
if (ctx.MatchMultiple(new[] { "member", "group" }, new[] { "limit" }) || ctx.Match("limit"))
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ using Myriad.Extensions;
|
|||
using Myriad.Cache;
|
||||
using Myriad.Rest;
|
||||
using Myriad.Types;
|
||||
using Myriad.Rest.Types.Requests;
|
||||
using Myriad.Rest.Exceptions;
|
||||
|
||||
using PluralKit.Core;
|
||||
|
||||
|
|
@ -19,12 +21,14 @@ public class Admin
|
|||
private readonly BotConfig _botConfig;
|
||||
private readonly DiscordApiClient _rest;
|
||||
private readonly IDiscordCache _cache;
|
||||
private readonly PrivateChannelService _dmCache;
|
||||
|
||||
public Admin(BotConfig botConfig, DiscordApiClient rest, IDiscordCache cache)
|
||||
public Admin(BotConfig botConfig, DiscordApiClient rest, IDiscordCache cache, PrivateChannelService dmCache)
|
||||
{
|
||||
_botConfig = botConfig;
|
||||
_rest = rest;
|
||||
_cache = cache;
|
||||
_dmCache = dmCache;
|
||||
}
|
||||
|
||||
private Task<(ulong Id, User? User)[]> GetUsers(IEnumerable<ulong> ids)
|
||||
|
|
@ -496,4 +500,34 @@ public class Admin
|
|||
await ctx.Repository.DeleteAbuseLog(abuseLog.Id);
|
||||
await ctx.Reply($"{Emojis.Success} Successfully deleted abuse log entry.");
|
||||
}
|
||||
|
||||
public async Task SendAdminMessage(Context ctx)
|
||||
{
|
||||
ctx.AssertBotAdmin();
|
||||
|
||||
var account = await ctx.MatchUser();
|
||||
if (account == null)
|
||||
throw new PKError("You must pass an account to send an admin message to (either ID or @mention).");
|
||||
if (!ctx.HasNext())
|
||||
throw new PKError("You must provide a message to send.");
|
||||
|
||||
var content = ctx.RemainderOrNull(false).NormalizeLineEndSpacing();
|
||||
var messageContent = $"## [Admin Message]\n\n{content}\n\nWe cannot read replies sent to this DM. If you wish to contact the staff team, please join the support server (<https://discord.gg/PczBt78>) or send us an email at <legal@pluralkit.me>.";
|
||||
|
||||
try
|
||||
{
|
||||
var dm = await _dmCache.GetOrCreateDmChannel(account.Id);
|
||||
var msg = await ctx.Rest.CreateMessage(dm,
|
||||
new MessageRequest { Content = messageContent }
|
||||
);
|
||||
}
|
||||
catch (ForbiddenException)
|
||||
{
|
||||
await ctx.Reply(
|
||||
$"{Emojis.Error} Error while sending DM.");
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.Reply($"{Emojis.Success} Successfully sent message.");
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ using NodaTime.TimeZones;
|
|||
using PluralKit.Core;
|
||||
|
||||
namespace PluralKit.Bot;
|
||||
|
||||
public class Config
|
||||
{
|
||||
private record PaginatedConfigItem(string Key, string Description, string? CurrentValue, string DefaultValue);
|
||||
|
|
@ -130,6 +131,13 @@ public class Config
|
|||
"disabled"
|
||||
));
|
||||
|
||||
items.Add(new(
|
||||
"fronter list format",
|
||||
"Whether to show the fronter list as full or short.",
|
||||
ctx.Config.FronterListFormat.ToUserString(),
|
||||
"short"
|
||||
));
|
||||
|
||||
items.Add(new(
|
||||
"Proxy Switch",
|
||||
"Switching behavior when proxy tags are used",
|
||||
|
|
@ -591,6 +599,29 @@ public class Config
|
|||
await ctx.Reply($"Showing color codes on system/member/group cards is now {EnabledDisabled(newVal)}.");
|
||||
}
|
||||
|
||||
public async Task FronterListFormat(Context ctx)
|
||||
{
|
||||
if (!ctx.HasNext())
|
||||
{
|
||||
var msg = $"Format of the fronter list is currently **{ctx.Config.FronterListFormat}**.";
|
||||
await ctx.Reply(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
var badInputError = "Valid list format settings are `short` or `full`.";
|
||||
if (ctx.Match("full", "f"))
|
||||
{
|
||||
await ctx.Repository.UpdateSystemConfig(ctx.System.Id, new() { FronterListFormat = SystemConfig.ListFormat.Full });
|
||||
await ctx.Reply($"Fronter lists are now formatted as `full`");
|
||||
}
|
||||
else if (ctx.Match("short", "s"))
|
||||
{
|
||||
await ctx.Repository.UpdateSystemConfig(ctx.System.Id, new() { FronterListFormat = SystemConfig.ListFormat.Short });
|
||||
await ctx.Reply($"Fronter lists are now formatted as `short`");
|
||||
}
|
||||
else throw new PKError(badInputError);
|
||||
}
|
||||
|
||||
public async Task ProxySwitch(Context ctx)
|
||||
{
|
||||
if (!ctx.HasNext())
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ public class GroupMember
|
|||
var opts = ctx.ParseListOptions(ctx.DirectLookupContextFor(target.System), ctx.LookupContextFor(target.System));
|
||||
opts.MemberFilter = target.Id;
|
||||
|
||||
var title = new StringBuilder($"Groups containing {target.Name} (`{target.DisplayHid(ctx.Config)}`) in ");
|
||||
var title = new StringBuilder($"Groups containing {target.NameFor(ctx)} (`{target.DisplayHid(ctx.Config)}`) in ");
|
||||
if (ctx.Guild != null)
|
||||
{
|
||||
var guildSettings = await ctx.Repository.GetSystemGuild(ctx.Guild.Id, targetSystem.Id);
|
||||
|
|
|
|||
|
|
@ -426,21 +426,33 @@ public class ProxiedMessage
|
|||
if (ctx.Match("author") || ctx.MatchFlag("author"))
|
||||
{
|
||||
var user = await _rest.GetUser(message.Message.Sender);
|
||||
var eb = new EmbedBuilder()
|
||||
.Author(new Embed.EmbedAuthor(
|
||||
user != null
|
||||
? $"{user.Username}#{user.Discriminator}"
|
||||
: $"Deleted user ${message.Message.Sender}",
|
||||
IconUrl: user != null ? user.AvatarUrl() : null))
|
||||
.Description(message.Message.Sender.ToString());
|
||||
if (ctx.MatchFlag("show-embed", "se"))
|
||||
{
|
||||
var eb = new EmbedBuilder()
|
||||
.Author(new Embed.EmbedAuthor(
|
||||
user != null
|
||||
? $"{user.Username}#{user.Discriminator}"
|
||||
: $"Deleted user ${message.Message.Sender}",
|
||||
IconUrl: user != null ? user.AvatarUrl() : null))
|
||||
.Description(message.Message.Sender.ToString());
|
||||
|
||||
await ctx.Reply(
|
||||
user != null ? $"{user.Mention()} ({user.Id})" : $"*(deleted user {message.Message.Sender})*",
|
||||
eb.Build());
|
||||
await ctx.Reply(
|
||||
user != null ? $"{user.Mention()} ({user.Id})" : $"*(deleted user {message.Message.Sender})*",
|
||||
eb.Build());
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.Reply(components: await _embeds.CreateAuthorMessageComponents(user, message));
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.Reply(embed: await _embeds.CreateMessageInfoEmbed(message, showContent, ctx.Config));
|
||||
if (ctx.MatchFlag("show-embed", "se"))
|
||||
{
|
||||
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)
|
||||
|
|
@ -472,6 +484,11 @@ public class ProxiedMessage
|
|||
else if (!await ctx.CheckPermissionsInGuildChannel(channel, PermissionSet.ViewChannel))
|
||||
showContent = false;
|
||||
|
||||
await ctx.Reply(embed: await _embeds.CreateCommandMessageInfoEmbed(msg, showContent));
|
||||
if (ctx.MatchFlag("show-embed", "se"))
|
||||
{
|
||||
await ctx.Reply(embed: await _embeds.CreateCommandMessageInfoEmbed(msg, showContent));
|
||||
return;
|
||||
}
|
||||
await ctx.Reply(components: await _embeds.CreateCommandMessageInfoMessageComponents(msg, showContent));
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,12 @@ public class SystemFront
|
|||
var sw = await ctx.Repository.GetLatestSwitch(system.Id);
|
||||
if (sw == null) throw Errors.NoRegisteredSwitches;
|
||||
|
||||
await ctx.Reply(embed: await _embeds.CreateFronterEmbed(sw, ctx.Zone, ctx.LookupContextFor(system.Id)));
|
||||
if (ctx.MatchFlag("show-embed", "se"))
|
||||
{
|
||||
await ctx.Reply(text: EmbedService.LEGACY_EMBED_WARNING, embed: await _embeds.CreateFronterEmbed(sw, ctx.Zone, ctx.LookupContextFor(system.Id)));
|
||||
return;
|
||||
}
|
||||
await ctx.Reply(components: await _embeds.CreateFronterMessageComponents(ctx, system, sw, ctx.LookupContextFor(system.Id)));
|
||||
}
|
||||
|
||||
public async Task SystemFrontHistory(Context ctx, PKSystem system)
|
||||
|
|
|
|||
|
|
@ -186,10 +186,9 @@ public class ReactionAdded: IEventHandler<MessageReactionAddEvent>
|
|||
{
|
||||
var dm = await _dmCache.GetOrCreateDmChannel(evt.UserId);
|
||||
|
||||
var embeds = new List<Embed>();
|
||||
|
||||
var components = new List<MessageComponent>();
|
||||
if (msg.Member != null)
|
||||
embeds.Add(await _embeds.CreateMemberEmbed(
|
||||
components.AddRange(await _embeds.CreateMemberMessageComponents(
|
||||
msg.System,
|
||||
msg.Member,
|
||||
guild,
|
||||
|
|
@ -197,10 +196,12 @@ public class ReactionAdded: IEventHandler<MessageReactionAddEvent>
|
|||
LookupContext.ByNonOwner,
|
||||
DateTimeZone.Utc
|
||||
));
|
||||
|
||||
embeds.Add(await _embeds.CreateMessageInfoEmbed(msg, true, config));
|
||||
|
||||
await _rest.CreateMessage(dm, new MessageRequest { Embeds = embeds.ToArray() });
|
||||
components.Add(new MessageComponent()
|
||||
{
|
||||
Type = ComponentType.Separator
|
||||
});
|
||||
components.AddRange(await _embeds.CreateMessageInfoMessageComponents(msg, true, config));
|
||||
await _rest.CreateMessage(dm, new MessageRequest { Components = components.ToArray(), Flags = Message.MessageFlags.IsComponentsV2, AllowedMentions = new AllowedMentions() });
|
||||
}
|
||||
catch (ForbiddenException) { } // No permissions to DM, can't check for this :(
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ public class EmbedService
|
|||
var countctx = LookupContext.ByNonOwner;
|
||||
if (cctx.MatchFlag("a", "all"))
|
||||
{
|
||||
if (system.Id == cctx.System.Id)
|
||||
if (system.Id == cctx.System?.Id)
|
||||
countctx = LookupContext.ByOwner;
|
||||
else
|
||||
throw Errors.LookupNotAllowed;
|
||||
|
|
@ -89,7 +89,7 @@ public class EmbedService
|
|||
if (system.MemberListPrivacy.CanAccess(ctx))
|
||||
{
|
||||
headerText += $"\n**Members:** {memberCount}";
|
||||
if (system.Id == cctx.System.Id)
|
||||
if (system.Id == cctx.System?.Id)
|
||||
if (memberCount > 0)
|
||||
headerText += $" (see `{cctx.DefaultPrefix}system list`)";
|
||||
else
|
||||
|
|
@ -192,7 +192,7 @@ public class EmbedService
|
|||
new MessageComponent()
|
||||
{
|
||||
Type = ComponentType.Text,
|
||||
Content = $"-# System ID: `{system.DisplayHid(cctx.Config)}`\n-# Created: {system.Created.FormatZoned(cctx.Zone)}",
|
||||
Content = $"-# System ID: `{system.DisplayHid(cctx.Config)}`\n-# Created: {DiscordUtils.InstantToTimestampString(system.Created)}",
|
||||
},
|
||||
],
|
||||
Accessory = new MessageComponent()
|
||||
|
|
@ -215,7 +215,7 @@ public class EmbedService
|
|||
var countctx = LookupContext.ByNonOwner;
|
||||
if (cctx.MatchFlag("a", "all"))
|
||||
{
|
||||
if (system.Id == cctx.System.Id)
|
||||
if (system.Id == cctx.System?.Id)
|
||||
countctx = LookupContext.ByOwner;
|
||||
else
|
||||
throw Errors.LookupNotAllowed;
|
||||
|
|
@ -469,7 +469,7 @@ public class EmbedService
|
|||
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)}" : "")}",
|
||||
Content = $"-# System ID: `{system.DisplayHid(ccfg)}` \u2219 Member ID: `{member.DisplayHid(ccfg)}`{(member.MetadataPrivacy.CanAccess(ctx) ? $"\n-# Created: {DiscordUtils.InstantToTimestampString(member.Created)}" : "")}",
|
||||
},
|
||||
],
|
||||
Accessory = new MessageComponent()
|
||||
|
|
@ -570,7 +570,7 @@ public class EmbedService
|
|||
var countctx = LookupContext.ByNonOwner;
|
||||
if (ctx.MatchFlag("a", "all"))
|
||||
{
|
||||
if (system.Id == ctx.System.Id)
|
||||
if (system.Id == ctx.System?.Id)
|
||||
countctx = LookupContext.ByOwner;
|
||||
else
|
||||
throw Errors.LookupNotAllowed;
|
||||
|
|
@ -588,7 +588,7 @@ public class EmbedService
|
|||
if (target.ListPrivacy.CanAccess(pctx))
|
||||
{
|
||||
headerText += $"\n**Members:** {memberCount}";
|
||||
if (system.Id == ctx.System.Id && memberCount == 0)
|
||||
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`)";
|
||||
|
|
@ -659,7 +659,7 @@ public class EmbedService
|
|||
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)}" : "")}",
|
||||
Content = $"-# System ID: `{system.DisplayHid(ctx.Config)}` \u2219 Group ID: `{target.DisplayHid(ctx.Config)}`{(target.MetadataPrivacy.CanAccess(pctx) ? $"\n-# Created: {DiscordUtils.InstantToTimestampString(target.Created)}" : "")}",
|
||||
},
|
||||
],
|
||||
Accessory = new MessageComponent()
|
||||
|
|
@ -680,7 +680,7 @@ public class EmbedService
|
|||
var countctx = LookupContext.ByNonOwner;
|
||||
if (ctx.MatchFlag("a", "all"))
|
||||
{
|
||||
if (system.Id == ctx.System.Id)
|
||||
if (system.Id == ctx.System?.Id)
|
||||
countctx = LookupContext.ByOwner;
|
||||
else
|
||||
throw Errors.LookupNotAllowed;
|
||||
|
|
@ -766,6 +766,337 @@ public class EmbedService
|
|||
.Build();
|
||||
}
|
||||
|
||||
public async Task<MessageComponent[]> CreateFronterMessageComponents(Context cctx, PKSystem system, PKSwitch sw, LookupContext ctx)
|
||||
{
|
||||
var formatType = cctx.Config.FronterListFormat;
|
||||
if (cctx.MatchFlag("short")) formatType = SystemConfig.ListFormat.Short;
|
||||
if (cctx.MatchFlag("full")) formatType = SystemConfig.ListFormat.Full;
|
||||
|
||||
if (formatType == SystemConfig.ListFormat.Full)
|
||||
{
|
||||
return await CreateFronterList(cctx, system, sw, ctx);
|
||||
}
|
||||
return await CreateFronterListShort(cctx, system, sw, ctx);
|
||||
}
|
||||
|
||||
public async Task<MessageComponent[]> CreateFronterListShort(Context cctx, PKSystem system, PKSwitch sw, LookupContext ctx)
|
||||
{
|
||||
var systemGuildSettings = await _repo.GetSystemGuild(cctx.Guild.Id, system.Id);
|
||||
var members = await _db.Execute(c => _repo.GetSwitchMembers(c, sw.Id).ToListAsync().AsTask());
|
||||
var memberStr = "*(no fronter)*";
|
||||
|
||||
if (members.Count > 0)
|
||||
{
|
||||
memberStr = "";
|
||||
foreach (var item in members.Select((value, i) => new { i, value }))
|
||||
{
|
||||
memberStr += item.i == 0 ? "" : ", ";
|
||||
if (memberStr.Length < 900)
|
||||
memberStr += item.value.NameFor(ctx);
|
||||
else
|
||||
{
|
||||
memberStr += $"*({members.Count - item.i} not shown)*";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
new MessageComponent(){
|
||||
Type = ComponentType.Text,
|
||||
Content = $"## Current fronter(s) in {systemGuildSettings.DisplayName ?? system.NameFor(ctx) ?? $"`{system.Hid}`"}"
|
||||
},
|
||||
new MessageComponent(){
|
||||
Type = ComponentType.Container,
|
||||
AccentColor = members.FirstOrDefault()?.Color?.ToDiscordColor(),
|
||||
Components = [
|
||||
new MessageComponent(){
|
||||
Type = ComponentType.Text,
|
||||
Content = $"**Current fronters:** \n{memberStr}"
|
||||
},
|
||||
new MessageComponent(){
|
||||
Type = ComponentType.Separator,
|
||||
},
|
||||
new MessageComponent()
|
||||
{
|
||||
Type = ComponentType.Text,
|
||||
Content = $"**Since:** {DiscordUtils.InstantToTimestampString(sw.Timestamp)} (<t:{sw.Timestamp.ToUnixTimeSeconds()}:R>)"
|
||||
}
|
||||
]
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
public async Task<MessageComponent[]> CreateFronterList(Context cctx, PKSystem system, PKSwitch sw, LookupContext ctx)
|
||||
{
|
||||
var systemGuildSettings = await _repo.GetSystemGuild(cctx.Guild.Id, system.Id);
|
||||
var members = await _db.Execute(c => _repo.GetSwitchMembers(c, sw.Id).ToListAsync().AsTask());
|
||||
var memberStr = "*(no fronter)*";
|
||||
var memberList = new List<MessageComponent>();
|
||||
|
||||
if (members.Count > 0)
|
||||
{
|
||||
memberStr = "";
|
||||
foreach (var item in members.Select((value, i) => new { i, value }))
|
||||
{
|
||||
if (item.i < 5)
|
||||
{
|
||||
var guildSettings = cctx.Guild != null ? await _repo.GetMemberGuild(cctx.Guild.Id, item.value.Id) : null;
|
||||
var avatar = guildSettings?.AvatarUrl ?? item.value.AvatarFor(ctx) ?? systemGuildSettings.AvatarUrl ?? system.AvatarFor(ctx);
|
||||
var pronouns = item.value.PronounsFor(ctx);
|
||||
var displayName = (guildSettings?.DisplayName) ?? item.value.DisplayName;
|
||||
|
||||
var headerText = "";
|
||||
if (!displayName.EmptyOrNull() && item.value.NamePrivacy.CanAccess(ctx))
|
||||
{
|
||||
headerText += $"**Display name:** {displayName}\n";
|
||||
}
|
||||
if (!pronouns.EmptyOrNull())
|
||||
{
|
||||
headerText += $"**Pronouns:** {item.value.PronounsFor(ctx)}\n";
|
||||
}
|
||||
headerText += $"[View on dashboard]({_coreConfig.DashboardBaseUrl}/profile/m/{item.value.Hid})";
|
||||
var nameItem = new MessageComponent()
|
||||
{
|
||||
Type = ComponentType.Text,
|
||||
Content = $"### {item.value.NameFor(ctx)}\n{headerText}"
|
||||
};
|
||||
if (!avatar.EmptyOrNull())
|
||||
{
|
||||
memberList.Add(new MessageComponent()
|
||||
{
|
||||
Type = ComponentType.Section,
|
||||
Components = [nameItem],
|
||||
Accessory = new MessageComponent()
|
||||
{
|
||||
Type = ComponentType.Thumbnail,
|
||||
Media = new ComponentMedia()
|
||||
{
|
||||
Url = avatar.TryGetCleanCdnUrl()
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
memberList.Add(nameItem);
|
||||
}
|
||||
|
||||
memberList.Add(new MessageComponent()
|
||||
{
|
||||
Type = ComponentType.Separator,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
memberStr += item.i == 5 ? "+ " : ", ";
|
||||
if (memberStr.Length < 900)
|
||||
memberStr += item.value.NameFor(ctx);
|
||||
else
|
||||
{
|
||||
memberStr += $"*({members.Count - item.i} not shown)*";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (memberStr.Length > 0)
|
||||
{
|
||||
memberList.Add(new MessageComponent()
|
||||
{
|
||||
Type = ComponentType.Text,
|
||||
Content = $"{memberStr}"
|
||||
});
|
||||
memberList.Add(new MessageComponent()
|
||||
{
|
||||
Type = ComponentType.Separator,
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
memberList.Add(new MessageComponent()
|
||||
{
|
||||
Type = ComponentType.Text,
|
||||
Content = "*(no fronter)*"
|
||||
});
|
||||
memberList.Add(new MessageComponent()
|
||||
{
|
||||
Type = ComponentType.Separator
|
||||
});
|
||||
}
|
||||
|
||||
memberList.Add(new MessageComponent()
|
||||
{
|
||||
Type = ComponentType.Text,
|
||||
Content = $"**Since:** {DiscordUtils.InstantToTimestampString(sw.Timestamp)} (<t:{sw.Timestamp.ToUnixTimeSeconds()}:R>)"
|
||||
});
|
||||
return [
|
||||
new MessageComponent(){
|
||||
Type = ComponentType.Text,
|
||||
Content = $"## Current fronter(s) in {systemGuildSettings.DisplayName ?? system.NameFor(ctx) ?? $"`{system.Hid}`"}"
|
||||
},
|
||||
new MessageComponent(){
|
||||
Type = ComponentType.Container,
|
||||
AccentColor = members.FirstOrDefault()?.Color?.ToDiscordColor(),
|
||||
Components = [
|
||||
.. memberList.ToArray()
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
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 == "" || avatarURL == null) ? 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} · {DiscordUtils.InstantToTimestampString(DiscordUtils.SnowflakeToInstant(msg.Message.Mid))}"
|
||||
};
|
||||
|
||||
return [
|
||||
new MessageComponent()
|
||||
{
|
||||
Type = ComponentType.Container,
|
||||
Components = [
|
||||
header,
|
||||
..body
|
||||
]
|
||||
},
|
||||
footer
|
||||
];
|
||||
}
|
||||
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);
|
||||
|
|
@ -852,6 +1183,106 @@ public class EmbedService
|
|||
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} · {DiscordUtils.InstantToTimestampString(DiscordUtils.SnowflakeToInstant(msg.OriginalMid))}"
|
||||
};
|
||||
|
||||
return [
|
||||
new MessageComponent(){
|
||||
Type = ComponentType.Container,
|
||||
Components = [
|
||||
..body
|
||||
]
|
||||
},
|
||||
footer
|
||||
];
|
||||
}
|
||||
public async Task<Embed> CreateCommandMessageInfoEmbed(Core.CommandMessage msg, bool showContent)
|
||||
{
|
||||
var content = "*(command message deleted or inaccessible)*";
|
||||
|
|
|
|||
|
|
@ -39,9 +39,15 @@ public static class DiscordUtils
|
|||
public static Instant SnowflakeToInstant(ulong snowflake) =>
|
||||
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) =>
|
||||
(ulong)(time - Instant.FromUtc(2015, 1, 1, 0, 0, 0)).TotalMilliseconds << 22;
|
||||
|
||||
public static string InstantToTimestampString(Instant time) =>
|
||||
$"<t:{time.ToUnixTimeSeconds()}:f>";
|
||||
|
||||
public static async Task CreateReactionsBulk(this DiscordApiClient rest, Message msg, string[] reactions)
|
||||
{
|
||||
foreach (var reaction in reactions)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Autofac;
|
|||
using Myriad.Cache;
|
||||
using Myriad.Gateway;
|
||||
using Myriad.Rest;
|
||||
using Myriad.Rest.Types;
|
||||
using Myriad.Types;
|
||||
|
||||
using PluralKit.Core;
|
||||
|
|
@ -76,6 +77,17 @@ public class InteractionContext
|
|||
});
|
||||
}
|
||||
|
||||
public async Task Reply(MessageComponent[] components = null, AllowedMentions? mentions = null)
|
||||
{
|
||||
await Respond(InteractionResponse.ResponseType.ChannelMessageWithSource,
|
||||
new InteractionApplicationCommandCallbackData
|
||||
{
|
||||
Components = components,
|
||||
Flags = Message.MessageFlags.Ephemeral | Message.MessageFlags.IsComponentsV2,
|
||||
AllowedMentions = mentions ?? new AllowedMentions()
|
||||
});
|
||||
}
|
||||
|
||||
public async Task Defer()
|
||||
{
|
||||
await Respond(InteractionResponse.ResponseType.DeferredChannelMessageWithSource,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ public class SystemConfigPatch: PatchObject
|
|||
public Partial<bool> CardShowColorHex { get; set; }
|
||||
public Partial<string?> NameFormat { get; set; }
|
||||
public Partial<SystemConfig.HidPadFormat> HidListPadding { get; set; }
|
||||
public Partial<SystemConfig.ListFormat> FronterListFormat { get; set; }
|
||||
public Partial<SystemConfig.ProxySwitchAction> ProxySwitch { get; set; }
|
||||
|
||||
public override Query Apply(Query q) => q.ApplyPatch(wrapper => wrapper
|
||||
|
|
@ -43,6 +44,7 @@ public class SystemConfigPatch: PatchObject
|
|||
.With("hid_display_caps", HidDisplayCaps)
|
||||
.With("hid_list_padding", HidListPadding)
|
||||
.With("card_show_color_hex", CardShowColorHex)
|
||||
.With("fronter_list_format", FronterListFormat)
|
||||
.With("proxy_switch", ProxySwitch)
|
||||
.With("name_format", NameFormat)
|
||||
);
|
||||
|
|
@ -112,6 +114,9 @@ public class SystemConfigPatch: PatchObject
|
|||
if (CardShowColorHex.IsPresent)
|
||||
o.Add("card_show_color_hex", CardShowColorHex.Value);
|
||||
|
||||
if (FronterListFormat.IsPresent)
|
||||
o.Add("fronter_list_format", FronterListFormat.Value.ToUserString());
|
||||
|
||||
if (ProxySwitch.IsPresent)
|
||||
o.Add("proxy_switch", ProxySwitch.Value.ToUserString());
|
||||
|
||||
|
|
@ -158,6 +163,13 @@ public class SystemConfigPatch: PatchObject
|
|||
if (o.ContainsKey("card_show_color_hex"))
|
||||
patch.CardShowColorHex = o.Value<bool>("card_show_color_hex");
|
||||
|
||||
if (o.ContainsKey("fronter_list_format"))
|
||||
patch.FronterListFormat = o.Value<string>("fronter_list_format") switch
|
||||
{
|
||||
"full" => SystemConfig.ListFormat.Full,
|
||||
_ => SystemConfig.ListFormat.Short,
|
||||
};
|
||||
|
||||
if (o.ContainsKey("proxy_switch"))
|
||||
patch.ProxySwitch = o.Value<string>("proxy_switch") switch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ public class SystemConfig
|
|||
public bool CardShowColorHex { get; }
|
||||
public HidPadFormat HidListPadding { get; }
|
||||
public ProxySwitchAction ProxySwitch { get; }
|
||||
public ListFormat FronterListFormat { get; }
|
||||
public string NameFormat { get; }
|
||||
|
||||
public enum HidPadFormat
|
||||
|
|
@ -40,6 +41,11 @@ public class SystemConfig
|
|||
New = 1,
|
||||
Add = 2,
|
||||
}
|
||||
public enum ListFormat
|
||||
{
|
||||
Short = 0,
|
||||
Full = 1,
|
||||
}
|
||||
}
|
||||
|
||||
public static class SystemConfigExt
|
||||
|
|
@ -62,6 +68,7 @@ public static class SystemConfigExt
|
|||
o.Add("hid_display_caps", cfg.HidDisplayCaps);
|
||||
o.Add("hid_list_padding", cfg.HidListPadding.ToUserString());
|
||||
o.Add("card_show_color_hex", cfg.CardShowColorHex);
|
||||
o.Add("fronter_list_format", cfg.FronterListFormat.ToUserString());
|
||||
o.Add("proxy_switch", cfg.ProxySwitch.ToUserString());
|
||||
o.Add("name_format", cfg.NameFormat);
|
||||
|
||||
|
|
@ -77,5 +84,5 @@ public static class SystemConfigExt
|
|||
}
|
||||
|
||||
public static string ToUserString(this SystemConfig.ProxySwitchAction val) => val.ToString().ToLower();
|
||||
|
||||
public static string ToUserString(this SystemConfig.ListFormat val) => val.ToString().ToLower();
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ use std::sync::Arc;
|
|||
use tokio::sync::mpsc::Sender;
|
||||
use tracing::{error, info, warn};
|
||||
use twilight_gateway::{
|
||||
CloseFrame, ConfigBuilder, Event, EventTypeFlags, Message, Shard, ShardId, create_iterator,
|
||||
ConfigBuilder, Event, EventTypeFlags, Message, Shard, ShardId, create_iterator,
|
||||
};
|
||||
use twilight_model::gateway::{
|
||||
Intents,
|
||||
|
|
@ -118,8 +118,11 @@ pub async fn runner(
|
|||
Message::Close(frame) => {
|
||||
let mut state_event = ShardStateEvent::Closed;
|
||||
let close_code = if let Some(close) = frame {
|
||||
if close == CloseFrame::RESUME {
|
||||
state_event = ShardStateEvent::Reconnect;
|
||||
match close.code {
|
||||
4000..=4003 | 4005..=4009 => {
|
||||
state_event = ShardStateEvent::Reconnect;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
close.code.to_string()
|
||||
} else {
|
||||
|
|
@ -176,32 +179,45 @@ pub async fn runner(
|
|||
)
|
||||
.increment(1);
|
||||
|
||||
// update shard state and discord cache
|
||||
if matches!(event, Event::Ready(_)) || matches!(event, Event::Resumed) {
|
||||
if let Err(error) = tx_state.try_send((
|
||||
shard.id(),
|
||||
ShardStateEvent::Other,
|
||||
Some(event.clone()),
|
||||
None,
|
||||
)) {
|
||||
tracing::error!(?error, "error updating shard state");
|
||||
// check for shard status events
|
||||
match event {
|
||||
Event::Ready(_) | Event::Resumed => {
|
||||
if let Err(error) = tx_state.try_send((
|
||||
shard.id(),
|
||||
ShardStateEvent::Other,
|
||||
Some(event.clone()),
|
||||
None,
|
||||
)) {
|
||||
tracing::error!(?error, "error updating shard state");
|
||||
}
|
||||
}
|
||||
}
|
||||
// need to do heartbeat separately, to get the latency
|
||||
let latency_num = shard
|
||||
.latency()
|
||||
.recent()
|
||||
.first()
|
||||
.map_or_else(|| 0, |d| d.as_millis()) as i32;
|
||||
if let Event::GatewayHeartbeatAck = event
|
||||
&& let Err(error) = tx_state.try_send((
|
||||
shard.id(),
|
||||
ShardStateEvent::Heartbeat,
|
||||
Some(event.clone()),
|
||||
Some(latency_num),
|
||||
))
|
||||
{
|
||||
tracing::error!(?error, "error updating shard state for latency");
|
||||
Event::GatewayReconnect => {
|
||||
if let Err(error) = tx_state.try_send((
|
||||
shard.id(),
|
||||
ShardStateEvent::Reconnect,
|
||||
Some(event.clone()),
|
||||
None,
|
||||
)) {
|
||||
tracing::error!(?error, "error updating shard state for reconnect");
|
||||
}
|
||||
}
|
||||
Event::GatewayHeartbeatAck => {
|
||||
// need to do heartbeat separately, to get the latency
|
||||
let latency_num = shard
|
||||
.latency()
|
||||
.recent()
|
||||
.first()
|
||||
.map_or_else(|| 0, |d| d.as_millis()) as i32;
|
||||
if let Err(error) = tx_state.try_send((
|
||||
shard.id(),
|
||||
ShardStateEvent::Heartbeat,
|
||||
Some(event.clone()),
|
||||
Some(latency_num),
|
||||
)) {
|
||||
tracing::error!(?error, "error updating shard state for latency");
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Event::Ready(_) = event {
|
||||
|
|
|
|||
6
crates/migrate/data/migrations/54.sql
Normal file
6
crates/migrate/data/migrations/54.sql
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
-- database version 54
|
||||
-- add short/full option for fronter list
|
||||
|
||||
alter table system_config add column fronter_list_format int default 0;
|
||||
|
||||
update info set schema_version = 54;
|
||||
|
|
@ -99,4 +99,9 @@ It is not possible to edit messages via ID. Please use the full link, or reply t
|
|||
You cannot reply-@ a proxied messages due to their nature as webhooks. If you want to "reply-@" a proxied message, you must react to the message with 🔔, 🛎, or 🏓. This will send a message from PluralKit that reads "Psst, MEMBER (@User), you have been pinged by @You", which will ping the Discord account behind the proxied message.
|
||||
|
||||
### Why do most of PluralKit's messages look blank or empty?
|
||||
A lot of PluralKit's command responses use Discord embeds. If you can't see them, it's likely you have embeds turned off. To change this, go into your discord settings and find the tab "Chat" under "App Settings". Find the setting "Show embeds and preview website links" and turn it on. If it's already on, try turning it off and then on again.
|
||||
PluralKit now uses Discord's "Components V2" for system/member/group cards - if the cards no longer show, your Discord app is too old to show the new components, and you should update it.
|
||||
A temporary workaround to show the old version of the cards exists as the -show-embed (or -se) flag to pk;system / pk;member / pk;group - however, we will be removing the old embed-based cards in the future (and as such, we will not add a config option to always use the old cards).
|
||||
|
||||
Please read the announcement post for more details: <https://pluralkit.me/posts/2025-09-08-components-v2/>
|
||||
|
||||
Some of PluralKit's command responses still use Discord embeds. If you can't see them, it's likely you have embeds turned off. To change this, go into your discord settings and find the tab "Chat" under "App Settings". Find the setting "Show embeds and preview website links" and turn it on. If it's already on, try turning it off and then on again.
|
||||
Loading…
Add table
Add a link
Reference in a new issue