mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 09:18:27 -04:00
Merge branch 'v3' of https://gitlab.com/kwoth/nadekobot into v3
This commit is contained in:
10
CHANGELOG.md
10
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
|
||||
|
@@ -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<GuildConfig, Task> JoinedGuild = delegate { return Task.CompletedTask; };
|
||||
|
||||
@@ -95,8 +95,8 @@ namespace NadekoBot
|
||||
}
|
||||
|
||||
var svcs = new ServiceCollection()
|
||||
.AddTransient<IBotCredentials>(_ => _creds) // bot creds
|
||||
.AddSingleton(_credsProvider)
|
||||
.AddTransient<IBotCredentials>(_ => _credsProvider.GetCreds()) // bot creds
|
||||
.AddSingleton<IBotCredsProvider>(_credsProvider)
|
||||
.AddSingleton(_db) // database
|
||||
.AddRedis(_creds.RedisOptions) // redis
|
||||
.AddSingleton(Client) // discord socket client
|
||||
|
@@ -12,7 +12,7 @@ namespace NadekoBot.Common.Attributes
|
||||
{
|
||||
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo executingCommand, IServiceProvider services)
|
||||
{
|
||||
var creds = services.GetRequiredService<BotCredsProvider>().GetCreds();
|
||||
var creds = services.GetRequiredService<IBotCredsProvider>().GetCreds();
|
||||
|
||||
return Task.FromResult((creds.IsOwner(context.User) || context.Client.CurrentUser.Id == context.User.Id ? PreconditionResult.FromSuccess() : PreconditionResult.FromError("Not owner")));
|
||||
}
|
||||
|
@@ -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)")]
|
||||
|
@@ -12,12 +12,11 @@ namespace NadekoBot
|
||||
string GoogleApiKey { get; }
|
||||
ICollection<ulong> 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; }
|
||||
|
@@ -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)]
|
||||
|
@@ -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<Datum> 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<PatreonMember> Data { get; set; }
|
||||
|
||||
[JsonPropertyName("included")]
|
||||
public List<PatreonUser> 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; }
|
||||
}
|
||||
}
|
||||
|
@@ -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; }
|
||||
}
|
||||
}
|
@@ -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; }
|
||||
}
|
||||
}
|
@@ -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)
|
||||
{
|
||||
|
@@ -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<bool> 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<PatreonRefreshData>();
|
||||
|
||||
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<PatreonPledge>();
|
||||
|
||||
var members = new List<PatreonMember>();
|
||||
var users = new List<PatreonUser>();
|
||||
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<PatreonData>(res);
|
||||
var pledgers = data.Data.Where(x => x["type"].ToString() == "pledge");
|
||||
rewards.AddRange(pledgers.Select(x => JsonConvert.DeserializeObject<PatreonPledge>(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<PatreonUser>(x.ToString())));
|
||||
}
|
||||
} while (!string.IsNullOrWhiteSpace(data.Links.next));
|
||||
var res = await http.GetStringAsync(page).ConfigureAwait(false);
|
||||
data = JsonSerializer.Deserialize<PatreonResponse>(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<int> ClaimReward(ulong userId)
|
||||
public async Task<int> 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<PatreonUserAndReward>();
|
||||
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<RewardedUser>();
|
||||
var usr = await users.FirstOrDefaultAsync(x => x.PatreonUserId == patreonUserId);
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
if (usr is null)
|
||||
{
|
||||
var users = uow.Set<RewardedUser>();
|
||||
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
|
||||
{
|
||||
|
@@ -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<Creds> 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<Creds> func)
|
||||
{
|
||||
var ymlData = File.ReadAllText(_credsFileName);
|
||||
var creds = Yaml.Deserializer.Deserialize<Creds>(ymlData);
|
||||
|
||||
func(creds);
|
||||
|
||||
ymlData = Yaml.Serializer.Serialize(creds);
|
||||
File.WriteAllText(_credsFileName, ymlData);
|
||||
|
||||
Reload();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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;
|
||||
}
|
||||
}
|
@@ -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: ''
|
||||
|
Reference in New Issue
Block a user