feat: add basic premium scaffolding

This commit is contained in:
alyssa 2025-12-21 01:19:02 -05:00
parent 578c09c216
commit 5e462a0ca2
8 changed files with 70 additions and 2 deletions

View file

@ -36,6 +36,10 @@ public class BotConfig
public bool IsBetaBot { get; set; } = false!; public bool IsBetaBot { get; set; } = false!;
public string BetaBotAPIUrl { get; set; } public string BetaBotAPIUrl { get; set; }
public String? PremiumSubscriberEmoji { get; set; }
public String? PremiumLifetimeEmoji { get; set; }
public String? PremiumDashboardUrl { get; set; }
public record ClusterSettings public record ClusterSettings
{ {
// this is zero-indexed // this is zero-indexed

View file

@ -92,6 +92,7 @@ public partial class CommandTree
if (ctx.Match("sus")) return ctx.Execute<Fun>(null, m => m.Sus(ctx)); if (ctx.Match("sus")) return ctx.Execute<Fun>(null, m => m.Sus(ctx));
if (ctx.Match("error")) return ctx.Execute<Fun>(null, m => m.Error(ctx)); if (ctx.Match("error")) return ctx.Execute<Fun>(null, m => m.Error(ctx));
if (ctx.Match("stats", "status")) return ctx.Execute<Misc>(null, m => m.Stats(ctx)); if (ctx.Match("stats", "status")) return ctx.Execute<Misc>(null, m => m.Stats(ctx));
if (ctx.Match("premium")) return ctx.Execute<Misc>(null, m => m.Premium(ctx));
if (ctx.Match("permcheck")) if (ctx.Match("permcheck"))
return ctx.Execute<Checks>(PermCheck, m => m.PermCheckGuild(ctx)); return ctx.Execute<Checks>(PermCheck, m => m.PermCheckGuild(ctx));
if (ctx.Match("proxycheck")) if (ctx.Match("proxycheck"))

View file

@ -28,6 +28,8 @@ public class Context
private Command? _currentCommand; private Command? _currentCommand;
private BotConfig _botConfig;
public Context(ILifetimeScope provider, int shardId, Guild? guild, Channel channel, MessageCreateEvent message, public Context(ILifetimeScope provider, int shardId, Guild? guild, Channel channel, MessageCreateEvent message,
int commandParseOffset, PKSystem senderSystem, SystemConfig config, int commandParseOffset, PKSystem senderSystem, SystemConfig config,
GuildConfig? guildConfig, string[] prefixes) GuildConfig? guildConfig, string[] prefixes)
@ -46,6 +48,7 @@ public class Context
_metrics = provider.Resolve<IMetrics>(); _metrics = provider.Resolve<IMetrics>();
_provider = provider; _provider = provider;
_commandMessageService = provider.Resolve<CommandMessageService>(); _commandMessageService = provider.Resolve<CommandMessageService>();
_botConfig = provider.Resolve<BotConfig>();
CommandPrefix = message.Content?.Substring(0, commandParseOffset); CommandPrefix = message.Content?.Substring(0, commandParseOffset);
DefaultPrefix = prefixes[0]; DefaultPrefix = prefixes[0];
Parameters = new Parameters(message.Content?.Substring(commandParseOffset)); Parameters = new Parameters(message.Content?.Substring(commandParseOffset));
@ -74,6 +77,23 @@ public class Context
public readonly SystemConfig Config; public readonly SystemConfig Config;
public DateTimeZone Zone => Config?.Zone ?? DateTimeZone.Utc; 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 CommandPrefix;
public readonly string DefaultPrefix; public readonly string DefaultPrefix;
public readonly Parameters Parameters; public readonly Parameters Parameters;

View file

@ -31,6 +31,32 @@ public class Misc
_shards = shards; _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 <t:{ctx.Config.PremiumUntil?.ToUnixTimeSeconds()}>. {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 <t:{ctx.Config.PremiumUntil?.ToUnixTimeSeconds()}> (<t:{ctx.Config.PremiumUntil?.ToUnixTimeSeconds()}:R>)";
}
}
await ctx.Reply(message + $"\n\nManage your subscription at <{_botConfig.PremiumDashboardUrl}>");
}
public async Task Invite(Context ctx) public async Task Invite(Context ctx)
{ {
var permissions = var permissions =

View file

@ -21,14 +21,16 @@ public class EmbedService
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 BotConfig _config;
private readonly CoreConfig _coreConfig; 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; _db = db;
_repo = repo; _repo = repo;
_cache = cache; _cache = cache;
_rest = rest; _rest = rest;
_config = config;
_coreConfig = coreConfig; _coreConfig = coreConfig;
} }
@ -192,7 +194,7 @@ public class EmbedService
new MessageComponent() new MessageComponent()
{ {
Type = ComponentType.Text, 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() Accessory = new MessageComponent()

View file

@ -28,6 +28,9 @@ public class SystemConfig
public ProxySwitchAction ProxySwitch { get; } public ProxySwitchAction ProxySwitch { get; }
public string NameFormat { get; } public string NameFormat { get; }
public bool PremiumLifetime { get; }
public Instant? PremiumUntil { get; }
public enum HidPadFormat public enum HidPadFormat
{ {
None = 0, None = 0,

View file

@ -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;

View file

@ -1,3 +1,4 @@
use chrono::NaiveDateTime;
use pk_macros::pk_model; use pk_macros::pk_model;
use sqlx::{postgres::PgTypeInfo, Database, Decode, Postgres, Type}; use sqlx::{postgres::PgTypeInfo, Database, Decode, Postgres, Type};
@ -87,4 +88,8 @@ struct SystemConfig {
name_format: Option<String>, name_format: Option<String>,
#[json = "description_templates"] #[json = "description_templates"]
description_templates: Vec<String>, description_templates: Vec<String>,
#[json = "premium_until"]
premium_until: Option<NaiveDateTime>,
#[json = "premium_lifetime"]
premium_lifetime: bool
} }