Compare commits

...

8 commits

Author SHA1 Message Date
alyssa
09ed215e6c chore: clean up .net sentry exceptions
Some checks failed
Build and push Docker image / .net docker build (push) Has been cancelled
.net checks / run .net tests (push) Has been cancelled
.net checks / dotnet-format (push) Has been cancelled
2026-01-25 06:53:15 -05:00
alyssa
ce0983ba16 try shortening http idle timeout for discord client 2026-01-24 14:07:43 -05:00
alyssa
4a947c01fc fix: cast hid parameter to char(6) in GetMemberByHid query
Some checks failed
Build and push Docker image / .net docker build (push) Has been cancelled
.net checks / run .net tests (push) Has been cancelled
.net checks / dotnet-format (push) Has been cancelled
avoids casting the hid column, which does a full table scan, which is slow
2026-01-17 17:08:55 -05:00
Iris System
4973c0b992 docs: add patreon/bmac link to premium announce 2026-01-16 13:45:42 +13:00
Iris System
f61731a915 docs: add premium announcement 2026-01-16 11:13:00 +13:00
Petal Ladenson
3e1a310884
fix(bot): make reproxy inherit SUPPRESS_NOTIFICATIONS (#776)
Some checks failed
Build and push Docker image / .net docker build (push) Has been cancelled
.net checks / run .net tests (push) Has been cancelled
.net checks / dotnet-format (push) Has been cancelled
2026-01-14 15:41:27 +13:00
Petal Ladenson
952bb02285 fix(docs): Correctly display bellhop emoji in user guide 2026-01-12 11:57:50 -07:00
Petal Ladenson
9dfbf64dac fix(docs): Correctly reflect what permissions PluralKit needs to work 2026-01-12 11:57:31 -07:00
14 changed files with 151 additions and 29 deletions

View file

@ -35,7 +35,10 @@ public class BaseRestClient: IAsyncDisposable
if (!token.StartsWith("Bot "))
token = "Bot " + token;
Client = new HttpClient();
Client = new HttpClient(new SocketsHttpHandler
{
PooledConnectionIdleTimeout = TimeSpan.FromSeconds(3),
});
Client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", userAgent);
Client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", token);

View file

@ -2,6 +2,8 @@ using Autofac.Extensions.DependencyInjection;
using PluralKit.Core;
using Sentry;
using Serilog;
namespace PluralKit.API;
@ -21,9 +23,16 @@ public class Program
opts.Dsn = config.SentryUrl ?? "";
opts.Release = BuildInfoService.FullVersion;
opts.AutoSessionTracking = true;
// opts.DisableTaskUnobservedTaskExceptionCapture();
opts.DisableUnobservedTaskExceptionCapture();
});
TaskScheduler.UnobservedTaskException += (_, e) =>
{
foreach (var inner in e.Exception.Flatten().InnerExceptions)
SentrySdk.CaptureException(inner);
e.SetObserved();
};
await host.Services.GetRequiredService<RedisService>().InitAsync(config);
await host.RunAsync();
}

View file

@ -442,7 +442,7 @@ public class ProxiedMessage
return;
}
await ctx.Reply(components: await _embeds.CreateAuthorMessageComponents(user, message));
await ctx.Reply(components: _embeds.CreateAuthorMessageComponents(user, message));
return;
}

View file

@ -58,13 +58,19 @@ public class MessageEdited: IEventHandler<MessageUpdateEvent>
var channel = await _cache.TryGetChannel(guildId, evt.ChannelId); // todo: is this correct for message update?
if (channel == null)
throw new Exception("could not find self channel in MessageEdited event");
{
_logger.Warning("could not find self channel in MessageEdited event");
return;
}
if (!DiscordUtils.IsValidGuildChannel(channel))
return;
var rootChannel = await _cache.GetRootChannel(guildId, channel.Id);
var guild = await _cache.TryGetGuild(channel.GuildId!.Value);
if (guild == null)
throw new Exception("could not find self guild in MessageEdited event");
{
_logger.Warning("could not find self guild in MessageEdited event");
return;
}
var lastMessage = (await _lastMessageCache.GetLastMessage(evt.GuildId.HasValue ? evt.GuildId.Value ?? 0 : 0, evt.ChannelId))?.Current;
// Only react to the last message in the channel

View file

@ -45,9 +45,16 @@ public class Init
opts.Dsn = services.Resolve<CoreConfig>().SentryUrl ?? "";
opts.Release = BuildInfoService.FullVersion;
opts.AutoSessionTracking = true;
// opts.DisableTaskUnobservedTaskExceptionCapture();
opts.DisableUnobservedTaskExceptionCapture();
});
TaskScheduler.UnobservedTaskException += (_, e) =>
{
foreach (var inner in e.Exception.Flatten().InnerExceptions)
SentrySdk.CaptureException(inner);
e.SetObserved();
};
var config = services.Resolve<BotConfig>();
var coreConfig = services.Resolve<CoreConfig>();

View file

@ -324,6 +324,12 @@ public class ProxyService
// Mangle embeds (for reply embed color changing)
var mangledEmbeds = originalMsg.Embeds!.Select(embed => MangleReproxyEmbed(embed, member)).Where(embed => embed != null).ToArray();
Message.MessageFlags flags = 0;
if (originalMsg.Flags.HasFlag(Message.MessageFlags.SuppressNotifications))
flags |= Message.MessageFlags.SuppressNotifications;
if (originalMsg.Flags.HasFlag(Message.MessageFlags.VoiceMessage))
flags |= Message.MessageFlags.VoiceMessage;
// Send the reproxied webhook
var proxyMessage = await _webhookExecutor.ExecuteWebhook(new ProxyRequest
{
@ -339,7 +345,7 @@ public class ProxyService
Embeds = mangledEmbeds,
Stickers = originalMsg.StickerItems!,
AllowEveryone = allowEveryone,
Flags = originalMsg.Flags.HasFlag(Message.MessageFlags.VoiceMessage) ? Message.MessageFlags.VoiceMessage : null,
Flags = flags,
Tts = tts,
Poll = originalMsg.Poll,
});

View file

@ -1004,7 +1004,7 @@ public class EmbedService
return eb.Build();
}
public async Task<MessageComponent[]> CreateAuthorMessageComponents(User? user, FullMessage msg)
public MessageComponent[] CreateAuthorMessageComponents(User? user, FullMessage msg)
{
MessageComponent authorInfo;
var author = user != null
@ -1049,16 +1049,15 @@ public class EmbedService
authorInfo,
]
};
return (
[
new MessageComponent()
{
Type = ComponentType.Text,
Content = user != null ? $"{user.Mention()} ({user.Id})" : $"*(deleted user {msg.Message.Sender})*"
},
container
]
);
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)

View file

@ -32,7 +32,7 @@ public class LastMessageCacheService
public async Task<CacheEntry?> GetLastMessage(ulong guild, ulong channel)
{
if (_maybeHttp is HttpDiscordCache)
return await (_maybeHttp as HttpDiscordCache).GetLastMessage<CacheEntry>(guild, channel);
return await (_maybeHttp as HttpDiscordCache)!.GetLastMessage<CacheEntry>(guild, channel);
return _cache.TryGetValue(channel, out var message) ? message : null;
}

View file

@ -13,7 +13,7 @@ public partial class ModelRepository
public Task<PKMember?> GetMemberByHid(string hid, SystemId? system = null)
{
var query = new Query("members").Where("hid", hid.ToLower());
var query = new Query("members").WhereRaw("hid = (?)::char(6)", hid.ToLower());
if (system != null)
query = query.Where("system", system);
return _db.QueryFirst<PKMember?>(query);

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

View file

@ -0,0 +1,89 @@
---
title: Announcing PluralKit Premium
permalink: /posts/2026-01-16-pluralkit-premium/
---
## Announcing PluralKit Premium
As we've teased earlier in [the support server](https://discord.gg/PczBt78),
we will be adding a "premium" subscription tier to PluralKit. We do want to
assure everyone, however, that **the bot will always be free to use, and no
existing features (or new core features) will ever be paywalled.**
Our goal for PK Premium is to have the income from subscriptions cover all the
running costs of the bot - which, up until now, has been paid for partly by
donations to our [Patreon](https://patreon.com/pluralkit) /
[Buy Me A Coffee](https://buymeacoffee.com/pluralkit); and partly out of the
pockets of the developers.
PluralKit Premium will cost **US$5/month** (plus tax), and will be **launching
before the end of February 2026**.
PluralKit Premium will offer both cosmetic perks, and "power user" features.
At launch, PK Premium will offer the following:
- the ability to set custom system/member/group IDs,
- lossless, higher resolution image hosting on PluralKit's CDN,
- the ability to upload avatars/banners directly from the PluralKit Dashboard,
- and a badge on your PluralKit system card to show off your support.
We will be adding more perks to PK Premium in future - including, but not
limited to:
- more description customisation options,
- automatic regex-based proxy text substitution ("automated typing quirks"),
- and more!
<div style="text-align:center;">
![PluralKit Premium teaser screenshot](../assets/premiumTeaser.png)
</div>
### FAQs
#### I didn't read any of the stuff above, give me a tl;dr!
- PK Premium is launching before the end of February 2026, at US$5/month (+ tax)
- No existing features, or new core features, will be paywalled - the premium
subscription offers cosmetic perks and power-user features only
- Features at launch include custom IDs; high-resolution image hosting; and
direct image upload from the web dashboard
- More premium features are still to come!
#### Can I pay yearly?
Yes, there will be a yearly subscription option. There is no discount for paying
yearly.
#### Can I gift a PK Premium subscription to someone?
Not at launch, but we will likely be revisiting this in future.
#### How will ID changes work?
There will be bot commands for changing your own system, member, and group IDs.
Each month of PK Premium you pay for will grant you a number of ID change "tokens,"
and each ID you change uses one of those "tokens." The exact number of ID changes
you will get each month has not yet been confirmed, but they will stack over time.
In addition to the "token" system, to ensure fairness, there is a cap on the number
of IDs you can change in a 24-hour period.
#### Can I transfer custom IDs to another system?
No, sorry.
#### If I subscribe; customise my IDs; and then cancel, do I keep my custom IDs?
Yes!
#### How does this affect PluralKit being open source?
PluralKit will remain open source, and there is no change to the licensing.
Premium features are included in the open source code.
#### I have another question!
Please ask in [#bot-support in the PluralKit support server](https://discord.gg/PczBt78),
and we'll update this FAQ!

View file

@ -4,6 +4,7 @@ title: Announcements & other posts
# Announcements & other posts
- 2026-01-16: [Announcing PluralKit Premium](/posts/2026-01-16-pluralkit-premium/)
- 2025-09-08: [on the switch to Components V2](/posts/2025-09-08-components-v2/)
- 2025-01-14: [january 2025 funding update](/posts/2025-01-14-funding-update/)
- 2024-12-05: [late 2024 downtime notes & funding update](/posts/2024-12-05-downtime-notes/)

View file

@ -2,21 +2,23 @@
PluralKit requires some channel permissions in order to function properly:
- Message proxying requires the **Manage Messages** and **Manage Webhooks** permissions in a channel.
- Most commands require the **Embed Links**, **Attach Files** and **Add Reactions** permissions to function properly.
- *Everything* PluralKit does aside from the Message Info app command requires **View Channel** permissions in a channel.
- Message proxying requires the **Manage Messages**, **Manage Webhooks**, and **Send Messages** permissions in a channel.
- Most commands require the **Embed Links** and **Add Reactions** permissions to function properly.
- Commands with reaction menus also require **Manage Messages** to remove reactions after clicking.
- Commands executed via reactions (for example the :x:, :bell:, and :question: reactions, as well as any commands with reaction menus) need **Read Message History** to be able to see that reactions were added.
- A couple commands (`pk;s color` and `pk;m <name> color`) currently require **Attach Files**.
- [Proxy logging](/staff/logging) requires the **Send Messages** permission in the log channel.
- [Log cleanup](/staff/compatibility/#log-cleanup) requires the **Manage Messages** permission in the log channels.
Denying the **Send Messages** permission will *not* stop the bot from proxying, although it will prevent it from sending command responses. Denying the **Read Messages** permission will, as any other bot, prevent the bot from interacting in that channel at all.
## Webhook permissions
Webhooks exist outside of the normal Discord permissions system, and (with a few exceptions) it's not possible to modify their permissions.
Webhooks exist outside of the normal Discord permissions system, but as of August 2022 they mostly follow the permissions of the webhook owner (in this case, PluralKit).
However, PluralKit will make an attempt to apply the sender account's permissions to proxied messages. For example, role mentions, `@everyone`, and `@here`
PluralKit will also make an attempt to apply the sender account's permissions to proxied messages. For example, role mentions, `@everyone`, and `@here`
will only function if the sender account has that permission. The same applies to link embeds.
For external emojis to work in proxied messages, the `@everyone` role must have the "Use External Emojis" permission. If it still doesn't work, check if the permission was denied in channel-specific permission settings.
For external emojis to work in proxied messages, PluralKit or one of its roles must have the "Use External Emojis" permission. If it still doesn't work,
check if the permission was denied in channel-specific permission settings. PluralKit must also be in the server the external emoji belongs to.
## Troubleshooting
@ -30,4 +32,4 @@ For example:
pk;debug permissions 466707357099884544
You can find this ID [by enabling Developer Mode and right-clicking (or long-pressing) on the server icon](https://discordia.me/developer-mode).
You can find this ID [by enabling Developer Mode and right-clicking (or long-pressing) on the server icon](https://discordia.me/developer-mode).

View file

@ -453,7 +453,7 @@ You can
#### Pinging the user who sent it
If you'd like to "ping" the account behind a proxied message without having to query the message and ping them yourself,
you can react to the message with the `:bell:` :bell: emoji (or `:bellhop:` :bellhop:, `:exclamation:` :exclamation:, or even `:ping_pong:` :ping_pong:), and PluralKit will ping the relevant member and account in the same channel on your behalf with a link to the message you reacted to.
you can react to the message with the `:bell:` :bell: emoji (or `:bellhop:` :bellhop_bell:, `:exclamation:` :exclamation:, or even `:ping_pong:` :ping_pong:), and PluralKit will ping the relevant member and account in the same channel on your behalf with a link to the message you reacted to.
## Autoproxy
The bot's *autoproxy* feature allows you to have messages be proxied without directly including the proxy tags. Autoproxy can be set up in various ways. There are three autoproxy modes currently implemented: