From 0a474c43ebccdf59e7b9923afa83e89eaeb8c196 Mon Sep 17 00:00:00 2001 From: alyssa Date: Sun, 21 Dec 2025 01:19:02 -0500 Subject: [PATCH] feat: add basic premium scaffolding --- PluralKit.Bot/BotConfig.cs | 4 +++ PluralKit.Bot/CommandMeta/CommandTree.cs | 1 + .../CommandSystem/Context/Context.cs | 20 ++++++++++++++ PluralKit.Bot/Commands/Misc.cs | 26 +++++++++++++++++++ PluralKit.Bot/Services/EmbedService.cs | 6 +++-- PluralKit.Core/Models/SystemConfig.cs | 3 +++ crates/migrate/data/migrations/54.sql | 7 +++++ crates/models/src/system_config.rs | 5 ++++ 8 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 crates/migrate/data/migrations/54.sql diff --git a/PluralKit.Bot/BotConfig.cs b/PluralKit.Bot/BotConfig.cs index 1e6e0f0b..cb3d2dfd 100644 --- a/PluralKit.Bot/BotConfig.cs +++ b/PluralKit.Bot/BotConfig.cs @@ -36,6 +36,10 @@ public class BotConfig public bool IsBetaBot { get; set; } = false!; public string BetaBotAPIUrl { get; set; } + public String? PremiumSubscriberEmoji { get; set; } + public String? PremiumLifetimeEmoji { get; set; } + public String? PremiumDashboardUrl { get; set; } + public record ClusterSettings { // this is zero-indexed diff --git a/PluralKit.Bot/CommandMeta/CommandTree.cs b/PluralKit.Bot/CommandMeta/CommandTree.cs index d1ab6da0..b64e603a 100644 --- a/PluralKit.Bot/CommandMeta/CommandTree.cs +++ b/PluralKit.Bot/CommandMeta/CommandTree.cs @@ -92,6 +92,7 @@ public partial class CommandTree if (ctx.Match("sus")) return ctx.Execute(null, m => m.Sus(ctx)); if (ctx.Match("error")) return ctx.Execute(null, m => m.Error(ctx)); if (ctx.Match("stats", "status")) return ctx.Execute(null, m => m.Stats(ctx)); + if (ctx.Match("premium")) return ctx.Execute(null, m => m.Premium(ctx)); if (ctx.Match("permcheck")) return ctx.Execute(PermCheck, m => m.PermCheckGuild(ctx)); if (ctx.Match("proxycheck")) diff --git a/PluralKit.Bot/CommandSystem/Context/Context.cs b/PluralKit.Bot/CommandSystem/Context/Context.cs index f155c8dc..5e52a141 100644 --- a/PluralKit.Bot/CommandSystem/Context/Context.cs +++ b/PluralKit.Bot/CommandSystem/Context/Context.cs @@ -28,6 +28,8 @@ public class Context private Command? _currentCommand; + private BotConfig _botConfig; + public Context(ILifetimeScope provider, int shardId, Guild? guild, Channel channel, MessageCreateEvent message, int commandParseOffset, PKSystem senderSystem, SystemConfig config, GuildConfig? guildConfig, string[] prefixes) @@ -46,6 +48,7 @@ public class Context _metrics = provider.Resolve(); _provider = provider; _commandMessageService = provider.Resolve(); + _botConfig = provider.Resolve(); CommandPrefix = message.Content?.Substring(0, commandParseOffset); DefaultPrefix = prefixes[0]; Parameters = new Parameters(message.Content?.Substring(commandParseOffset)); @@ -74,6 +77,23 @@ public class Context public readonly SystemConfig Config; public DateTimeZone Zone => Config?.Zone ?? DateTimeZone.Utc; + public bool Premium + { + get + { + if (Config?.PremiumLifetime ?? false) return true; + // generate _this_ current instant _before_ the check, otherwise it will always be true... + var premiumUntil = Config?.PremiumUntil ?? SystemClock.Instance.GetCurrentInstant(); + return SystemClock.Instance.GetCurrentInstant() < premiumUntil; + } + } + + public string PremiumEmoji => (Config?.PremiumLifetime ?? false) + ? ($"<:lifetime_premium:{_botConfig.PremiumLifetimeEmoji}>" ?? "\u2729") + : Premium + ? ($"<:premium_subscriber:{_botConfig.PremiumSubscriberEmoji}>" ?? "\u2729") + : ""; + public readonly string CommandPrefix; public readonly string DefaultPrefix; public readonly Parameters Parameters; diff --git a/PluralKit.Bot/Commands/Misc.cs b/PluralKit.Bot/Commands/Misc.cs index 514c8999..ae2b9200 100644 --- a/PluralKit.Bot/Commands/Misc.cs +++ b/PluralKit.Bot/Commands/Misc.cs @@ -31,6 +31,32 @@ public class Misc _shards = shards; } + public async Task Premium(Context ctx) + { + ctx.CheckSystem(); + + String message; + + if (ctx.Config?.PremiumLifetime ?? false) + { + message = $"Your system has lifetime PluralKit Premium. {ctx.PremiumEmoji} Thanks for the support!"; + } + else if (ctx.Premium) + { + message = $"Your system has PluralKit Premium active until . {ctx.PremiumEmoji} Thanks for the support!"; + } + else + { + message = "PluralKit Premium is not currently active for your system."; + if (ctx.Config?.PremiumUntil != null) + { + message += $" The subscription expired at ()"; + } + } + + await ctx.Reply(message + $"\n\nManage your subscription at <{_botConfig.PremiumDashboardUrl}>"); + } + public async Task Invite(Context ctx) { var permissions = diff --git a/PluralKit.Bot/Services/EmbedService.cs b/PluralKit.Bot/Services/EmbedService.cs index f33d56f8..2dd08abf 100644 --- a/PluralKit.Bot/Services/EmbedService.cs +++ b/PluralKit.Bot/Services/EmbedService.cs @@ -21,14 +21,16 @@ public class EmbedService private readonly IDatabase _db; private readonly ModelRepository _repo; private readonly DiscordApiClient _rest; + private readonly BotConfig _config; private readonly CoreConfig _coreConfig; - public EmbedService(IDatabase db, ModelRepository repo, IDiscordCache cache, DiscordApiClient rest, CoreConfig coreConfig) + public EmbedService(IDatabase db, ModelRepository repo, IDiscordCache cache, DiscordApiClient rest, BotConfig config, CoreConfig coreConfig) { _db = db; _repo = repo; _cache = cache; _rest = rest; + _config = config; _coreConfig = coreConfig; } @@ -192,7 +194,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)}`{cctx.PremiumEmoji}\n-# Created: {system.Created.FormatZoned(cctx.Zone)}", }, ], Accessory = new MessageComponent() diff --git a/PluralKit.Core/Models/SystemConfig.cs b/PluralKit.Core/Models/SystemConfig.cs index dac7965f..39706042 100644 --- a/PluralKit.Core/Models/SystemConfig.cs +++ b/PluralKit.Core/Models/SystemConfig.cs @@ -28,6 +28,9 @@ public class SystemConfig public ProxySwitchAction ProxySwitch { get; } public string NameFormat { get; } + public bool PremiumLifetime { get; } + public Instant? PremiumUntil { get; } + public enum HidPadFormat { None = 0, diff --git a/crates/migrate/data/migrations/54.sql b/crates/migrate/data/migrations/54.sql new file mode 100644 index 00000000..6b2a70cc --- /dev/null +++ b/crates/migrate/data/migrations/54.sql @@ -0,0 +1,7 @@ +-- database version 54 +-- initial support for premium + +alter table system_config add column premium_until timestamp; +alter table system_config add column premium_lifetime bool default false; + +update info set schema_version = 54; \ No newline at end of file diff --git a/crates/models/src/system_config.rs b/crates/models/src/system_config.rs index 772d231d..08302d2e 100644 --- a/crates/models/src/system_config.rs +++ b/crates/models/src/system_config.rs @@ -1,3 +1,4 @@ +use chrono::NaiveDateTime; use pk_macros::pk_model; use sqlx::{postgres::PgTypeInfo, Database, Decode, Postgres, Type}; @@ -87,4 +88,8 @@ struct SystemConfig { name_format: Option, #[json = "description_templates"] description_templates: Vec, + #[json = "premium_until"] + premium_until: Option, + #[json = "premium_lifetime"] + premium_lifetime: bool }