mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
Fixed bugs when users update their patreon pledge. Updated some packages
This commit is contained in:
@@ -35,4 +35,14 @@ public class PatronUser
|
|||||||
|
|
||||||
// Date Only component
|
// Date Only component
|
||||||
public DateTime ValidThru { get; set; }
|
public DateTime ValidThru { get; set; }
|
||||||
|
|
||||||
|
public PatronUser Clone()
|
||||||
|
=> new PatronUser()
|
||||||
|
{
|
||||||
|
UniquePlatformUserId = this.UniquePlatformUserId,
|
||||||
|
UserId = this.UserId,
|
||||||
|
AmountCents = this.AmountCents,
|
||||||
|
LastCharge = this.LastCharge,
|
||||||
|
ValidThru = this.ValidThru
|
||||||
|
};
|
||||||
}
|
}
|
@@ -75,19 +75,4 @@ public sealed class BankService : IBankService, INService
|
|||||||
?.Balance
|
?.Balance
|
||||||
?? 0;
|
?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<long> BurnAllAsync(ulong userId)
|
|
||||||
{
|
|
||||||
await using var ctx = _db.GetDbContext();
|
|
||||||
var output = await ctx.GetTable<BankUser>()
|
|
||||||
.Where(x => x.UserId == userId)
|
|
||||||
.UpdateWithOutputAsync(old => new()
|
|
||||||
{
|
|
||||||
Balance = 0
|
|
||||||
});
|
|
||||||
if (output.Length == 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return output[0].Deleted.Balance;
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -5,5 +5,4 @@ public interface IBankService
|
|||||||
Task<bool> DepositAsync(ulong userId, long amount);
|
Task<bool> DepositAsync(ulong userId, long amount);
|
||||||
Task<bool> WithdrawAsync(ulong userId, long amount);
|
Task<bool> WithdrawAsync(ulong userId, long amount);
|
||||||
Task<long> GetBalanceAsync(ulong userId);
|
Task<long> GetBalanceAsync(ulong userId);
|
||||||
Task<long> BurnAllAsync(ulong userId);
|
|
||||||
}
|
}
|
@@ -50,67 +50,91 @@ public class CurrencyRewardService : INService, IDisposable
|
|||||||
|
|
||||||
private async Task OnPatronUpdate(Patron oldPatron, Patron newPatron)
|
private async Task OnPatronUpdate(Patron oldPatron, Patron newPatron)
|
||||||
{
|
{
|
||||||
if (oldPatron.Amount != newPatron.Amount)
|
// if pledge was increased
|
||||||
|
if (oldPatron.Amount < newPatron.Amount)
|
||||||
{
|
{
|
||||||
var conf = _config.Data;
|
var conf = _config.Data;
|
||||||
|
var newAmount = (long)(newPatron.Amount * conf.PatreonCurrencyPerCent);
|
||||||
|
|
||||||
var newAmount = (long)(Math.Max(newPatron.Amount, oldPatron.Amount) * conf.PatreonCurrencyPerCent);
|
RewardedUser old;
|
||||||
UpdateOutput<RewardedUser>[] output;
|
|
||||||
await using (var ctx = _db.GetDbContext())
|
await using (var ctx = _db.GetDbContext())
|
||||||
{
|
{
|
||||||
output = await ctx.GetTable<RewardedUser>()
|
old = await ctx.GetTable<RewardedUser>()
|
||||||
.Where(x => x.PlatformUserId == newPatron.UnqiuePlatformUserId)
|
.Where(x => x.PlatformUserId == newPatron.UniquePlatformUserId)
|
||||||
.UpdateWithOutputAsync(old => new()
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
if (old is null)
|
||||||
|
{
|
||||||
|
await OnNewPayment(newPatron);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no action as the amount is the same or lower
|
||||||
|
if (old.AmountRewardedThisMonth >= newAmount)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var count = await ctx.GetTable<RewardedUser>()
|
||||||
|
.Where(x => x.PlatformUserId == newPatron.UniquePlatformUserId)
|
||||||
|
.UpdateAsync(_ => new()
|
||||||
{
|
{
|
||||||
PlatformUserId = newPatron.UnqiuePlatformUserId,
|
PlatformUserId = newPatron.UniquePlatformUserId,
|
||||||
UserId = newPatron.UserId,
|
UserId = newPatron.UserId,
|
||||||
// amount before bonuses
|
// amount before bonuses
|
||||||
AmountRewardedThisMonth = newAmount,
|
AmountRewardedThisMonth = newAmount,
|
||||||
LastReward = newPatron.PaidAt
|
LastReward = newPatron.PaidAt
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// shouldn't ever happen
|
||||||
|
if (count == 0)
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the user wasn't previously in the db for some reason,
|
var oldAmount = old.AmountRewardedThisMonth;
|
||||||
// we will treat him as a new patron
|
|
||||||
if (output.Length == 0)
|
|
||||||
{
|
|
||||||
await OnNewPayment(newPatron);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var oldAmount = output[0].Deleted.AmountRewardedThisMonth;
|
var realNewAmount = GetRealCurrencyReward(
|
||||||
|
(int)(newAmount / conf.PatreonCurrencyPerCent),
|
||||||
var diff = newAmount - oldAmount;
|
newAmount,
|
||||||
|
out var percentBonus);
|
||||||
|
|
||||||
|
var realOldAmount = GetRealCurrencyReward(
|
||||||
|
(int)(oldAmount / conf.PatreonCurrencyPerCent),
|
||||||
|
oldAmount,
|
||||||
|
out _);
|
||||||
|
|
||||||
|
var diff = realNewAmount - realOldAmount;
|
||||||
if (diff <= 0)
|
if (diff <= 0)
|
||||||
return; // no action if new is lower
|
return; // no action if new is lower
|
||||||
|
|
||||||
// if the user pledges 5$ or more, they will get X % more flowers where X is amount in dollars,
|
// if the user pledges 5$ or more, they will get X % more flowers where X is amount in dollars,
|
||||||
// up to 100%
|
// up to 100%
|
||||||
|
|
||||||
var realAmount = GetRealCurrencyReward(newPatron.Amount, diff, out var percentBonus);
|
await _cs.AddAsync(newPatron.UserId, diff, new TxData("patron","update"));
|
||||||
await _cs.AddAsync(newPatron.UserId, realAmount, new TxData("patron","update"));
|
|
||||||
|
|
||||||
_ = SendMessageToUser(newPatron.UserId,
|
_ = SendMessageToUser(newPatron.UserId,
|
||||||
$"You've received an additional **{realAmount}**{_config.Data.Currency.Sign} as a currency reward (+{percentBonus}%)!");
|
$"You've received an additional **{diff}**{_config.Data.Currency.Sign} as a currency reward (+{percentBonus}%)!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private long GetRealCurrencyReward(int fullPledge, long currentAmount, out int percentBonus)
|
private long GetRealCurrencyReward(int pledgeCents, long modifiedAmount, out int percentBonus)
|
||||||
{
|
{
|
||||||
// needs at least 5$ to be eligible for a bonus
|
// needs at least 5$ to be eligible for a bonus
|
||||||
if (fullPledge < 500)
|
if (pledgeCents < 500)
|
||||||
{
|
{
|
||||||
percentBonus = 0;
|
percentBonus = 0;
|
||||||
return currentAmount;
|
return modifiedAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
var dollarValue = fullPledge / 100;
|
var dollarValue = pledgeCents / 100;
|
||||||
percentBonus = dollarValue switch
|
percentBonus = dollarValue switch
|
||||||
{
|
{
|
||||||
> 100 => 100,
|
>= 100 => 100,
|
||||||
_ => dollarValue
|
>= 50 => 50,
|
||||||
|
>= 20 => 20,
|
||||||
|
>= 10 => 10,
|
||||||
|
>= 5 => 5,
|
||||||
|
_ => 0
|
||||||
};
|
};
|
||||||
return (long)(currentAmount * (1 + (percentBonus / 100.0f)));
|
return (long)(modifiedAmount * (1 + (percentBonus / 100.0f)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// on a new payment, always give the full amount.
|
// on a new payment, always give the full amount.
|
||||||
@@ -121,7 +145,7 @@ public class CurrencyRewardService : INService, IDisposable
|
|||||||
await ctx.GetTable<RewardedUser>()
|
await ctx.GetTable<RewardedUser>()
|
||||||
.InsertOrUpdateAsync(() => new()
|
.InsertOrUpdateAsync(() => new()
|
||||||
{
|
{
|
||||||
PlatformUserId = patron.UnqiuePlatformUserId,
|
PlatformUserId = patron.UniquePlatformUserId,
|
||||||
UserId = patron.UserId,
|
UserId = patron.UserId,
|
||||||
AmountRewardedThisMonth = amount,
|
AmountRewardedThisMonth = amount,
|
||||||
LastReward = patron.PaidAt,
|
LastReward = patron.PaidAt,
|
||||||
@@ -134,7 +158,7 @@ public class CurrencyRewardService : INService, IDisposable
|
|||||||
},
|
},
|
||||||
() => new()
|
() => new()
|
||||||
{
|
{
|
||||||
PlatformUserId = patron.UnqiuePlatformUserId
|
PlatformUserId = patron.UniquePlatformUserId
|
||||||
});
|
});
|
||||||
|
|
||||||
var realAmount = GetRealCurrencyReward(patron.Amount, amount, out var percentBonus);
|
var realAmount = GetRealCurrencyReward(patron.Amount, amount, out var percentBonus);
|
||||||
@@ -167,24 +191,9 @@ public class CurrencyRewardService : INService, IDisposable
|
|||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
_ = await ctx.GetTable<RewardedUser>()
|
_ = await ctx.GetTable<RewardedUser>()
|
||||||
.UpdateWithOutputAsync(old => new()
|
.UpdateAsync(old => new()
|
||||||
{
|
{
|
||||||
AmountRewardedThisMonth = old.AmountRewardedThisMonth * 2
|
AmountRewardedThisMonth = old.AmountRewardedThisMonth * 2
|
||||||
});
|
});
|
||||||
|
|
||||||
// var toTake = old.Length == 0
|
|
||||||
// ? patron.Amount
|
|
||||||
// : old[0].Inserted.AmountRewardedThisMonth;
|
|
||||||
|
|
||||||
// if (toTake > 0)
|
|
||||||
// {
|
|
||||||
// Log.Warning("Wiping the wallet and bank of the user {UserId} due to a refund/fraud...",
|
|
||||||
// patron.UserId);
|
|
||||||
// await _cs.RemoveAsync(patron.UserId, patron.Amount, new("patreon", "refund"));
|
|
||||||
// await _bs.BurnAllAsync(patron.UserId);
|
|
||||||
// Log.Warning("Burned {Amount} currency from the bank of the user {UserId} due to a refund/fraud.",
|
|
||||||
// patron.Amount,
|
|
||||||
// patron.UserId);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -5,10 +5,10 @@ public readonly struct Patron
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unique id assigned to this patron by the payment platform
|
/// Unique id assigned to this patron by the payment platform
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string UnqiuePlatformUserId { get; init; }
|
public string UniquePlatformUserId { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Discord UserId to which this <see cref="UnqiuePlatformUserId"/> is connected to
|
/// Discord UserId to which this <see cref="UniquePlatformUserId"/> is connected to
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ulong UserId { get; init; }
|
public ulong UserId { get; init; }
|
||||||
|
|
||||||
|
@@ -173,7 +173,7 @@ public sealed class PatronageService
|
|||||||
|
|
||||||
var lastChargeUtc = subscriber.LastCharge.Value.ToUniversalTime();
|
var lastChargeUtc = subscriber.LastCharge.Value.ToUniversalTime();
|
||||||
var dateInOneMonth = lastChargeUtc.Date.AddMonths(1);
|
var dateInOneMonth = lastChargeUtc.Date.AddMonths(1);
|
||||||
await using var tran = await ctx.Database.BeginTransactionAsync();
|
// await using var tran = await ctx.Database.BeginTransactionAsync();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var dbPatron = await ctx.GetTable<PatronUser>()
|
var dbPatron = await ctx.GetTable<PatronUser>()
|
||||||
@@ -193,7 +193,7 @@ public sealed class PatronageService
|
|||||||
ValidThru = dateInOneMonth,
|
ValidThru = dateInOneMonth,
|
||||||
});
|
});
|
||||||
|
|
||||||
await tran.CommitAsync();
|
// await tran.CommitAsync();
|
||||||
|
|
||||||
var newPatron = PatronUserToPatron(dbPatron);
|
var newPatron = PatronUserToPatron(dbPatron);
|
||||||
_ = SendWelcomeMessage(newPatron);
|
_ = SendWelcomeMessage(newPatron);
|
||||||
@@ -222,45 +222,38 @@ public sealed class PatronageService
|
|||||||
// this should never happen
|
// this should never happen
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
{
|
{
|
||||||
await tran.RollbackAsync();
|
// await tran.RollbackAsync();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
await tran.CommitAsync();
|
// await tran.CommitAsync();
|
||||||
|
|
||||||
await OnNewPatronPayment(PatronUserToPatron(dbPatron));
|
await OnNewPatronPayment(PatronUserToPatron(dbPatron));
|
||||||
}
|
}
|
||||||
else if (dbPatron.AmountCents != subscriber.Cents // if user changed the amount
|
else if (dbPatron.AmountCents != subscriber.Cents // if user changed the amount
|
||||||
|| dbPatron.UserId != subscriber.UserId) // if user updated user id)
|
|| dbPatron.UserId != subscriber.UserId) // if user updated user id)
|
||||||
{
|
{
|
||||||
|
var cents = subscriber.Cents;
|
||||||
// the user updated the pledge or changed the connected discord account
|
// the user updated the pledge or changed the connected discord account
|
||||||
var count = await ctx.GetTable<PatronUser>()
|
await ctx.GetTable<PatronUser>()
|
||||||
.Where(x => x.UniquePlatformUserId == subscriber.UniquePlatformUserId
|
.Where(x => x.UniquePlatformUserId == subscriber.UniquePlatformUserId)
|
||||||
&& x.LastCharge < lastChargeUtc)
|
.UpdateAsync(old => new()
|
||||||
.UpdateAsync(old => new()
|
{
|
||||||
{
|
UserId = subscriber.UserId,
|
||||||
UserId = subscriber.UserId,
|
AmountCents = cents,
|
||||||
AmountCents = subscriber.Cents,
|
LastCharge = lastChargeUtc,
|
||||||
LastCharge = lastChargeUtc,
|
ValidThru = old.ValidThru,
|
||||||
ValidThru = old.ValidThru,
|
});
|
||||||
});
|
|
||||||
|
|
||||||
if (count == 0)
|
var newPatron = dbPatron.Clone();
|
||||||
{
|
newPatron.AmountCents = cents;
|
||||||
await tran.RollbackAsync();
|
newPatron.UserId = subscriber.UserId;
|
||||||
continue;
|
|
||||||
}
|
// idk what's going on but UpdateWithOutputAsync doesn't work properly here
|
||||||
|
// nor does firstordefault after update. I'm not seeing something obvious
|
||||||
var newData = await ctx.GetTable<PatronUser>()
|
|
||||||
.FirstAsync(x => x.UniquePlatformUserId
|
|
||||||
== subscriber.UniquePlatformUserId);
|
|
||||||
|
|
||||||
|
|
||||||
await tran.CommitAsync();
|
|
||||||
await OnPatronUpdated(
|
await OnPatronUpdated(
|
||||||
PatronUserToPatron(dbPatron),
|
PatronUserToPatron(dbPatron),
|
||||||
PatronUserToPatron(newData));
|
PatronUserToPatron(newPatron));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -745,7 +738,7 @@ public sealed class PatronageService
|
|||||||
private Patron PatronUserToPatron(PatronUser user)
|
private Patron PatronUserToPatron(PatronUser user)
|
||||||
=> new Patron()
|
=> new Patron()
|
||||||
{
|
{
|
||||||
UnqiuePlatformUserId = user.UniquePlatformUserId,
|
UniquePlatformUserId = user.UniquePlatformUserId,
|
||||||
UserId = user.UserId,
|
UserId = user.UserId,
|
||||||
Amount = user.AmountCents,
|
Amount = user.AmountCents,
|
||||||
Tier = CalculateTier(user),
|
Tier = CalculateTier(user),
|
||||||
@@ -798,7 +791,7 @@ public sealed class PatronageService
|
|||||||
*- Any user in any of your servers can use Patron-only commands, but they will spend **your quota**, which is why it's recommended to use Nadeko's command cooldown system (.h .cmdcd) or permission system to limit the command usage for your server members.*
|
*- Any user in any of your servers can use Patron-only commands, but they will spend **your quota**, which is why it's recommended to use Nadeko's command cooldown system (.h .cmdcd) or permission system to limit the command usage for your server members.*
|
||||||
*- Permission guide can be found here if you're not familiar with it: <https://nadekobot.readthedocs.io/en/latest/permissions-system/>*",
|
*- Permission guide can be found here if you're not familiar with it: <https://nadekobot.readthedocs.io/en/latest/permissions-system/>*",
|
||||||
isInline: false)
|
isInline: false)
|
||||||
.WithFooter($"platform id: {patron.UnqiuePlatformUserId}");
|
.WithFooter($"platform id: {patron.UniquePlatformUserId}");
|
||||||
|
|
||||||
await user.EmbedAsync(eb);
|
await user.EmbedAsync(eb);
|
||||||
}
|
}
|
||||||
|
@@ -69,15 +69,15 @@
|
|||||||
<PackageReference Include="JetBrains.Annotations" Version="2022.1.0" />
|
<PackageReference Include="JetBrains.Annotations" Version="2022.1.0" />
|
||||||
|
|
||||||
<!-- Db-related packages -->
|
<!-- Db-related packages -->
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.5" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.5">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.5">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|
||||||
<PackageReference Include="linq2db.EntityFrameworkCore" Version="6.7.1" />
|
<PackageReference Include="linq2db.EntityFrameworkCore" Version="6.8.0" />
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.5" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.6" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.4" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.4" />
|
||||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.1" />
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.1" />
|
||||||
|
|
||||||
|
@@ -23,6 +23,8 @@ quotas:
|
|||||||
C: 100000
|
C: 100000
|
||||||
# Dictionary of commands with their respective quota data
|
# Dictionary of commands with their respective quota data
|
||||||
commands:
|
commands:
|
||||||
|
cleverbot:
|
||||||
|
V: null
|
||||||
prune:
|
prune:
|
||||||
X:
|
X:
|
||||||
PerHour: 1
|
PerHour: 1
|
||||||
|
Reference in New Issue
Block a user