diff --git a/CHANGELOG.md b/CHANGELOG.md index 597877b4a..d2913c267 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog. ## Unreleased +### Added + - Patreon Access and Refresh Tokens should now be automatically updated once a month as long as the user has provided the necessary credentials in creds.yml file: + - `Patreon.ClientId` + - `Patreon.RefreshToken` (will also get updated once a month but needs an initial value) + - `Patreon.ClientSecret` + - `Patreon.CampaignId` + +### Fixed +- Fixed an error that would show up in the console when a club image couldn't be drawn in certain circumstances + ## [3.0.8] - 03.11.2021 ### Added diff --git a/src/NadekoBot/Bot.cs b/src/NadekoBot/Bot.cs index 7d92cc5ac..c44cc9f96 100644 --- a/src/NadekoBot/Bot.cs +++ b/src/NadekoBot/Bot.cs @@ -28,7 +28,7 @@ namespace NadekoBot private readonly IBotCredentials _creds; private readonly CommandService _commandService; private readonly DbService _db; - private readonly BotCredsProvider _credsProvider; + private readonly IBotCredsProvider _credsProvider; public event Func JoinedGuild = delegate { return Task.CompletedTask; }; @@ -95,8 +95,8 @@ namespace NadekoBot } var svcs = new ServiceCollection() - .AddTransient(_ => _creds) // bot creds - .AddSingleton(_credsProvider) + .AddTransient(_ => _credsProvider.GetCreds()) // bot creds + .AddSingleton(_credsProvider) .AddSingleton(_db) // database .AddRedis(_creds.RedisOptions) // redis .AddSingleton(Client) // discord socket client diff --git a/src/NadekoBot/Common/Attributes/OwnerOnlyAttribute.cs b/src/NadekoBot/Common/Attributes/OwnerOnlyAttribute.cs index f217d4539..7ccae8631 100644 --- a/src/NadekoBot/Common/Attributes/OwnerOnlyAttribute.cs +++ b/src/NadekoBot/Common/Attributes/OwnerOnlyAttribute.cs @@ -12,7 +12,7 @@ namespace NadekoBot.Common.Attributes { public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo executingCommand, IServiceProvider services) { - var creds = services.GetRequiredService().GetCreds(); + var creds = services.GetRequiredService().GetCreds(); return Task.FromResult((creds.IsOwner(context.User) || context.Client.CurrentUser.Id == context.User.Id ? PreconditionResult.FromSuccess() : PreconditionResult.FromError("Not owner"))); } diff --git a/src/NadekoBot/Common/Creds.cs b/src/NadekoBot/Common/Creds.cs index 28354fdaf..2ce2e06cf 100644 --- a/src/NadekoBot/Common/Creds.cs +++ b/src/NadekoBot/Common/Creds.cs @@ -73,11 +73,6 @@ go to https://www.patreon.com/portal -> my clients -> create client")] Change only if you've changed the coordinator address or port.")] public string CoordinatorUrl { get; set; } - [YamlIgnore] - public string PatreonCampaignId => Patreon?.CampaignId; - [YamlIgnore] - public string PatreonAccessToken => Patreon?.AccessToken; - [Comment(@"Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)")] public string RapidApiKey { get; set; } @@ -121,11 +116,9 @@ Windows default // todo fixup patreon public sealed record PatreonSettings { - [Comment(@"Access token. You have to manually update this 1st of each month by refreshing the token on https://patreon.com/portal")] + public string ClientId { get; set; } public string AccessToken { get; set; } - [Comment(@"Unused atm")] public string RefreshToken { get; set; } - [Comment(@"Unused atm")] public string ClientSecret { get; set; } [Comment(@"Campaign ID of your patreon page. Go to your patreon page (make sure you're logged in) and type ""prompt('Campaign ID', window.patreon.bootstrap.creator.data.id);"" in the console. (ctrl + shift + i)")] diff --git a/src/NadekoBot/Common/IBotCredentials.cs b/src/NadekoBot/Common/IBotCredentials.cs index f759bbcb3..444abb73b 100644 --- a/src/NadekoBot/Common/IBotCredentials.cs +++ b/src/NadekoBot/Common/IBotCredentials.cs @@ -12,12 +12,11 @@ namespace NadekoBot string GoogleApiKey { get; } ICollection OwnerIds { get; } string RapidApiKey { get; } - string PatreonAccessToken { get; } Creds.DbOptions Db { get; } string OsuApiKey { get; } int TotalShards { get; } - string PatreonCampaignId { get; } + Creds.PatreonSettings Patreon { get; } string CleverbotApiKey { get; } RestartConfig RestartCommand { get; } Creds.VotesSettings Votes { get; } diff --git a/src/NadekoBot/Modules/Games/CleverBotCommands.cs b/src/NadekoBot/Modules/Games/CleverBotCommands.cs index 3b096bfd0..66524a0bf 100644 --- a/src/NadekoBot/Modules/Games/CleverBotCommands.cs +++ b/src/NadekoBot/Modules/Games/CleverBotCommands.cs @@ -2,6 +2,7 @@ using Discord.Commands; using System; using System.Threading.Tasks; +using NadekoBot.Common; using NadekoBot.Common.Attributes; using NadekoBot.Services; using NadekoBot.Db; @@ -23,6 +24,7 @@ namespace NadekoBot.Modules.Games _db = db; } + [NoPublicBot] [NadekoCommand, Aliases] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageMessages)] diff --git a/src/NadekoBot/Modules/Utility/Common/Patreon/PatreonData.cs b/src/NadekoBot/Modules/Utility/Common/Patreon/PatreonData.cs index 748c7170c..5064fb4d4 100644 --- a/src/NadekoBot/Modules/Utility/Common/Patreon/PatreonData.cs +++ b/src/NadekoBot/Modules/Utility/Common/Patreon/PatreonData.cs @@ -1,23 +1,134 @@ -using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; namespace NadekoBot.Modules.Utility.Common.Patreon { - public class PatreonData + public sealed class Attributes { - public JObject[] Included { get; set; } - public JObject[] Data { get; set; } - public PatreonDataLinks Links { get; set; } + [JsonPropertyName("full_name")] + public string FullName { get; set; } + + [JsonPropertyName("is_follower")] + public bool IsFollower { get; set; } + + [JsonPropertyName("last_charge_date")] + public DateTime LastChargeDate { get; set; } + + [JsonPropertyName("last_charge_status")] + public string LastChargeStatus { get; set; } + + [JsonPropertyName("lifetime_support_cents")] + public int LifetimeSupportCents { get; set; } + + [JsonPropertyName("currently_entitled_amount_cents")] + public int CurrentlyEntitledAmountCents { get; set; } + + [JsonPropertyName("patron_status")] + public string PatronStatus { get; set; } } - public class PatreonDataLinks + public sealed class Data { - public string first { get; set; } - public string next { get; set; } + [JsonPropertyName("id")] + public string Id { get; set; } + + [JsonPropertyName("type")] + public string Type { get; set; } } - public class PatreonUserAndReward + public sealed class Address { - public PatreonUser User { get; set; } - public PatreonPledge Reward { get; set; } + [JsonPropertyName("data")] + public Data Data { get; set; } + } + + // public sealed class CurrentlyEntitledTiers + // { + // [JsonPropertyName("data")] + // public List Data { get; set; } + // } + + // public sealed class Relationships + // { + // [JsonPropertyName("address")] + // public Address Address { get; set; } + // + // // [JsonPropertyName("currently_entitled_tiers")] + // // public CurrentlyEntitledTiers CurrentlyEntitledTiers { get; set; } + // } + + public sealed class PatreonResponse + { + [JsonPropertyName("data")] + public List Data { get; set; } + + [JsonPropertyName("included")] + public List Included { get; set; } + + [JsonPropertyName("links")] + public PatreonLinks Links { get; set; } + } + + public sealed class PatreonLinks + { + [JsonPropertyName("next")] + public string Next { get; set; } + } + + public sealed class PatreonUser + { + [JsonPropertyName("attributes")] + public PatreonUserAttributes Attributes { get; set; } + [JsonPropertyName("id")] + public string Id { get; set; } + // public string Type { get; set; } + } + + public sealed class PatreonUserAttributes + { + [JsonPropertyName("social_connections")] + public PatreonSocials SocialConnections { get; set; } + } + public sealed class PatreonSocials + { + [JsonPropertyName("discord")] + public DiscordSocial Discord { get; set; } + } + + public sealed class DiscordSocial + { + [JsonPropertyName("user_id")] + public string UserId { get; set; } + } + + public sealed class PatreonMember + { + [JsonPropertyName("attributes")] + public Attributes Attributes { get; set; } + + [JsonPropertyName("relationships")] + public Relationships Relationships { get; set; } + + [JsonPropertyName("type")] + public string Type { get; set; } + } + + public sealed class Relationships + { + [JsonPropertyName("user")] + public PatreonRelationshipUser User { get; set; } + } + + public sealed class PatreonRelationshipUser + { + [JsonPropertyName("data")] + public PatreonUserData Data { get; set; } + } + + public sealed class PatreonUserData + { + [JsonPropertyName("id")] + public string Id { get; set; } } } diff --git a/src/NadekoBot/Modules/Utility/Common/Patreon/PatreonPledge.cs b/src/NadekoBot/Modules/Utility/Common/Patreon/PatreonPledge.cs deleted file mode 100644 index ae46aeaa5..000000000 --- a/src/NadekoBot/Modules/Utility/Common/Patreon/PatreonPledge.cs +++ /dev/null @@ -1,62 +0,0 @@ -namespace NadekoBot.Modules.Utility.Common.Patreon -{ - public class Attributes - { - public int amount_cents { get; set; } - public string created_at { get; set; } - public object declined_since { get; set; } - public bool is_twitch_pledge { get; set; } - public bool patron_pays_fees { get; set; } - public int? pledge_cap_cents { get; set; } - } - - public class Address - { - public object data { get; set; } - } - - public class Data - { - public string id { get; set; } - public string type { get; set; } - } - - public class Links - { - public string related { get; set; } - } - - public class Creator - { - public Data data { get; set; } - public Links links { get; set; } - } - - public class Patron - { - public Data data { get; set; } - public Links links { get; set; } - } - - public class Reward - { - public Data data { get; set; } - public Links links { get; set; } - } - - public class Relationships - { - public Address address { get; set; } - public Creator creator { get; set; } - public Patron patron { get; set; } - public Reward reward { get; set; } - } - - public class PatreonPledge - { - public Attributes attributes { get; set; } - public string id { get; set; } - public Relationships relationships { get; set; } - public string type { get; set; } - } -} diff --git a/src/NadekoBot/Modules/Utility/Common/Patreon/PatreonUser.cs b/src/NadekoBot/Modules/Utility/Common/Patreon/PatreonUser.cs deleted file mode 100644 index e4d168696..000000000 --- a/src/NadekoBot/Modules/Utility/Common/Patreon/PatreonUser.cs +++ /dev/null @@ -1,64 +0,0 @@ -namespace NadekoBot.Modules.Utility.Common.Patreon -{ - public class DiscordConnection - { - public string user_id { get; set; } - } - - public class SocialConnections - { - public object deviantart { get; set; } - public DiscordConnection discord { get; set; } - public object facebook { get; set; } - public object spotify { get; set; } - public object twitch { get; set; } - public object twitter { get; set; } - public object youtube { get; set; } - } - - public class UserAttributes - { - public string about { get; set; } - public string created { get; set; } - public object discord_id { get; set; } - public string email { get; set; } - public object facebook { get; set; } - public object facebook_id { get; set; } - public string first_name { get; set; } - public string full_name { get; set; } - public int gender { get; set; } - public bool has_password { get; set; } - public string image_url { get; set; } - public bool is_deleted { get; set; } - public bool is_nuked { get; set; } - public bool is_suspended { get; set; } - public string last_name { get; set; } - public SocialConnections social_connections { get; set; } - public int status { get; set; } - public string thumb_url { get; set; } - public object twitch { get; set; } - public string twitter { get; set; } - public string url { get; set; } - public string vanity { get; set; } - public object youtube { get; set; } - } - - public class Campaign - { - public Data data { get; set; } - public Links links { get; set; } - } - - public class UserRelationships - { - public Campaign campaign { get; set; } - } - - public class PatreonUser - { - public UserAttributes attributes { get; set; } - public string id { get; set; } - public UserRelationships relationships { get; set; } - public string type { get; set; } - } -} diff --git a/src/NadekoBot/Modules/Utility/PatreonCommands.cs b/src/NadekoBot/Modules/Utility/PatreonCommands.cs index db1e91acd..f444e3197 100644 --- a/src/NadekoBot/Modules/Utility/PatreonCommands.cs +++ b/src/NadekoBot/Modules/Utility/PatreonCommands.cs @@ -6,6 +6,7 @@ using NadekoBot.Extensions; using Discord; using NadekoBot.Common.Attributes; using NadekoBot.Modules.Utility.Services; +using Serilog; namespace NadekoBot.Modules.Utility { @@ -25,8 +26,12 @@ namespace NadekoBot.Modules.Utility [RequireContext(ContextType.DM)] public async Task ClaimPatreonRewards() { - if (string.IsNullOrWhiteSpace(_creds.PatreonAccessToken)) + if (string.IsNullOrWhiteSpace(_creds.Patreon.AccessToken)) + { + Log.Warning("In order to use patreon reward commands, " + + "you need to specify CampaignId and AccessToken in creds.yml"); return; + } if (DateTime.UtcNow.Day < 5) { diff --git a/src/NadekoBot/Modules/Utility/Services/PatreonRewardsService.cs b/src/NadekoBot/Modules/Utility/Services/PatreonRewardsService.cs index 7aa422855..8621734d7 100644 --- a/src/NadekoBot/Modules/Utility/Services/PatreonRewardsService.cs +++ b/src/NadekoBot/Modules/Utility/Services/PatreonRewardsService.cs @@ -2,17 +2,21 @@ using NadekoBot.Services; using NadekoBot.Services.Database.Models; using NadekoBot.Modules.Utility.Common.Patreon; -using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Net.Http.Json; +using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using System.Web; using Discord; using NadekoBot.Modules.Gambling.Services; using NadekoBot.Extensions; using Serilog; +using StackExchange.Redis; +using JsonSerializer = System.Text.Json.JsonSerializer; namespace NadekoBot.Modules.Utility.Services { @@ -20,96 +24,195 @@ namespace NadekoBot.Modules.Utility.Services { private readonly SemaphoreSlim getPledgesLocker = new SemaphoreSlim(1, 1); - private PatreonUserAndReward[] _pledges; - private readonly Timer _updater; private readonly SemaphoreSlim claimLockJustInCase = new SemaphoreSlim(1, 1); public TimeSpan Interval { get; } = TimeSpan.FromMinutes(3); - private readonly IBotCredentials _creds; private readonly DbService _db; private readonly ICurrencyService _currency; private readonly GamblingConfigService _gamblingConfigService; + private readonly ConnectionMultiplexer _redis; + private readonly IBotCredsProvider _credsProvider; private readonly IHttpClientFactory _httpFactory; private readonly IEmbedBuilderService _eb; private readonly DiscordSocketClient _client; public DateTime LastUpdate { get; private set; } = DateTime.UtcNow; - public PatreonRewardsService(IBotCredentials creds, DbService db, - ICurrencyService currency, IHttpClientFactory factory, IEmbedBuilderService eb, - DiscordSocketClient client, GamblingConfigService gamblingConfigService) + public PatreonRewardsService( + DbService db, + ICurrencyService currency, + IHttpClientFactory factory, + IEmbedBuilderService eb, + DiscordSocketClient client, + GamblingConfigService gamblingConfigService, + ConnectionMultiplexer redis, + IBotCredsProvider credsProvider) { - _creds = creds; _db = db; _currency = currency; _gamblingConfigService = gamblingConfigService; + _redis = redis; + _credsProvider = credsProvider; _httpFactory = factory; _eb = eb; _client = client; if (client.ShardId == 0) - _updater = new Timer(async _ => await RefreshPledges().ConfigureAwait(false), + _updater = new Timer(async _ => await RefreshPledges(_credsProvider.GetCreds()).ConfigureAwait(false), null, TimeSpan.Zero, Interval); } - public async Task RefreshPledges() + private DateTime LastAccessTokenUpdate(IBotCredentials creds) { - if (string.IsNullOrWhiteSpace(_creds.PatreonAccessToken) - || string.IsNullOrWhiteSpace(_creds.PatreonAccessToken)) - return; + var db = _redis.GetDatabase(); + var val = db.StringGet($"{creds.RedisKey()}_patreon_update"); + if (val == default) + return DateTime.MinValue; + + var lastTime = DateTime.FromBinary((long)val); + return lastTime; + } + + + private sealed class PatreonRefreshData + { + [JsonPropertyName("access_token")] + public string AccessToken { get; set; } + + [JsonPropertyName("refresh_token")] + public string RefreshToken { get; set; } + + [JsonPropertyName("expires_in")] + public long ExpiresIn { get; set; } + + [JsonPropertyName("scope")] + public string Scope { get; set; } + + [JsonPropertyName("token_type")] + public string TokenType { get; set; } + } + + private async Task UpdateAccessToken(IBotCredentials creds) + { + Log.Information("Updating patreon access token..."); + try + { + using var http = _httpFactory.CreateClient(); + var res = await http.PostAsync($"https://www.patreon.com/api/oauth2/token" + + $"?grant_type=refresh_token" + + $"&refresh_token={creds.Patreon.RefreshToken}" + + $"&client_id={creds.Patreon.ClientId}" + + $"&client_secret={creds.Patreon.ClientSecret}", + new StringContent(string.Empty)); + + res.EnsureSuccessStatusCode(); + + var data = await res.Content.ReadFromJsonAsync(); + + if (data is null) + throw new("Invalid patreon response."); + + _credsProvider.ModifyCredsFile(oldData => + { + oldData.Patreon.AccessToken = data.AccessToken; + oldData.Patreon.RefreshToken = data.RefreshToken; + }); + + var db = _redis.GetDatabase(); + await db.StringSetAsync($"{creds.RedisKey()}_patreon_update", DateTime.UtcNow.ToBinary()); + return true; + } + catch (Exception ex) + { + Log.Error("Failed updating patreon access token: {ErrorMessage}", ex.ToString()); + return false; + } + } + + private bool HasPatreonCreds(IBotCredentials creds) + { + var _1 = creds.Patreon.ClientId; + var _2 = creds.Patreon.ClientSecret; + var _4 = creds.Patreon.RefreshToken; + return !(string.IsNullOrWhiteSpace(_1) + || string.IsNullOrWhiteSpace(_2) + || string.IsNullOrWhiteSpace(_4)); + } + + public async Task RefreshPledges(IBotCredentials creds) + { if (DateTime.UtcNow.Day < 5) return; + // if the user has the necessary patreon creds + // and the access token expired or doesn't exist + // -> update access token + if (!HasPatreonCreds(creds)) + return; + + if (LastAccessTokenUpdate(creds).Month < DateTime.UtcNow.Month + || string.IsNullOrWhiteSpace(creds.Patreon.AccessToken)) + { + var success = await UpdateAccessToken(creds); + if (!success) + return; + } + LastUpdate = DateTime.UtcNow; await getPledgesLocker.WaitAsync().ConfigureAwait(false); try { - var rewards = new List(); + + var members = new List(); var users = new List(); using (var http = _httpFactory.CreateClient()) { http.DefaultRequestHeaders.Clear(); - http.DefaultRequestHeaders.Add("Authorization", "Bearer " + _creds.PatreonAccessToken); - var data = new PatreonData() - { - Links = new PatreonDataLinks() - { - next = $"https://api.patreon.com/oauth2/api/campaigns/{_creds.PatreonCampaignId}/pledges" - } - }; + http.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", + $"Bearer {creds.Patreon.AccessToken}"); + + var page = $"https://www.patreon.com/api/oauth2/v2/campaigns/{creds.Patreon.CampaignId}/members" + + "?fields%5Bmember%5D=full_name,currently_entitled_amount_cents" + + "&fields%5Buser%5D=social_connections" + + "&include=user"; + PatreonResponse data = null; do { - var res = await http.GetStringAsync(data.Links.next) - .ConfigureAwait(false); - data = JsonConvert.DeserializeObject(res); - var pledgers = data.Data.Where(x => x["type"].ToString() == "pledge"); - rewards.AddRange(pledgers.Select(x => JsonConvert.DeserializeObject(x.ToString())) - .Where(x => x.attributes.declined_since is null)); - if (data.Included != null) - { - users.AddRange(data.Included - .Where(x => x["type"].ToString() == "user") - .Select(x => JsonConvert.DeserializeObject(x.ToString()))); - } - } while (!string.IsNullOrWhiteSpace(data.Links.next)); + var res = await http.GetStringAsync(page).ConfigureAwait(false); + data = JsonSerializer.Deserialize(res); + + if (data is null) + break; + + members.AddRange(data.Data); + users.AddRange(data.Included); + } while (!string.IsNullOrWhiteSpace(page = data?.Links?.Next)); } - var toSet = rewards.Join(users, (r) => r.relationships?.patron?.data?.id, (u) => u.id, (x, y) => new PatreonUserAndReward() - { - User = y, - Reward = x, - }).ToArray(); - _pledges = toSet; - - foreach (var pledge in _pledges) - { - var userIdStr = pledge.User.attributes?.social_connections?.discord?.user_id; - if (userIdStr != null && ulong.TryParse(userIdStr, out var userId)) + var userData = members.Join(users, + (m) => m.Relationships.User.Data.Id, + (u) => u.Id, + (m, u) => new { - await ClaimReward(userId); - } + PatreonUserId = m.Relationships.User.Data.Id, + UserId = ulong.TryParse(u.Attributes?.SocialConnections?.Discord?.UserId ?? string.Empty, + out var userId) + ? userId + : 0, + EntitledTo = m.Attributes.CurrentlyEntitledAmountCents, + }) + .Where(x => x is + { + UserId: not 0, + EntitledTo: > 0 + }) + .ToList(); + + foreach (var pledge in userData) + { + await ClaimReward(pledge.UserId, pledge.PatreonUserId, pledge.EntitledTo); } } catch (Exception ex) @@ -123,80 +226,73 @@ namespace NadekoBot.Modules.Utility.Services } - public async Task ClaimReward(ulong userId) + public async Task ClaimReward(ulong userId, string patreonUserId, int cents) { await claimLockJustInCase.WaitAsync().ConfigureAwait(false); var settings = _gamblingConfigService.Data; var now = DateTime.UtcNow; try { - var datas = _pledges?.Where(x => x.User.attributes?.social_connections?.discord?.user_id == userId.ToString()) - ?? Enumerable.Empty(); + var eligibleFor = (int)(cents * settings.PatreonCurrencyPerCent); - var totalAmount = 0; - foreach (var data in datas) + using (var uow = _db.GetDbContext()) { - var amount = (int)(data.Reward.attributes.amount_cents * settings.PatreonCurrencyPerCent); + var users = uow.Set(); + var usr = await users.FirstOrDefaultAsync(x => x.PatreonUserId == patreonUserId); - using (var uow = _db.GetDbContext()) + if (usr is null) { - var users = uow.Set(); - var usr = users.FirstOrDefault(x => x.PatreonUserId == data.User.id); - - if (usr is null) + users.Add(new RewardedUser() { - users.Add(new RewardedUser() - { - PatreonUserId = data.User.id, - LastReward = now, - AmountRewardedThisMonth = amount, - }); + PatreonUserId = patreonUserId, + LastReward = now, + AmountRewardedThisMonth = eligibleFor, + }); - await uow.SaveChangesAsync(); + await uow.SaveChangesAsync(); - await _currency.AddAsync(userId, "Patreon reward - new", amount, gamble: true); - totalAmount += amount; - - Log.Information($"Sending new currency reward to {userId}"); - await SendMessageToUser(userId, $"Thank you for your pledge! " + - $"You've been awarded **{amount}**{settings.Currency.Sign} !"); - continue; - } - - if (usr.LastReward.Month != now.Month) - { - usr.LastReward = now; - usr.AmountRewardedThisMonth = amount; - - await uow.SaveChangesAsync(); - - await _currency.AddAsync(userId, "Patreon reward - recurring", amount, gamble: true); - totalAmount += amount; - Log.Information($"Sending recurring currency reward to {userId}"); - await SendMessageToUser(userId, $"Thank you for your continued support! " + - $"You've been awarded **{amount}**{settings.Currency.Sign} for this month's support!"); - continue; - } - - if (usr.AmountRewardedThisMonth < amount) - { - var toAward = amount - usr.AmountRewardedThisMonth; - - usr.LastReward = now; - usr.AmountRewardedThisMonth = amount; - await uow.SaveChangesAsync(); - - await _currency.AddAsync(userId, "Patreon reward - update", toAward, gamble: true); - totalAmount += toAward; - Log.Information($"Sending updated currency reward to {userId}"); - await SendMessageToUser(userId, $"Thank you for increasing your pledge! " + - $"You've been awarded an additional **{toAward}**{settings.Currency.Sign} !"); - continue; - } + await _currency.AddAsync(userId, "Patreon reward - new", eligibleFor, gamble: true); + + Log.Information($"Sending new currency reward to {userId}"); + await SendMessageToUser(userId, $"Thank you for your pledge! " + + $"You've been awarded **{eligibleFor}**{settings.Currency.Sign} !"); + return eligibleFor; } - } - return totalAmount; + if (usr.LastReward.Month != now.Month) + { + usr.LastReward = now; + usr.AmountRewardedThisMonth = eligibleFor; + + await uow.SaveChangesAsync(); + + await _currency.AddAsync(userId, "Patreon reward - recurring", eligibleFor, gamble: true); + + Log.Information($"Sending recurring currency reward to {userId}"); + await SendMessageToUser(userId, $"Thank you for your continued support! " + + $"You've been awarded **{eligibleFor}**{settings.Currency.Sign} for this month's support!"); + + return eligibleFor; + } + + if (usr.AmountRewardedThisMonth < eligibleFor) + { + var toAward = eligibleFor - usr.AmountRewardedThisMonth; + + usr.LastReward = now; + usr.AmountRewardedThisMonth = toAward; + await uow.SaveChangesAsync(); + + await _currency.AddAsync(userId, "Patreon reward - update", toAward, gamble: true); + + Log.Information($"Sending updated currency reward to {userId}"); + await SendMessageToUser(userId, $"Thank you for increasing your pledge! " + + $"You've been awarded an additional **{toAward}**{settings.Currency.Sign} !"); + return toAward; + } + + return 0; + } } finally { diff --git a/src/NadekoBot/Services/Impl/BotCredsProvider.cs b/src/NadekoBot/Services/Impl/BotCredsProvider.cs index f2d5f0ae3..a1c69002a 100644 --- a/src/NadekoBot/Services/Impl/BotCredsProvider.cs +++ b/src/NadekoBot/Services/Impl/BotCredsProvider.cs @@ -10,7 +10,14 @@ using Serilog; namespace NadekoBot.Services { - public sealed class BotCredsProvider + public interface IBotCredsProvider + { + public void Reload(); + public IBotCredentials GetCreds(); + public void ModifyCredsFile(Action func); + } + + public sealed class BotCredsProvider : IBotCredsProvider { private readonly int? _totalShards; private const string _credsFileName = "creds.yml"; @@ -27,7 +34,7 @@ namespace NadekoBot.Services private readonly object reloadLock = new object(); - private void Reload() + public void Reload() { lock (reloadLock) { @@ -102,6 +109,19 @@ namespace NadekoBot.Services Reload(); } + public void ModifyCredsFile(Action func) + { + var ymlData = File.ReadAllText(_credsFileName); + var creds = Yaml.Deserializer.Deserialize(ymlData); + + func(creds); + + ymlData = Yaml.Serializer.Serialize(creds); + File.WriteAllText(_credsFileName, ymlData); + + Reload(); + } + /// /// Checks if there's a V2 credentials file present, loads it if it exists, /// converts it to new model, and saves it to YAML. Also backs up old credentials to credentials.json.bak @@ -157,6 +177,6 @@ namespace NadekoBot.Services } - public Creds GetCreds() => _creds; + public IBotCredentials GetCreds() => _creds; } } \ No newline at end of file diff --git a/src/NadekoBot/creds_example.yml b/src/NadekoBot/creds_example.yml index 05f9cd773..b392b011e 100644 --- a/src/NadekoBot/creds_example.yml +++ b/src/NadekoBot/creds_example.yml @@ -31,11 +31,9 @@ votes: # Patreon auto reward system settings. # go to https://www.patreon.com/portal -> my clients -> create client patreon: -# Access token. You have to manually update this 1st of each month by refreshing the token on https://patreon.com/portal + clientId: accessToken: '' - # Unused atm refreshToken: '' - # Unused atm clientSecret: '' # Campaign ID of your patreon page. Go to your patreon page (make sure you're logged in) and type "prompt('Campaign ID', window.patreon.bootstrap.creator.data.id);" in the console. (ctrl + shift + i) campaignId: ''