From aba5c4fbfdb9c501fbb64f374b6e040de70f2054 Mon Sep 17 00:00:00 2001 From: Toastie <toastie@toastiet0ast.com> Date: Sun, 30 Mar 2025 16:16:57 +1300 Subject: [PATCH] simplified quota system --- CHANGELOG.md | 1 + .../Games/ChatterBot/ChatterBotService.cs | 10 +- .../Modules/Patronage/PatronageCommands.cs | 54 +--- .../Modules/Patronage/PatronageService.cs | 290 +++++++----------- .../LiveChannel/LiveChannelCommands.cs | 4 +- .../Utility/LiveChannel/LiveChannelService.cs | 25 +- .../Modules/Utility/Quote/QuoteCommands.cs | 3 +- .../_common/Patronage/FeatureLimitKey.cs | 7 - .../_common/Patronage/IPatronageService.cs | 8 +- .../_common/Patronage/PatronConfigData.cs | 6 +- src/EllieBot/_common/Patronage/PatronTier.cs | 2 + src/EllieBot/_common/Patronage/QuotaLimit.cs | 23 -- src/EllieBot/_common/Patronage/QuotaPer.cs | 9 - .../_common/TypeReaders/Rgba32TypeReader.cs | 21 -- src/EllieBot/data/commandlist.json | 16 - src/EllieBot/data/patron.yml | 53 ++-- 16 files changed, 166 insertions(+), 366 deletions(-) delete mode 100644 src/EllieBot/_common/Patronage/QuotaLimit.cs delete mode 100644 src/EllieBot/_common/Patronage/QuotaPer.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index c7be193..465e1dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - .notify will now let you know if you can't set a notify message due to a missing channel - `.say` will no longer reply - `.vote` and `.timely` will now show active bonuses +- `.lcha` (live channel) limit increased to 5 ### Fixed - Fixed `.antispamignore` restart persistence diff --git a/src/EllieBot/Modules/Games/ChatterBot/ChatterBotService.cs b/src/EllieBot/Modules/Games/ChatterBot/ChatterBotService.cs index e37046c..3562d56 100644 --- a/src/EllieBot/Modules/Games/ChatterBot/ChatterBotService.cs +++ b/src/EllieBot/Modules/Games/ChatterBot/ChatterBotService.cs @@ -145,7 +145,7 @@ public class ChatterBotService : IExecOnMessage, IReadyExecutor if (!res.IsAllowed) return false; - if (!await _ps.LimitHitAsync(LimitedFeatureName.ChatBot, usrMsg.Author.Id, 2048 / 2)) + if (!await _ps.LimitHitAsync("ai", guild.OwnerId, 1)) { // limit exceeded return false; @@ -156,14 +156,6 @@ public class ChatterBotService : IExecOnMessage, IReadyExecutor if (response.TryPickT0(out var result, out var error)) { - // calculate the diff in case we overestimated user's usage - var inTokens = (result.TokensIn - 2048) / 2; - - // add the output tokens to the limit - await _ps.LimitForceHit(LimitedFeatureName.ChatBot, - usrMsg.Author.Id, - (inTokens) + (result.TokensOut / 2 * 3)); - await _sender.Response(channel) .Confirm(result.Text) .SendAsync(); diff --git a/src/EllieBot/Modules/Patronage/PatronageCommands.cs b/src/EllieBot/Modules/Patronage/PatronageCommands.cs index 64850f3..892944d 100644 --- a/src/EllieBot/Modules/Patronage/PatronageCommands.cs +++ b/src/EllieBot/Modules/Patronage/PatronageCommands.cs @@ -36,11 +36,11 @@ public partial class Help var result = await _service.SendMessageToPatronsAsync(tierAndHigher, message); await Response() - .Confirm(strs.patron_msg_sent( - Format.Code(tierAndHigher.ToString()), - Format.Bold(result.Success.ToString()), - Format.Bold(result.Failed.ToString()))) - .SendAsync(); + .Confirm(strs.patron_msg_sent( + Format.Code(tierAndHigher.ToString()), + Format.Bold(result.Success.ToString()), + Format.Bold(result.Failed.ToString()))) + .SendAsync(); } // [OwnerOnly] @@ -73,32 +73,24 @@ public partial class Help var maybePatron = await _service.GetPatronAsync(user.Id); - var quotaStats = await _service.LimitStats(user.Id); - var eb = CreateEmbed() - .WithAuthor(user) - .WithTitle(GetText(strs.patron_info)) - .WithOkColor(); + .WithAuthor(user) + .WithTitle(GetText(strs.patron_info)) + .WithOkColor(); - if (quotaStats.Count == 0 || maybePatron is not { } patron) + if (maybePatron is not { } patron) { - eb.WithDescription(GetText(strs.no_quota_found)); + eb.WithDescription("You don't have an active subscription"); } else { eb.AddField(GetText(strs.tier), Format.Bold(patron.Tier.ToFullName()), true) - .AddField(GetText(strs.pledge), $"**{patron.Amount / 100.0f:N1}$**", true); + .AddField(GetText(strs.pledge), $"**{patron.Amount / 100.0f:N1}$**", true); if (patron.Tier != PatronTier.None) eb.AddField(GetText(strs.expires), patron.ValidThru.AddDays(1).ToShortAndRelativeTimestampTag(), true); - - eb.AddField(GetText(strs.quotas), "", false); - - var text = GetQuotaList(quotaStats); - if (!string.IsNullOrWhiteSpace(text)) - eb.AddField(GetText(strs.modules), text, true); } @@ -112,29 +104,5 @@ public partial class Help await Response().Error(strs.cant_dm).SendAsync(); } } - - private string GetQuotaList( - IReadOnlyDictionary<LimitedFeatureName, (int Cur, QuotaLimit Quota)> featureQuotaStats) - { - var text = string.Empty; - foreach (var (key, (cur, quota)) in featureQuotaStats) - { - text += $"\n\t`{key}`\n"; - if (quota.QuotaPeriod == QuotaPer.PerHour) - text += $" {cur}/{(quota.Quota == -1 ? "∞" : quota.Quota)} {QuotaPeriodToString(quota.QuotaPeriod)}\n"; - } - - return text; - } - - public string QuotaPeriodToString(QuotaPer per) - => per switch - { - QuotaPer.PerHour => "per hour", - QuotaPer.PerDay => "per day", - QuotaPer.PerMonth => "per month", - QuotaPer.Total => "total", - _ => throw new ArgumentOutOfRangeException(nameof(per), per, null) - }; } } \ No newline at end of file diff --git a/src/EllieBot/Modules/Patronage/PatronageService.cs b/src/EllieBot/Modules/Patronage/PatronageService.cs index 3a30c99..b9b299a 100644 --- a/src/EllieBot/Modules/Patronage/PatronageService.cs +++ b/src/EllieBot/Modules/Patronage/PatronageService.cs @@ -98,21 +98,21 @@ public sealed class PatronageService try { var dbPatron = await ctx.GetTable<PatronUser>() - .FirstOrDefaultAsync(x - => x.UniquePlatformUserId == subscriber.UniquePlatformUserId); + .FirstOrDefaultAsync(x + => x.UniquePlatformUserId == subscriber.UniquePlatformUserId); if (dbPatron is null) { // if the user is not in the database alrady dbPatron = await ctx.GetTable<PatronUser>() - .InsertWithOutputAsync(() => new() - { - UniquePlatformUserId = subscriber.UniquePlatformUserId, - UserId = subscriber.UserId, - AmountCents = subscriber.Cents, - LastCharge = lastChargeUtc, - ValidThru = dateInOneMonth, - }); + .InsertWithOutputAsync(() => new() + { + UniquePlatformUserId = subscriber.UniquePlatformUserId, + UserId = subscriber.UserId, + AmountCents = subscriber.Cents, + LastCharge = lastChargeUtc, + ValidThru = dateInOneMonth, + }); // await tran.CommitAsync(); @@ -129,18 +129,18 @@ public sealed class PatronageService // if his sub would end in teh future, extend it by one month. // if it's not, just add 1 month to the last charge date await ctx.GetTable<PatronUser>() - .Where(x => x.UniquePlatformUserId - == subscriber.UniquePlatformUserId) - .UpdateAsync(old => new() - { - UserId = subscriber.UserId, - AmountCents = subscriber.Cents, - LastCharge = lastChargeUtc, - ValidThru = old.ValidThru >= todayDate - // ? Sql.DateAdd(Sql.DateParts.Month, 1, old.ValidThru).Value - ? old.ValidThru.AddMonths(1) - : dateInOneMonth, - }); + .Where(x => x.UniquePlatformUserId + == subscriber.UniquePlatformUserId) + .UpdateAsync(old => new() + { + UserId = subscriber.UserId, + AmountCents = subscriber.Cents, + LastCharge = lastChargeUtc, + ValidThru = old.ValidThru >= todayDate + // ? Sql.DateAdd(Sql.DateParts.Month, 1, old.ValidThru).Value + ? old.ValidThru.AddMonths(1) + : dateInOneMonth, + }); dbPatron.UserId = subscriber.UserId; @@ -158,14 +158,14 @@ public sealed class PatronageService var cents = subscriber.Cents; // the user updated the pledge or changed the connected discord account await ctx.GetTable<PatronUser>() - .Where(x => x.UniquePlatformUserId == subscriber.UniquePlatformUserId) - .UpdateAsync(old => new() - { - UserId = subscriber.UserId, - AmountCents = cents, - LastCharge = lastChargeUtc, - ValidThru = old.ValidThru, - }); + .Where(x => x.UniquePlatformUserId == subscriber.UniquePlatformUserId) + .UpdateAsync(old => new() + { + UserId = subscriber.UserId, + AmountCents = cents, + LastCharge = lastChargeUtc, + ValidThru = old.ValidThru, + }); var newPatron = dbPatron.Clone(); newPatron.AmountCents = cents; @@ -192,19 +192,19 @@ public sealed class PatronageService { // if the subscription is refunded, Disable user's valid thru var changedCount = await ctx.GetTable<PatronUser>() - .Where(x => x.UniquePlatformUserId == patron.UniquePlatformUserId - && x.ValidThru != expiredDate) - .UpdateAsync(old => new() - { - ValidThru = expiredDate - }); + .Where(x => x.UniquePlatformUserId == patron.UniquePlatformUserId + && x.ValidThru != expiredDate) + .UpdateAsync(old => new() + { + ValidThru = expiredDate + }); if (changedCount == 0) continue; var updated = await ctx.GetTable<PatronUser>() - .Where(x => x.UniquePlatformUserId == patron.UniquePlatformUserId) - .FirstAsync(); + .Where(x => x.UniquePlatformUserId == patron.UniquePlatformUserId) + .FirstAsync(); await OnPatronRefunded(PatronUserToPatron(updated)); } @@ -218,8 +218,8 @@ public sealed class PatronageService // is subscribed on multiple platforms // or if there are multiple users on the same platform who connected the same discord account?! var users = await ctx.GetTable<PatronUser>() - .Where(x => x.UserId == userId) - .ToListAsync(); + .Where(x => x.UserId == userId) + .ToListAsync(); // first find all active subscriptions // and return the one with the highest amount @@ -236,136 +236,56 @@ public sealed class PatronageService return PatronUserToPatron(max); } - public async Task<bool> LimitHitAsync(LimitedFeatureName key, ulong userId, int amount = 1) + private Func<string, ulong, TypedKey<int>> Limitkey + => (name, userId) => new($"patron_limit:{userId}:{name}"); + + public async Task<bool> LimitHitAsync(string name, ulong userId, int defaultMax) { - if (_creds.GetCreds().IsOwner(userId)) + var data = _pConf.Data; + if (!data.IsEnabled) return true; - if (!_pConf.Data.IsEnabled) + var limit = await GetUserLimit(name, userId, defaultMax); + + if (limit == -1) return true; + + var timeUntilTomorrow = (DateTime.UtcNow.Date.AddDays(1) - DateTime.UtcNow); + var soFar = await _cache.GetOrAddAsync(Limitkey(name, userId), + () => Task.FromResult(0), + expiry: timeUntilTomorrow); - var userLimit = await GetUserLimit(key, userId); - - if (userLimit.Quota == 0) + if (soFar >= limit) return false; - if (userLimit.Quota == -1) - return true; - - return await TryAddLimit(key, userLimit, userId, amount); + await _cache.AddAsync(Limitkey(name, userId), soFar + 1, timeUntilTomorrow, overwrite: true); + return true; } - public async Task<bool> LimitForceHit(LimitedFeatureName key, ulong userId, int amount) + public async Task<int> GetUserLimit(string name, ulong userId, int defaultMax) { - if (_creds.GetCreds().IsOwner(userId)) - return true; + var data = _pConf.Data; + if (!data.IsEnabled || _creds.GetCreds().OwnerIds.Contains(userId)) + return defaultMax; - if (!_pConf.Data.IsEnabled) - return true; + var mPatron = await GetPatronAsync(userId); - var userLimit = await GetUserLimit(key, userId); - - var cacheKey = CreateKey(key, userId); - await _cache.GetOrAddAsync(cacheKey, () => Task.FromResult(0), GetExpiry(userLimit)); - - return await TryAddLimit(key, userLimit, userId, amount); - } - - private async Task<bool> TryAddLimit( - LimitedFeatureName key, - QuotaLimit userLimit, - ulong userId, - int amount) - { - var cacheKey = CreateKey(key, userId); - var cur = await _cache.GetOrAddAsync(cacheKey, () => Task.FromResult(0), GetExpiry(userLimit)); - - if (cur + amount < userLimit.Quota) + if (mPatron is not { } patron || !patron.IsActive) { - await _cache.AddAsync(cacheKey, cur + amount); - return true; + if (data.Quotas.TryGetValue(PatronTier.I, out var limits) + && limits.TryGetValue(name, out var limit)) + return limit; + + return 0; } - return false; + if (data.Quotas.TryGetValue(patron.Tier, out var plimits) + && plimits.TryGetValue(name, out var plimit)) + return plimit; + + return 0; } - private TimeSpan? GetExpiry(QuotaLimit userLimit) - { - var now = DateTime.UtcNow; - switch (userLimit.QuotaPeriod) - { - case QuotaPer.PerHour: - return TimeSpan.FromMinutes(60 - now.Minute); - case QuotaPer.PerDay: - return TimeSpan.FromMinutes((24 * 60) - ((now.Hour * 60) + now.Minute)); - case QuotaPer.PerMonth: - var firstOfNextMonth = now.FirstOfNextMonth(); - return firstOfNextMonth - now; - default: - return null; - } - } - - private TypedKey<int> CreateKey(LimitedFeatureName key, ulong userId) - => new($"limited_feature:{key}:{userId}"); - - private readonly QuotaLimit _emptyQuota = new QuotaLimit() - { - Quota = 0, - QuotaPeriod = QuotaPer.PerDay, - }; - - private readonly QuotaLimit _infiniteQuota = new QuotaLimit() - { - Quota = -1, - QuotaPeriod = QuotaPer.PerDay, - }; - - public async Task<QuotaLimit> GetUserLimit(LimitedFeatureName name, ulong userId) - { - if (!_pConf.Data.IsEnabled) - return _infiniteQuota; - - var maybePatron = await GetPatronAsync(userId); - - if (maybePatron is not { } patron) - return _emptyQuota; - - if (patron.ValidThru < DateTime.UtcNow) - return _emptyQuota; - - foreach (var (key, value) in _pConf.Data.Limits) - { - if (patron.Amount >= key) - { - if (value.TryGetValue(name, out var quotaLimit)) - { - return quotaLimit; - } - - break; - } - } - - return _emptyQuota; - } - - public async Task<Dictionary<LimitedFeatureName, (int, QuotaLimit)>> LimitStats(ulong userId) - { - var dict = new Dictionary<LimitedFeatureName, (int, QuotaLimit)>(); - foreach (var featureName in Enum.GetValues<LimitedFeatureName>()) - { - var cacheKey = CreateKey(featureName, userId); - var userLimit = await GetUserLimit(featureName, userId); - var cur = await _cache.GetOrAddAsync(cacheKey, () => Task.FromResult(0), GetExpiry(userLimit)); - - dict[featureName] = (cur, userLimit); - } - - return dict; - } - - private Patron PatronUserToPatron(PatronUser user) => new Patron() { @@ -384,10 +304,13 @@ public sealed class PatronageService return user.AmountCents switch { - <= 200 => PatronTier.I, - <= 1_000 => PatronTier.C, - <= 5_000 => PatronTier.L, - _ => PatronTier.None + >= 10_000 => PatronTier.C, + >= 5_000 => PatronTier.L, + >= 2_000 => PatronTier.XX, + >= 1_000 => PatronTier.X, + >= 500 => PatronTier.V, + >= 100 => PatronTier.I, + _ => 0, }; } @@ -399,10 +322,12 @@ public sealed class PatronageService public int PercentBonus(long amount) => amount switch { - < 200 => 0, - < 1_000 => 10, - < 5_000 => 50, - _ => 100 + >= 10_000 => 100, + >= 5_000 => 50, + >= 2_000 => 25, + >= 1_000 => 10, + >= 500 => 5, + _ => 0, }; private async Task SendWelcomeMessage(Patron patron) @@ -414,28 +339,23 @@ public sealed class PatronageService return; var eb = _sender.CreateEmbed() - .WithOkColor() - .WithTitle("❤️ Thank you for supporting EllieBot! ❤️") - .WithDescription( - "Your donation has been processed and you will receive the rewards shortly.\n" - + "You can visit <https://www.patreon.com/join/elliebot> to see rewards for your tier. 🎉") - .AddField("Tier", Format.Bold(patron.Tier.ToString()), true) - .AddField("Pledge", $"**{patron.Amount / 100.0f:N1}$**", true) - .AddField("Expires", - patron.ValidThru.AddDays(1).ToShortAndRelativeTimestampTag(), - true) - .AddField("Instructions", - """ - *- Within the next **1-2 minutes** you will have all of the benefits of the Tier you've subscribed to.* - *- You can check your benefits on <https://www.patreon.com/join/elliebot>* - *- You can use the `.patron` command in this chat to check your current quota usage for the Patron-only commands* - *- **ALL** of the servers that you **own** will enjoy your Patron benefits.* - *- You can use any of the commands available in your tier on any server (assuming you have sufficient permissions to run those commands)* - *- Any user in any of your servers can use Patron-only commands, but they will spend **your quota**, which is why it's recommended to use Ellie's command cooldown system (.h .cmdcd) or permission system to limit the command usage for your server members.* - *- Permission guide can be found here if you're not familiar with it: <https://docs.elliebot.net/ellie/features/permissions-system/>* - """, - inline: false) - .WithFooter($"platform id: {patron.UniquePlatformUserId}"); + .WithOkColor() + .WithTitle("❤️ Thank you for supporting EllieBot! ❤️") + .WithDescription( + "Your donation has been processed and you will receive the rewards shortly.\n" + + "You can visit <https://www.patreon.com/join/elliebot> to see rewards for your tier. 🎉") + .AddField("Tier", Format.Bold(patron.Tier.ToString()), true) + .AddField("Pledge", $"**{patron.Amount / 100.0f:N1}$**", true) + .AddField("Expires", + patron.ValidThru.AddDays(1).ToShortAndRelativeTimestampTag(), + true) + .AddField("Instructions", + """ + *- Within the next **1-2 minutes** you will have all of the benefits of the Tier you've subscribed to.* + *- You can use the `.patron` command in this chat to check your current quota usage for the Patron-only commands* + """, + inline: false) + .WithFooter($"platform id: {patron.UniquePlatformUserId}"); await _sender.Response(user).Embed(eb).SendAsync(); } @@ -450,8 +370,8 @@ public sealed class PatronageService await using var ctx = _db.GetDbContext(); var patrons = await ctx.GetTable<PatronUser>() - .Where(x => x.ValidThru > DateTime.UtcNow) - .ToArrayAsync(); + .Where(x => x.ValidThru > DateTime.UtcNow) + .ToArrayAsync(); var text = SmartText.CreateFrom(message); diff --git a/src/EllieBot/Modules/Utility/LiveChannel/LiveChannelCommands.cs b/src/EllieBot/Modules/Utility/LiveChannel/LiveChannelCommands.cs index 7b8ca9a..879af02 100644 --- a/src/EllieBot/Modules/Utility/LiveChannel/LiveChannelCommands.cs +++ b/src/EllieBot/Modules/Utility/LiveChannel/LiveChannelCommands.cs @@ -13,10 +13,10 @@ public partial class Utility [BotPerm(GuildPerm.ManageChannels)] public async Task LiveChAdd(IChannel channel, [Leftover] string template) { - if (!await svc.AddLiveChannelAsync(ctx.Guild.Id, channel.Id, template)) + if (!await svc.AddLiveChannelAsync(ctx.Guild.Id, channel.Id, ctx.Guild.OwnerId, template)) { await Response() - .Error(strs.livechannel_limit(LiveChannelService.MAX_LIVECHANNELS)) + .Error(strs.livechannel_limit(await svc.GetMaxLiveChannels(ctx.Guild.OwnerId))) .SendAsync(); return; } diff --git a/src/EllieBot/Modules/Utility/LiveChannel/LiveChannelService.cs b/src/EllieBot/Modules/Utility/LiveChannel/LiveChannelService.cs index e8c764a..ab22fc7 100644 --- a/src/EllieBot/Modules/Utility/LiveChannel/LiveChannelService.cs +++ b/src/EllieBot/Modules/Utility/LiveChannel/LiveChannelService.cs @@ -4,6 +4,7 @@ using LinqToDB.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using EllieBot.Common.ModuleBehaviors; using EllieBot.Db.Models; +using EllieBot.Modules.Patronage; using Newtonsoft.Json.Linq; namespace EllieBot.Modules.Utility.LiveChannel; @@ -15,9 +16,10 @@ public class LiveChannelService( DbService db, DiscordSocketClient client, IReplacementService repSvc, + IPatronageService patron, ShardData shardData) : IReadyExecutor, IEService { - public const int MAX_LIVECHANNELS = 1; + public const int DEFAULT_MAX_LIVECHANNELS = 5; private readonly ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, LiveChannelConfig>> _liveChannels = new(); @@ -122,23 +124,23 @@ public class LiveChannelService( /// <param name="channelId">ID of the channel</param> /// <param name="template">Template text to use for the channel</param> /// <returns>True if successfully added, false otherwise</returns> - public async Task<bool> AddLiveChannelAsync(ulong guildId, ulong channelId, string template) + public async Task<bool> AddLiveChannelAsync(ulong guildId, ulong channelId, ulong guildOwnerId, string template) { var guildDict = _liveChannels.GetOrAdd( guildId, _ => new()); - if (!guildDict.ContainsKey(channelId) && guildDict.Count >= MAX_LIVECHANNELS) + if (!guildDict.ContainsKey(channelId) && guildDict.Count >= await GetMaxLiveChannels(guildOwnerId)) return false; await using var uow = db.GetDbContext(); await uow.GetTable<LiveChannelConfig>() .InsertOrUpdateAsync(() => new() - { - GuildId = guildId, - ChannelId = channelId, - Template = template - }, + { + GuildId = guildId, + ChannelId = channelId, + Template = template + }, (_) => new() { Template = template @@ -194,4 +196,11 @@ public class LiveChannelService( .Where(x => x.GuildId == guildId) .ToListAsyncLinqToDB(); } + + + public async Task<int> GetMaxLiveChannels(ulong guildOwnerId) + { + var limit = await patron.GetUserLimit("livechannels", guildOwnerId, DEFAULT_MAX_LIVECHANNELS); + return limit; + } } \ No newline at end of file diff --git a/src/EllieBot/Modules/Utility/Quote/QuoteCommands.cs b/src/EllieBot/Modules/Utility/Quote/QuoteCommands.cs index 38c392e..8b5b180 100644 --- a/src/EllieBot/Modules/Utility/Quote/QuoteCommands.cs +++ b/src/EllieBot/Modules/Utility/Quote/QuoteCommands.cs @@ -235,8 +235,7 @@ public partial class Utility .SendAsync(); } - - [Cmd] + [RequireContext(ContextType.Guild)] public async Task QuoteAdd(string keyword, [Leftover] string text) { diff --git a/src/EllieBot/_common/Patronage/FeatureLimitKey.cs b/src/EllieBot/_common/Patronage/FeatureLimitKey.cs index 10278e1..9aa8a46 100644 --- a/src/EllieBot/_common/Patronage/FeatureLimitKey.cs +++ b/src/EllieBot/_common/Patronage/FeatureLimitKey.cs @@ -1,12 +1,5 @@ namespace EllieBot.Modules.Patronage; -public enum LimitedFeatureName -{ - ChatBot, - ReactionRole, - Prune, - -} public readonly struct FeatureLimitKey { public string PrettyName { get; init; } diff --git a/src/EllieBot/_common/Patronage/IPatronageService.cs b/src/EllieBot/_common/Patronage/IPatronageService.cs index 379de6b..e093fa0 100644 --- a/src/EllieBot/_common/Patronage/IPatronageService.cs +++ b/src/EllieBot/_common/Patronage/IPatronageService.cs @@ -30,12 +30,8 @@ public interface IPatronageService /// <returns>A patron with the specifeid userId</returns> public Task<Patron?> GetPatronAsync(ulong userId); - Task<bool> LimitHitAsync(LimitedFeatureName key, ulong userId, int amount = 1); - Task<bool> LimitForceHit(LimitedFeatureName key, ulong userId, int amount); - Task<QuotaLimit> GetUserLimit(LimitedFeatureName name, ulong userId); - - Task<Dictionary<LimitedFeatureName, (int, QuotaLimit)>> LimitStats(ulong userId); - + Task<bool> LimitHitAsync(string name, ulong userId, int def); + Task<int> GetUserLimit(string name, ulong userId, int def); PatronConfigData GetConfig(); int PercentBonus(Patron? user); int PercentBonus(long amount); diff --git a/src/EllieBot/_common/Patronage/PatronConfigData.cs b/src/EllieBot/_common/Patronage/PatronConfigData.cs index 9becae0..d59a294 100644 --- a/src/EllieBot/_common/Patronage/PatronConfigData.cs +++ b/src/EllieBot/_common/Patronage/PatronConfigData.cs @@ -11,7 +11,7 @@ public partial class PatronConfigData : ICloneable<PatronConfigData> [Comment("Whether the patronage feature is enabled")] public bool IsEnabled { get; set; } - - [Comment("Who can do how much of what")] - public Dictionary<int, Dictionary<LimitedFeatureName, QuotaLimit>> Limits { get; set; } = new(); + + [Comment("Quotas for patron system")] + public Dictionary<PatronTier, Dictionary<string, int>> Quotas { get; set; } = new(); } \ No newline at end of file diff --git a/src/EllieBot/_common/Patronage/PatronTier.cs b/src/EllieBot/_common/Patronage/PatronTier.cs index a285fec..35fa670 100644 --- a/src/EllieBot/_common/Patronage/PatronTier.cs +++ b/src/EllieBot/_common/Patronage/PatronTier.cs @@ -5,7 +5,9 @@ public enum PatronTier { None, I, + V, X, + XX, L, C } \ No newline at end of file diff --git a/src/EllieBot/_common/Patronage/QuotaLimit.cs b/src/EllieBot/_common/Patronage/QuotaLimit.cs deleted file mode 100644 index 5669c0c..0000000 --- a/src/EllieBot/_common/Patronage/QuotaLimit.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace EllieBot.Modules.Patronage; - -/// <summary> -/// Represents information about why the user has triggered a quota limit -/// </summary> -public readonly struct QuotaLimit -{ - /// <summary> - /// Amount of usages reached, which is the limit - /// </summary> - public int Quota { get; init; } - - /// <summary> - /// Which period is this quota limit for (hourly, daily, monthly, etc...) - /// </summary> - public QuotaPer QuotaPeriod { get; init; } - - public QuotaLimit(int quota, QuotaPer quotaPeriod) - { - Quota = quota; - QuotaPeriod = quotaPeriod; - } -} \ No newline at end of file diff --git a/src/EllieBot/_common/Patronage/QuotaPer.cs b/src/EllieBot/_common/Patronage/QuotaPer.cs deleted file mode 100644 index 9f67a40..0000000 --- a/src/EllieBot/_common/Patronage/QuotaPer.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace EllieBot.Modules.Patronage; - -public enum QuotaPer -{ - PerHour, - PerDay, - PerMonth, - Total, -} \ No newline at end of file diff --git a/src/EllieBot/_common/TypeReaders/Rgba32TypeReader.cs b/src/EllieBot/_common/TypeReaders/Rgba32TypeReader.cs index ab1f9e8..cb5bd51 100644 --- a/src/EllieBot/_common/TypeReaders/Rgba32TypeReader.cs +++ b/src/EllieBot/_common/TypeReaders/Rgba32TypeReader.cs @@ -14,28 +14,7 @@ public sealed class Rgba32TypeReader : EllieTypeReader<Rgba32> return ValueTask.FromResult( TypeReaderResult.FromError<Rgba32>(CommandError.ParseFailed, "Parameter is not a valid color hex.")); } - Log.Information(color.ToHex()); return ValueTask.FromResult(TypeReaderResult.FromSuccess((Rgba32)color)); - - if (Rgba32.TryParseHex(input, out var clr)) - { - return ValueTask.FromResult(TypeReaderResult.FromSuccess(clr)); - } - - if (!Enum.TryParse<Color>(input, true, out var clrName)) - return ValueTask.FromResult( - TypeReaderResult.FromError<Rgba32>(CommandError.ParseFailed, - "Parameter is not a valid color hex.")); - - Log.Information(clrName.ToString()); - - if (Rgba32.TryParseHex(clrName.ToHex(), out clr)) - { - return ValueTask.FromResult(TypeReaderResult.FromSuccess(clr)); - } - - return ValueTask.FromResult( - TypeReaderResult.FromError<Rgba32>(CommandError.ParseFailed, "Parameter is not a valid color hex.")); } } \ No newline at end of file diff --git a/src/EllieBot/data/commandlist.json b/src/EllieBot/data/commandlist.json index 8324dad..d72493b 100644 --- a/src/EllieBot/data/commandlist.json +++ b/src/EllieBot/data/commandlist.json @@ -7717,22 +7717,6 @@ "Options": null, "Requirements": [] }, - { - "Aliases": [ - ".quoteadd", - ".qa", - ".qadd", - ".quadd" - ], - "Description": "Adds a new quote with the specified name and message.", - "Usage": [ - ".quoteadd sayhi Hi" - ], - "Submodule": "QuoteCommands", - "Module": "Utility", - "Options": null, - "Requirements": [] - }, { "Aliases": [ ".quoteedit", diff --git a/src/EllieBot/data/patron.yml b/src/EllieBot/data/patron.yml index 56da34a..f7a101a 100644 --- a/src/EllieBot/data/patron.yml +++ b/src/EllieBot/data/patron.yml @@ -2,35 +2,24 @@ version: 3 # Whether the patronage feature is enabled isEnabled: false -# Who can do how much of what -limits: - 50: - ChatBot: - quota: 20000000 - quotaPeriod: PerMonth - ReactionRole: - quota: -1 - quotaPeriod: Total - Prune: - quota: -1 - quotaPeriod: PerDay - 10: - ChatBot: - quota: 2500000 - quotaPeriod: PerMonth - ReactionRole: - quota: 50 - quotaPeriod: Total - Prune: - quota: 5 - quotaPeriod: PerDay - 2: - ChatBot: - quota: 1000000 - quotaPeriod: PerMonth - ReactionRole: - quota: 25 - quotaPeriod: Total - Prune: - quota: 2 - quotaPeriod: PerDay +# Quotas for patron system +quotas: + None: + I: + livechannels: 1 + ai: 0 + V: + livechannels: 2 + ai: 50 + X: + livechannels: 5 + ai: 100 + XX: + livechannels: 5 + ai: 200 + L: + livechannels: 5 + ai: 500 + C: + livechannels: 5 + ai: -1 \ No newline at end of file