mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28: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
|
## 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
|
## [3.0.8] - 03.11.2021
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@@ -28,7 +28,7 @@ namespace NadekoBot
|
|||||||
private readonly IBotCredentials _creds;
|
private readonly IBotCredentials _creds;
|
||||||
private readonly CommandService _commandService;
|
private readonly CommandService _commandService;
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
private readonly BotCredsProvider _credsProvider;
|
private readonly IBotCredsProvider _credsProvider;
|
||||||
|
|
||||||
public event Func<GuildConfig, Task> JoinedGuild = delegate { return Task.CompletedTask; };
|
public event Func<GuildConfig, Task> JoinedGuild = delegate { return Task.CompletedTask; };
|
||||||
|
|
||||||
@@ -95,8 +95,8 @@ namespace NadekoBot
|
|||||||
}
|
}
|
||||||
|
|
||||||
var svcs = new ServiceCollection()
|
var svcs = new ServiceCollection()
|
||||||
.AddTransient<IBotCredentials>(_ => _creds) // bot creds
|
.AddTransient<IBotCredentials>(_ => _credsProvider.GetCreds()) // bot creds
|
||||||
.AddSingleton(_credsProvider)
|
.AddSingleton<IBotCredsProvider>(_credsProvider)
|
||||||
.AddSingleton(_db) // database
|
.AddSingleton(_db) // database
|
||||||
.AddRedis(_creds.RedisOptions) // redis
|
.AddRedis(_creds.RedisOptions) // redis
|
||||||
.AddSingleton(Client) // discord socket client
|
.AddSingleton(Client) // discord socket client
|
||||||
|
@@ -12,7 +12,7 @@ namespace NadekoBot.Common.Attributes
|
|||||||
{
|
{
|
||||||
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo executingCommand, IServiceProvider services)
|
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")));
|
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.")]
|
Change only if you've changed the coordinator address or port.")]
|
||||||
public string CoordinatorUrl { get; set; }
|
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)")]
|
[Comment(@"Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)")]
|
||||||
public string RapidApiKey { get; set; }
|
public string RapidApiKey { get; set; }
|
||||||
|
|
||||||
@@ -121,11 +116,9 @@ Windows default
|
|||||||
// todo fixup patreon
|
// todo fixup patreon
|
||||||
public sealed record PatreonSettings
|
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; }
|
public string AccessToken { get; set; }
|
||||||
[Comment(@"Unused atm")]
|
|
||||||
public string RefreshToken { get; set; }
|
public string RefreshToken { get; set; }
|
||||||
[Comment(@"Unused atm")]
|
|
||||||
public string ClientSecret { get; set; }
|
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)")]
|
[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; }
|
string GoogleApiKey { get; }
|
||||||
ICollection<ulong> OwnerIds { get; }
|
ICollection<ulong> OwnerIds { get; }
|
||||||
string RapidApiKey { get; }
|
string RapidApiKey { get; }
|
||||||
string PatreonAccessToken { get; }
|
|
||||||
|
|
||||||
Creds.DbOptions Db { get; }
|
Creds.DbOptions Db { get; }
|
||||||
string OsuApiKey { get; }
|
string OsuApiKey { get; }
|
||||||
int TotalShards { get; }
|
int TotalShards { get; }
|
||||||
string PatreonCampaignId { get; }
|
Creds.PatreonSettings Patreon { get; }
|
||||||
string CleverbotApiKey { get; }
|
string CleverbotApiKey { get; }
|
||||||
RestartConfig RestartCommand { get; }
|
RestartConfig RestartCommand { get; }
|
||||||
Creds.VotesSettings Votes { get; }
|
Creds.VotesSettings Votes { get; }
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using NadekoBot.Common;
|
||||||
using NadekoBot.Common.Attributes;
|
using NadekoBot.Common.Attributes;
|
||||||
using NadekoBot.Services;
|
using NadekoBot.Services;
|
||||||
using NadekoBot.Db;
|
using NadekoBot.Db;
|
||||||
@@ -23,6 +24,7 @@ namespace NadekoBot.Modules.Games
|
|||||||
_db = db;
|
_db = db;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[NoPublicBot]
|
||||||
[NadekoCommand, Aliases]
|
[NadekoCommand, Aliases]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.ManageMessages)]
|
[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
|
namespace NadekoBot.Modules.Utility.Common.Patreon
|
||||||
{
|
{
|
||||||
public class PatreonData
|
public sealed class Attributes
|
||||||
{
|
{
|
||||||
public JObject[] Included { get; set; }
|
[JsonPropertyName("full_name")]
|
||||||
public JObject[] Data { get; set; }
|
public string FullName { get; set; }
|
||||||
public PatreonDataLinks Links { 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; }
|
[JsonPropertyName("id")]
|
||||||
public string next { get; set; }
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("type")]
|
||||||
|
public string Type { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PatreonUserAndReward
|
public sealed class Address
|
||||||
{
|
{
|
||||||
public PatreonUser User { get; set; }
|
[JsonPropertyName("data")]
|
||||||
public PatreonPledge Reward { get; set; }
|
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 Discord;
|
||||||
using NadekoBot.Common.Attributes;
|
using NadekoBot.Common.Attributes;
|
||||||
using NadekoBot.Modules.Utility.Services;
|
using NadekoBot.Modules.Utility.Services;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Utility
|
namespace NadekoBot.Modules.Utility
|
||||||
{
|
{
|
||||||
@@ -25,8 +26,12 @@ namespace NadekoBot.Modules.Utility
|
|||||||
[RequireContext(ContextType.DM)]
|
[RequireContext(ContextType.DM)]
|
||||||
public async Task ClaimPatreonRewards()
|
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;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (DateTime.UtcNow.Day < 5)
|
if (DateTime.UtcNow.Day < 5)
|
||||||
{
|
{
|
||||||
|
@@ -2,17 +2,21 @@
|
|||||||
using NadekoBot.Services;
|
using NadekoBot.Services;
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
using NadekoBot.Modules.Utility.Common.Patreon;
|
using NadekoBot.Modules.Utility.Common.Patreon;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Web;
|
||||||
using Discord;
|
using Discord;
|
||||||
using NadekoBot.Modules.Gambling.Services;
|
using NadekoBot.Modules.Gambling.Services;
|
||||||
using NadekoBot.Extensions;
|
using NadekoBot.Extensions;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
using StackExchange.Redis;
|
||||||
|
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Utility.Services
|
namespace NadekoBot.Modules.Utility.Services
|
||||||
{
|
{
|
||||||
@@ -20,96 +24,195 @@ namespace NadekoBot.Modules.Utility.Services
|
|||||||
{
|
{
|
||||||
private readonly SemaphoreSlim getPledgesLocker = new SemaphoreSlim(1, 1);
|
private readonly SemaphoreSlim getPledgesLocker = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
private PatreonUserAndReward[] _pledges;
|
|
||||||
|
|
||||||
private readonly Timer _updater;
|
private readonly Timer _updater;
|
||||||
private readonly SemaphoreSlim claimLockJustInCase = new SemaphoreSlim(1, 1);
|
private readonly SemaphoreSlim claimLockJustInCase = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
public TimeSpan Interval { get; } = TimeSpan.FromMinutes(3);
|
public TimeSpan Interval { get; } = TimeSpan.FromMinutes(3);
|
||||||
private readonly IBotCredentials _creds;
|
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
private readonly ICurrencyService _currency;
|
private readonly ICurrencyService _currency;
|
||||||
private readonly GamblingConfigService _gamblingConfigService;
|
private readonly GamblingConfigService _gamblingConfigService;
|
||||||
|
private readonly ConnectionMultiplexer _redis;
|
||||||
|
private readonly IBotCredsProvider _credsProvider;
|
||||||
private readonly IHttpClientFactory _httpFactory;
|
private readonly IHttpClientFactory _httpFactory;
|
||||||
private readonly IEmbedBuilderService _eb;
|
private readonly IEmbedBuilderService _eb;
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
|
|
||||||
public DateTime LastUpdate { get; private set; } = DateTime.UtcNow;
|
public DateTime LastUpdate { get; private set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
public PatreonRewardsService(IBotCredentials creds, DbService db,
|
public PatreonRewardsService(
|
||||||
ICurrencyService currency, IHttpClientFactory factory, IEmbedBuilderService eb,
|
DbService db,
|
||||||
DiscordSocketClient client, GamblingConfigService gamblingConfigService)
|
ICurrencyService currency,
|
||||||
|
IHttpClientFactory factory,
|
||||||
|
IEmbedBuilderService eb,
|
||||||
|
DiscordSocketClient client,
|
||||||
|
GamblingConfigService gamblingConfigService,
|
||||||
|
ConnectionMultiplexer redis,
|
||||||
|
IBotCredsProvider credsProvider)
|
||||||
{
|
{
|
||||||
_creds = creds;
|
|
||||||
_db = db;
|
_db = db;
|
||||||
_currency = currency;
|
_currency = currency;
|
||||||
_gamblingConfigService = gamblingConfigService;
|
_gamblingConfigService = gamblingConfigService;
|
||||||
|
_redis = redis;
|
||||||
|
_credsProvider = credsProvider;
|
||||||
_httpFactory = factory;
|
_httpFactory = factory;
|
||||||
_eb = eb;
|
_eb = eb;
|
||||||
_client = client;
|
_client = client;
|
||||||
|
|
||||||
if (client.ShardId == 0)
|
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);
|
null, TimeSpan.Zero, Interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RefreshPledges()
|
private DateTime LastAccessTokenUpdate(IBotCredentials creds)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(_creds.PatreonAccessToken)
|
var db = _redis.GetDatabase();
|
||||||
|| string.IsNullOrWhiteSpace(_creds.PatreonAccessToken))
|
var val = db.StringGet($"{creds.RedisKey()}_patreon_update");
|
||||||
return;
|
|
||||||
|
|
||||||
|
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)
|
if (DateTime.UtcNow.Day < 5)
|
||||||
return;
|
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;
|
LastUpdate = DateTime.UtcNow;
|
||||||
await getPledgesLocker.WaitAsync().ConfigureAwait(false);
|
await getPledgesLocker.WaitAsync().ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var rewards = new List<PatreonPledge>();
|
|
||||||
|
var members = new List<PatreonMember>();
|
||||||
var users = new List<PatreonUser>();
|
var users = new List<PatreonUser>();
|
||||||
using (var http = _httpFactory.CreateClient())
|
using (var http = _httpFactory.CreateClient())
|
||||||
{
|
{
|
||||||
http.DefaultRequestHeaders.Clear();
|
http.DefaultRequestHeaders.Clear();
|
||||||
http.DefaultRequestHeaders.Add("Authorization", "Bearer " + _creds.PatreonAccessToken);
|
http.DefaultRequestHeaders.TryAddWithoutValidation("Authorization",
|
||||||
var data = new PatreonData()
|
$"Bearer {creds.Patreon.AccessToken}");
|
||||||
{
|
|
||||||
Links = new PatreonDataLinks()
|
var page = $"https://www.patreon.com/api/oauth2/v2/campaigns/{creds.Patreon.CampaignId}/members" +
|
||||||
{
|
"?fields%5Bmember%5D=full_name,currently_entitled_amount_cents" +
|
||||||
next = $"https://api.patreon.com/oauth2/api/campaigns/{_creds.PatreonCampaignId}/pledges"
|
"&fields%5Buser%5D=social_connections" +
|
||||||
}
|
"&include=user";
|
||||||
};
|
PatreonResponse data = null;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
var res = await http.GetStringAsync(data.Links.next)
|
var res = await http.GetStringAsync(page).ConfigureAwait(false);
|
||||||
.ConfigureAwait(false);
|
data = JsonSerializer.Deserialize<PatreonResponse>(res);
|
||||||
data = JsonConvert.DeserializeObject<PatreonData>(res);
|
|
||||||
var pledgers = data.Data.Where(x => x["type"].ToString() == "pledge");
|
if (data is null)
|
||||||
rewards.AddRange(pledgers.Select(x => JsonConvert.DeserializeObject<PatreonPledge>(x.ToString()))
|
break;
|
||||||
.Where(x => x.attributes.declined_since is null));
|
|
||||||
if (data.Included != null)
|
members.AddRange(data.Data);
|
||||||
{
|
users.AddRange(data.Included);
|
||||||
users.AddRange(data.Included
|
} while (!string.IsNullOrWhiteSpace(page = data?.Links?.Next));
|
||||||
.Where(x => x["type"].ToString() == "user")
|
|
||||||
.Select(x => JsonConvert.DeserializeObject<PatreonUser>(x.ToString())));
|
|
||||||
}
|
|
||||||
} while (!string.IsNullOrWhiteSpace(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;
|
var userData = members.Join(users,
|
||||||
|
(m) => m.Relationships.User.Data.Id,
|
||||||
foreach (var pledge in _pledges)
|
(u) => u.Id,
|
||||||
{
|
(m, u) => new
|
||||||
var userIdStr = pledge.User.attributes?.social_connections?.discord?.user_id;
|
|
||||||
if (userIdStr != null && ulong.TryParse(userIdStr, out var userId))
|
|
||||||
{
|
{
|
||||||
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)
|
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);
|
await claimLockJustInCase.WaitAsync().ConfigureAwait(false);
|
||||||
var settings = _gamblingConfigService.Data;
|
var settings = _gamblingConfigService.Data;
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var datas = _pledges?.Where(x => x.User.attributes?.social_connections?.discord?.user_id == userId.ToString())
|
var eligibleFor = (int)(cents * settings.PatreonCurrencyPerCent);
|
||||||
?? Enumerable.Empty<PatreonUserAndReward>();
|
|
||||||
|
|
||||||
var totalAmount = 0;
|
using (var uow = _db.GetDbContext())
|
||||||
foreach (var data in datas)
|
|
||||||
{
|
{
|
||||||
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>();
|
users.Add(new RewardedUser()
|
||||||
var usr = users.FirstOrDefault(x => x.PatreonUserId == data.User.id);
|
|
||||||
|
|
||||||
if (usr is null)
|
|
||||||
{
|
{
|
||||||
users.Add(new RewardedUser()
|
PatreonUserId = patreonUserId,
|
||||||
{
|
LastReward = now,
|
||||||
PatreonUserId = data.User.id,
|
AmountRewardedThisMonth = eligibleFor,
|
||||||
LastReward = now,
|
});
|
||||||
AmountRewardedThisMonth = amount,
|
|
||||||
});
|
|
||||||
|
|
||||||
await uow.SaveChangesAsync();
|
await uow.SaveChangesAsync();
|
||||||
|
|
||||||
await _currency.AddAsync(userId, "Patreon reward - new", amount, gamble: true);
|
await _currency.AddAsync(userId, "Patreon reward - new", eligibleFor, gamble: true);
|
||||||
totalAmount += amount;
|
|
||||||
|
Log.Information($"Sending new currency reward to {userId}");
|
||||||
Log.Information($"Sending new currency reward to {userId}");
|
await SendMessageToUser(userId, $"Thank you for your pledge! " +
|
||||||
await SendMessageToUser(userId, $"Thank you for your pledge! " +
|
$"You've been awarded **{eligibleFor}**{settings.Currency.Sign} !");
|
||||||
$"You've been awarded **{amount}**{settings.Currency.Sign} !");
|
return eligibleFor;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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
|
finally
|
||||||
{
|
{
|
||||||
|
@@ -10,7 +10,14 @@ using Serilog;
|
|||||||
|
|
||||||
namespace NadekoBot.Services
|
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 readonly int? _totalShards;
|
||||||
private const string _credsFileName = "creds.yml";
|
private const string _credsFileName = "creds.yml";
|
||||||
@@ -27,7 +34,7 @@ namespace NadekoBot.Services
|
|||||||
|
|
||||||
|
|
||||||
private readonly object reloadLock = new object();
|
private readonly object reloadLock = new object();
|
||||||
private void Reload()
|
public void Reload()
|
||||||
{
|
{
|
||||||
lock (reloadLock)
|
lock (reloadLock)
|
||||||
{
|
{
|
||||||
@@ -102,6 +109,19 @@ namespace NadekoBot.Services
|
|||||||
Reload();
|
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>
|
/// <summary>
|
||||||
/// Checks if there's a V2 credentials file present, loads it if it exists,
|
/// 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
|
/// 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.
|
# Patreon auto reward system settings.
|
||||||
# go to https://www.patreon.com/portal -> my clients -> create client
|
# go to https://www.patreon.com/portal -> my clients -> create client
|
||||||
patreon:
|
patreon:
|
||||||
# Access token. You have to manually update this 1st of each month by refreshing the token on https://patreon.com/portal
|
clientId:
|
||||||
accessToken: ''
|
accessToken: ''
|
||||||
# Unused atm
|
|
||||||
refreshToken: ''
|
refreshToken: ''
|
||||||
# Unused atm
|
|
||||||
clientSecret: ''
|
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)
|
# 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: ''
|
campaignId: ''
|
||||||
|
Reference in New Issue
Block a user