Reenabled converter commands. Improved rewards reload on bots with multiple shards.
This commit is contained in:
parent
741538a982
commit
a8f2ca60c2
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,6 @@
|
|||||||
#Manually added files
|
#Manually added files
|
||||||
|
|
||||||
|
patreon_rewards.json
|
||||||
command_errors*.txt
|
command_errors*.txt
|
||||||
|
|
||||||
src/NadekoBot/Command Errors*.txt
|
src/NadekoBot/Command Errors*.txt
|
||||||
|
@ -1,94 +1,93 @@
|
|||||||
//using Discord;
|
using Discord;
|
||||||
//using Discord.Commands;
|
using Discord.Commands;
|
||||||
//using NadekoBot.Attributes;
|
using NadekoBot.Attributes;
|
||||||
//using NadekoBot.Extensions;
|
using NadekoBot.Extensions;
|
||||||
//using NadekoBot.Services.Utility;
|
using NadekoBot.Services.Utility;
|
||||||
//using System;
|
using System;
|
||||||
//using System.Linq;
|
using System.Linq;
|
||||||
//using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
////todo Rewrite
|
namespace NadekoBot.Modules.Utility
|
||||||
//namespace NadekoBot.Modules.Utility
|
{
|
||||||
//{
|
public partial class Utility
|
||||||
// public partial class Utility
|
{
|
||||||
// {
|
[Group]
|
||||||
// [Group]
|
public class UnitConverterCommands : NadekoSubmodule
|
||||||
// public class UnitConverterCommands : NadekoSubmodule
|
{
|
||||||
// {
|
private readonly ConverterService _service;
|
||||||
// private readonly ConverterService _service;
|
|
||||||
|
|
||||||
// public UnitConverterCommands(ConverterService service)
|
public UnitConverterCommands(ConverterService service)
|
||||||
// {
|
{
|
||||||
// _service = service;
|
_service = service;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// [NadekoCommand, Usage, Description, Aliases]
|
[NadekoCommand, Usage, Description, Aliases]
|
||||||
// public async Task ConvertList()
|
public async Task ConvertList()
|
||||||
// {
|
{
|
||||||
// var res = _service.Units.GroupBy(x => x.UnitType)
|
var res = _service.Units.GroupBy(x => x.UnitType)
|
||||||
// .Aggregate(new EmbedBuilder().WithTitle(GetText("convertlist"))
|
.Aggregate(new EmbedBuilder().WithTitle(GetText("convertlist"))
|
||||||
// .WithColor(NadekoBot.OkColor),
|
.WithColor(NadekoBot.OkColor),
|
||||||
// (embed, g) => embed.AddField(efb =>
|
(embed, g) => embed.AddField(efb =>
|
||||||
// efb.WithName(g.Key.ToTitleCase())
|
efb.WithName(g.Key.ToTitleCase())
|
||||||
// .WithValue(String.Join(", ", g.Select(x => x.Triggers.FirstOrDefault())
|
.WithValue(String.Join(", ", g.Select(x => x.Triggers.FirstOrDefault())
|
||||||
// .OrderBy(x => x)))));
|
.OrderBy(x => x)))));
|
||||||
// await Context.Channel.EmbedAsync(res);
|
await Context.Channel.EmbedAsync(res);
|
||||||
// }
|
}
|
||||||
// [NadekoCommand, Usage, Description, Aliases]
|
[NadekoCommand, Usage, Description, Aliases]
|
||||||
// public async Task Convert(string origin, string target, decimal value)
|
public async Task Convert(string origin, string target, decimal value)
|
||||||
// {
|
{
|
||||||
// var originUnit = _service.Units.Find(x => x.Triggers.Select(y => y.ToLowerInvariant()).Contains(origin.ToLowerInvariant()));
|
var originUnit = _service.Units.Find(x => x.Triggers.Select(y => y.ToLowerInvariant()).Contains(origin.ToLowerInvariant()));
|
||||||
// var targetUnit = _service.Units.Find(x => x.Triggers.Select(y => y.ToLowerInvariant()).Contains(target.ToLowerInvariant()));
|
var targetUnit = _service.Units.Find(x => x.Triggers.Select(y => y.ToLowerInvariant()).Contains(target.ToLowerInvariant()));
|
||||||
// if (originUnit == null || targetUnit == null)
|
if (originUnit == null || targetUnit == null)
|
||||||
// {
|
{
|
||||||
// await ReplyErrorLocalized("convert_not_found", Format.Bold(origin), Format.Bold(target)).ConfigureAwait(false);
|
await ReplyErrorLocalized("convert_not_found", Format.Bold(origin), Format.Bold(target)).ConfigureAwait(false);
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
// if (originUnit.UnitType != targetUnit.UnitType)
|
if (originUnit.UnitType != targetUnit.UnitType)
|
||||||
// {
|
{
|
||||||
// await ReplyErrorLocalized("convert_type_error", Format.Bold(originUnit.Triggers.First()), Format.Bold(targetUnit.Triggers.First())).ConfigureAwait(false);
|
await ReplyErrorLocalized("convert_type_error", Format.Bold(originUnit.Triggers.First()), Format.Bold(targetUnit.Triggers.First())).ConfigureAwait(false);
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
// decimal res;
|
decimal res;
|
||||||
// if (originUnit.Triggers == targetUnit.Triggers) res = value;
|
if (originUnit.Triggers == targetUnit.Triggers) res = value;
|
||||||
// else if (originUnit.UnitType == "temperature")
|
else if (originUnit.UnitType == "temperature")
|
||||||
// {
|
{
|
||||||
// //don't really care too much about efficiency, so just convert to Kelvin, then to target
|
//don't really care too much about efficiency, so just convert to Kelvin, then to target
|
||||||
// switch (originUnit.Triggers.First().ToUpperInvariant())
|
switch (originUnit.Triggers.First().ToUpperInvariant())
|
||||||
// {
|
{
|
||||||
// case "C":
|
case "C":
|
||||||
// res = value + 273.15m; //celcius!
|
res = value + 273.15m; //celcius!
|
||||||
// break;
|
break;
|
||||||
// case "F":
|
case "F":
|
||||||
// res = (value + 459.67m) * (5m / 9m);
|
res = (value + 459.67m) * (5m / 9m);
|
||||||
// break;
|
break;
|
||||||
// default:
|
default:
|
||||||
// res = value;
|
res = value;
|
||||||
// break;
|
break;
|
||||||
// }
|
}
|
||||||
// //from Kelvin to target
|
//from Kelvin to target
|
||||||
// switch (targetUnit.Triggers.First().ToUpperInvariant())
|
switch (targetUnit.Triggers.First().ToUpperInvariant())
|
||||||
// {
|
{
|
||||||
// case "C":
|
case "C":
|
||||||
// res = res - 273.15m; //celcius!
|
res = res - 273.15m; //celcius!
|
||||||
// break;
|
break;
|
||||||
// case "F":
|
case "F":
|
||||||
// res = res * (9m / 5m) - 459.67m;
|
res = res * (9m / 5m) - 459.67m;
|
||||||
// break;
|
break;
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// else
|
else
|
||||||
// {
|
{
|
||||||
// if (originUnit.UnitType == "currency")
|
if (originUnit.UnitType == "currency")
|
||||||
// {
|
{
|
||||||
// res = (value * targetUnit.Modifier) / originUnit.Modifier;
|
res = (value * targetUnit.Modifier) / originUnit.Modifier;
|
||||||
// }
|
}
|
||||||
// else
|
else
|
||||||
// res = (value * originUnit.Modifier) / targetUnit.Modifier;
|
res = (value * originUnit.Modifier) / targetUnit.Modifier;
|
||||||
// }
|
}
|
||||||
// res = Math.Round(res, 4);
|
res = Math.Round(res, 4);
|
||||||
|
|
||||||
// await Context.Channel.SendConfirmAsync(GetText("convert", value, (originUnit.Triggers.First()).SnPl(value.IsInteger() ? (int)value : 2), res, (targetUnit.Triggers.First() + "s").SnPl(res.IsInteger() ? (int)res : 2)));
|
await Context.Channel.SendConfirmAsync(GetText("convert", value, (originUnit.Triggers.First()).SnPl(value.IsInteger() ? (int)value : 2), res, (targetUnit.Triggers.First() + "s").SnPl(res.IsInteger() ? (int)res : 2)));
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
//}
|
}
|
@ -290,7 +290,10 @@ namespace NadekoBot
|
|||||||
|
|
||||||
Task SetClientReady()
|
Task SetClientReady()
|
||||||
{
|
{
|
||||||
clientReady.TrySetResult(true);
|
var _ = Task.Run(() =>
|
||||||
|
{
|
||||||
|
clientReady.TrySetResult(true);
|
||||||
|
});
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,5 +90,6 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Modules\Music\Classes\" />
|
<Folder Include="Modules\Music\Classes\" />
|
||||||
|
<Folder Include="Utility\Services\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -126,7 +126,6 @@ namespace NadekoBot.Services.Impl
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
};
|
};
|
||||||
|
|
||||||
//todo carbonitex update
|
|
||||||
if (sc != null)
|
if (sc != null)
|
||||||
{
|
{
|
||||||
_carbonitexTimer = new Timer(async (state) =>
|
_carbonitexTimer = new Timer(async (state) =>
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using NadekoBot.Services.Database.Models;
|
using Discord.WebSocket;
|
||||||
|
using NadekoBot.Services;
|
||||||
|
using NadekoBot.Services.Database.Models;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using NLog;
|
using NLog;
|
||||||
using System;
|
using System;
|
||||||
@ -11,27 +13,29 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace NadekoBot.Services.Utility
|
namespace NadekoBot.Services.Utility
|
||||||
{
|
{
|
||||||
//todo periodically load from the database, update only on shard 0
|
|
||||||
public class ConverterService
|
public class ConverterService
|
||||||
{
|
{
|
||||||
public List<ConvertUnit> Units { get; set; } = new List<ConvertUnit>();
|
public List<ConvertUnit> Units { get; } = new List<ConvertUnit>();
|
||||||
private readonly Logger _log;
|
private readonly Logger _log;
|
||||||
private Timer _timer;
|
private readonly Timer _currencyUpdater;
|
||||||
|
private readonly Timer _currencyLoader;
|
||||||
private readonly TimeSpan _updateInterval = new TimeSpan(12, 0, 0);
|
private readonly TimeSpan _updateInterval = new TimeSpan(12, 0, 0);
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
|
|
||||||
public ConverterService(DbService db)
|
public ConverterService(DiscordSocketClient client, DbService db)
|
||||||
{
|
{
|
||||||
_log = LogManager.GetCurrentClassLogger();
|
_log = LogManager.GetCurrentClassLogger();
|
||||||
_db = db;
|
_db = db;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var data = JsonConvert.DeserializeObject<List<MeasurementUnit>>(File.ReadAllText("data/units.json")).Select(u => new ConvertUnit()
|
var data = JsonConvert.DeserializeObject<List<MeasurementUnit>>(
|
||||||
{
|
File.ReadAllText("data/units.json"))
|
||||||
Modifier = u.Modifier,
|
.Select(u => new ConvertUnit()
|
||||||
UnitType = u.UnitType,
|
{
|
||||||
InternalTrigger = string.Join("|", u.Triggers)
|
Modifier = u.Modifier,
|
||||||
}).ToArray();
|
UnitType = u.UnitType,
|
||||||
|
InternalTrigger = string.Join("|", u.Triggers)
|
||||||
|
}).ToArray();
|
||||||
|
|
||||||
using (var uow = _db.UnitOfWork)
|
using (var uow = _db.UnitOfWork)
|
||||||
{
|
{
|
||||||
@ -48,10 +52,10 @@ namespace NadekoBot.Services.Utility
|
|||||||
_log.Warn("Could not load units: " + ex.Message);
|
_log.Warn("Could not load units: " + ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
_timer = new Timer(async (obj) => await UpdateCurrency(), null, _updateInterval, _updateInterval);
|
_currencyUpdater = new Timer(async (shouldLoad) => await UpdateCurrency((bool)shouldLoad), client.ShardId == 0, _updateInterval, _updateInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<Rates> UpdateCurrencyRates()
|
private async Task<Rates> GetCurrencyRates()
|
||||||
{
|
{
|
||||||
using (var http = new HttpClient())
|
using (var http = new HttpClient())
|
||||||
{
|
{
|
||||||
@ -60,38 +64,48 @@ namespace NadekoBot.Services.Utility
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateCurrency()
|
private async Task UpdateCurrency(bool shouldLoad)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var currencyRates = await UpdateCurrencyRates();
|
|
||||||
var unitTypeString = "currency";
|
var unitTypeString = "currency";
|
||||||
var range = currencyRates.ConversionRates.Select(u => new ConvertUnit()
|
if (shouldLoad)
|
||||||
{
|
{
|
||||||
InternalTrigger = u.Key,
|
var currencyRates = await GetCurrencyRates();
|
||||||
Modifier = u.Value,
|
var baseType = new ConvertUnit()
|
||||||
UnitType = unitTypeString
|
{
|
||||||
}).ToArray();
|
Triggers = new[] { currencyRates.Base },
|
||||||
var baseType = new ConvertUnit()
|
Modifier = decimal.One,
|
||||||
{
|
UnitType = unitTypeString
|
||||||
Triggers = new[] { currencyRates.Base },
|
};
|
||||||
Modifier = decimal.One,
|
var range = currencyRates.ConversionRates.Select(u => new ConvertUnit()
|
||||||
UnitType = unitTypeString
|
{
|
||||||
};
|
InternalTrigger = u.Key,
|
||||||
var toRemove = Units.Where(u => u.UnitType == unitTypeString);
|
Modifier = u.Value,
|
||||||
|
UnitType = unitTypeString
|
||||||
|
}).ToArray();
|
||||||
|
var toRemove = Units.Where(u => u.UnitType == unitTypeString);
|
||||||
|
|
||||||
using (var uow = _db.UnitOfWork)
|
using (var uow = _db.UnitOfWork)
|
||||||
{
|
{
|
||||||
uow.ConverterUnits.RemoveRange(toRemove.ToArray());
|
uow.ConverterUnits.RemoveRange(toRemove.ToArray());
|
||||||
uow.ConverterUnits.Add(baseType);
|
uow.ConverterUnits.Add(baseType);
|
||||||
uow.ConverterUnits.AddRange(range);
|
uow.ConverterUnits.AddRange(range);
|
||||||
|
|
||||||
await uow.CompleteAsync().ConfigureAwait(false);
|
await uow.CompleteAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
Units.RemoveAll(u => u.UnitType == unitTypeString);
|
||||||
|
Units.Add(baseType);
|
||||||
|
Units.AddRange(range);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using (var uow = _db.UnitOfWork)
|
||||||
|
{
|
||||||
|
Units.RemoveAll(u => u.UnitType == unitTypeString);
|
||||||
|
Units.AddRange(uow.ConverterUnits.GetAll().ToArray());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Units.RemoveAll(u => u.UnitType == unitTypeString);
|
|
||||||
Units.Add(baseType);
|
|
||||||
Units.AddRange(range);
|
|
||||||
_log.Info("Updated Currency");
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
using NadekoBot.Services.Database.Models;
|
using Discord.WebSocket;
|
||||||
|
using NadekoBot.Services.Database.Models;
|
||||||
using NadekoBot.Services.Utility.Patreon;
|
using NadekoBot.Services.Utility.Patreon;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using NLog;
|
using NLog;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@ -24,12 +27,13 @@ namespace NadekoBot.Services.Utility
|
|||||||
private readonly SemaphoreSlim claimLockJustInCase = new SemaphoreSlim(1, 1);
|
private readonly SemaphoreSlim claimLockJustInCase = new SemaphoreSlim(1, 1);
|
||||||
private readonly Logger _log;
|
private readonly Logger _log;
|
||||||
|
|
||||||
public readonly TimeSpan Interval = TimeSpan.FromMinutes(15);
|
public readonly TimeSpan Interval = TimeSpan.FromMinutes(3);
|
||||||
private IBotCredentials _creds;
|
private readonly IBotCredentials _creds;
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
private readonly CurrencyService _currency;
|
private readonly CurrencyService _currency;
|
||||||
|
|
||||||
public PatreonRewardsService(IBotCredentials creds, DbService db, CurrencyService currency)
|
public PatreonRewardsService(IBotCredentials creds, DbService db, CurrencyService currency,
|
||||||
|
DiscordSocketClient client)
|
||||||
{
|
{
|
||||||
_creds = creds;
|
_creds = creds;
|
||||||
_db = db;
|
_db = db;
|
||||||
@ -37,58 +41,63 @@ namespace NadekoBot.Services.Utility
|
|||||||
if (string.IsNullOrWhiteSpace(creds.PatreonAccessToken))
|
if (string.IsNullOrWhiteSpace(creds.PatreonAccessToken))
|
||||||
return;
|
return;
|
||||||
_log = LogManager.GetCurrentClassLogger();
|
_log = LogManager.GetCurrentClassLogger();
|
||||||
Updater = new Timer(async (_) => await LoadPledges(), null, TimeSpan.Zero, Interval);
|
Updater = new Timer(async (load) => await RefreshPledges((bool)load), client.ShardId == 0, TimeSpan.Zero, Interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task LoadPledges()
|
public async Task RefreshPledges(bool shouldLoad)
|
||||||
{
|
{
|
||||||
LastUpdate = DateTime.UtcNow;
|
if (shouldLoad)
|
||||||
await getPledgesLocker.WaitAsync(1000).ConfigureAwait(false);
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var rewards = new List<PatreonPledge>();
|
LastUpdate = DateTime.UtcNow;
|
||||||
var users = new List<PatreonUser>();
|
await getPledgesLocker.WaitAsync().ConfigureAwait(false);
|
||||||
using (var http = new HttpClient())
|
try
|
||||||
{
|
{
|
||||||
http.DefaultRequestHeaders.Clear();
|
var rewards = new List<PatreonPledge>();
|
||||||
http.DefaultRequestHeaders.Add("Authorization", "Bearer " + _creds.PatreonAccessToken);
|
var users = new List<PatreonUser>();
|
||||||
var data = new PatreonData()
|
using (var http = new HttpClient())
|
||||||
{
|
{
|
||||||
Links = new PatreonDataLinks()
|
http.DefaultRequestHeaders.Clear();
|
||||||
|
http.DefaultRequestHeaders.Add("Authorization", "Bearer " + _creds.PatreonAccessToken);
|
||||||
|
var data = new PatreonData()
|
||||||
{
|
{
|
||||||
next = "https://api.patreon.com/oauth2/api/campaigns/334038/pledges"
|
Links = new PatreonDataLinks()
|
||||||
}
|
{
|
||||||
};
|
next = "https://api.patreon.com/oauth2/api/campaigns/334038/pledges"
|
||||||
do
|
}
|
||||||
|
};
|
||||||
|
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 == null));
|
||||||
|
users.AddRange(data.Included
|
||||||
|
.Where(x => x["type"].ToString() == "user")
|
||||||
|
.Select(x => JsonConvert.DeserializeObject<PatreonUser>(x.ToString())));
|
||||||
|
} while (!string.IsNullOrWhiteSpace(data.Links.next));
|
||||||
|
}
|
||||||
|
Pledges = rewards.Join(users, (r) => r.relationships?.patron?.data?.id, (u) => u.id, (x, y) => new PatreonUserAndReward()
|
||||||
{
|
{
|
||||||
var res = await http.GetStringAsync(data.Links.next)
|
User = y,
|
||||||
.ConfigureAwait(false);
|
Reward = x,
|
||||||
data = JsonConvert.DeserializeObject<PatreonData>(res);
|
}).ToImmutableArray();
|
||||||
var pledgers = data.Data.Where(x => x["type"].ToString() == "pledge");
|
File.WriteAllText("./patreon_rewards.json", JsonConvert.SerializeObject(Pledges));
|
||||||
rewards.AddRange(pledgers.Select(x => JsonConvert.DeserializeObject<PatreonPledge>(x.ToString()))
|
|
||||||
.Where(x => x.attributes.declined_since == null));
|
|
||||||
users.AddRange(data.Included
|
|
||||||
.Where(x => x["type"].ToString() == "user")
|
|
||||||
.Select(x => JsonConvert.DeserializeObject<PatreonUser>(x.ToString())));
|
|
||||||
} while (!string.IsNullOrWhiteSpace(data.Links.next));
|
|
||||||
}
|
}
|
||||||
Pledges = rewards.Join(users, (r) => r.relationships?.patron?.data?.id, (u) => u.id, (x, y) => new PatreonUserAndReward()
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
User = y,
|
_log.Warn(ex);
|
||||||
Reward = x,
|
}
|
||||||
}).ToImmutableArray();
|
finally
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_log.Warn(ex);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
var _ = Task.Run(async () =>
|
|
||||||
{
|
{
|
||||||
await Task.Delay(TimeSpan.FromMinutes(5)).ConfigureAwait(false);
|
|
||||||
getPledgesLocker.Release();
|
getPledgesLocker.Release();
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Pledges = JsonConvert.DeserializeObject<PatreonUserAndReward[]>(File.ReadAllText("./patreon_rewards.json"))
|
||||||
|
.ToImmutableArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user