Reworked currency service. Some features are missing

This commit is contained in:
Kwoth
2022-01-20 13:22:38 +01:00
parent fa41c5a319
commit 0ef2da6f10
31 changed files with 433 additions and 325 deletions

View File

@@ -342,6 +342,7 @@ resharper_place_simple_embedded_statement_on_same_line = false
resharper_wrap_chained_binary_expressions = chop_if_long resharper_wrap_chained_binary_expressions = chop_if_long
resharper_wrap_chained_binary_patterns = chop_if_long resharper_wrap_chained_binary_patterns = chop_if_long
resharper_wrap_chained_method_calls = chop_if_long resharper_wrap_chained_method_calls = chop_if_long
resharper_wrap_object_and_collection_initializer_style = chop_always
resharper_csharp_wrap_before_first_type_parameter_constraint = true resharper_csharp_wrap_before_first_type_parameter_constraint = true
resharper_csharp_place_type_constraints_on_same_line = false resharper_csharp_place_type_constraints_on_same_line = false

View File

@@ -75,7 +75,7 @@ public sealed class AnimalRace : IDisposable
if (CurrentPhase != Phase.WaitingForPlayers) if (CurrentPhase != Phase.WaitingForPlayers)
throw new AlreadyStartedException(); throw new AlreadyStartedException();
if (!await _currency.RemoveAsync(userId, "BetRace", bet)) if (!await _currency.RemoveAsync(userId, bet, new("animalrace", "bet")))
throw new NotEnoughFundsException(); throw new NotEnoughFundsException();
if (_users.Contains(user)) if (_users.Contains(user))
@@ -100,7 +100,7 @@ public sealed class AnimalRace : IDisposable
{ {
foreach (var user in _users) foreach (var user in _users)
if (user.Bet > 0) if (user.Bet > 0)
await _currency.AddAsync(user.UserId, "Race refund", user.Bet); await _currency.AddAsync(user.UserId, user.Bet, new("animalrace", "refund"));
_ = OnStartingFailed?.Invoke(this); _ = OnStartingFailed?.Invoke(this);
CurrentPhase = Phase.Ended; CurrentPhase = Phase.Ended;
@@ -130,8 +130,8 @@ public sealed class AnimalRace : IDisposable
if (FinishedUsers[0].Bet > 0) if (FinishedUsers[0].Bet > 0)
await _currency.AddAsync(FinishedUsers[0].UserId, await _currency.AddAsync(FinishedUsers[0].UserId,
"Won a Race", FinishedUsers[0].Bet * (_users.Count - 1),
FinishedUsers[0].Bet * (_users.Count - 1)); new("animalrace", "win"));
_ = OnEnded?.Invoke(this); _ = OnEnded?.Invoke(this);
}); });

View File

@@ -120,7 +120,7 @@ public class Blackjack
if (Players.Count >= 5) if (Players.Count >= 5)
return false; return false;
if (!await _cs.RemoveAsync(user, "BlackJack-gamble", bet, gamble: true)) return false; if (!await _cs.RemoveAsync(user, bet, new("blackjack","gamble"))) return false;
Players.Add(new(user, bet)); Players.Add(new(user, bet));
_= PrintState(); _= PrintState();
@@ -210,7 +210,7 @@ public class Blackjack
foreach (var usr in Players) foreach (var usr in Players)
if (usr.State is User.UserState.Won or User.UserState.Blackjack) if (usr.State is User.UserState.Won or User.UserState.Blackjack)
await _cs.AddAsync(usr.DiscordUser.Id, "BlackJack-win", usr.Bet * 2, true); await _cs.AddAsync(usr.DiscordUser.Id, usr.Bet * 2, new("blackjack", "win"));
} }
public async Task<bool> Double(IUser u) public async Task<bool> Double(IUser u)
@@ -234,7 +234,7 @@ public class Blackjack
if (CurrentUser != u) if (CurrentUser != u)
return false; return false;
if (!await _cs.RemoveAsync(u.DiscordUser.Id, "Blackjack-double", u.Bet)) if (!await _cs.RemoveAsync(u.DiscordUser.Id, u.Bet, new("blackjack", "double")))
return false; return false;
u.Bet *= 2; u.Bet *= 2;

View File

@@ -98,7 +98,7 @@ public sealed class Connect4Game : IDisposable
{ {
var __ = OnGameFailedToStart?.Invoke(this); var __ = OnGameFailedToStart?.Invoke(this);
CurrentPhase = Phase.Ended; CurrentPhase = Phase.Ended;
await _cs.AddAsync(_players[0].Value.UserId, "Connect4-refund", _options.Bet, true); await _cs.AddAsync(_players[0].Value.UserId, _options.Bet, new("connect4", "refund"));
} }
} }
finally { _locker.Release(); } finally { _locker.Release(); }
@@ -119,7 +119,7 @@ public sealed class Connect4Game : IDisposable
if (bet != _options.Bet) // can't join if bet amount is not the same if (bet != _options.Bet) // can't join if bet amount is not the same
return false; return false;
if (!await _cs.RemoveAsync(userId, "Connect4-bet", bet, true)) // user doesn't have enough money to gamble if (!await _cs.RemoveAsync(userId, bet, new("connect4", "bet"))) // user doesn't have enough money to gamble
return false; return false;
if (_rng.Next(0, 2) == 0) //rolling from 0-1, if number is 0, join as first player if (_rng.Next(0, 2) == 0) //rolling from 0-1, if number is 0, join as first player
@@ -342,13 +342,13 @@ public sealed class Connect4Game : IDisposable
if (result == Result.Draw) if (result == Result.Draw)
{ {
_cs.AddAsync(CurrentPlayer.UserId, "Connect4-draw", _options.Bet, true); _cs.AddAsync(CurrentPlayer.UserId, _options.Bet, new("connect4", "draw"));
_cs.AddAsync(OtherPlayer.UserId, "Connect4-draw", _options.Bet, true); _cs.AddAsync(OtherPlayer.UserId, _options.Bet, new("connect4", "draw"));
return; return;
} }
if (winId is not null) if (winId is not null)
_cs.AddAsync(winId.Value, "Connnect4-win", (long)(_options.Bet * 1.98), true); _cs.AddAsync(winId.Value, (long)(_options.Bet * 1.98), new("connect4", "win"));
} }
private Field GetPlayerPiece(ulong userId) private Field GetPlayerPiece(ulong userId)

View File

@@ -64,7 +64,7 @@ public partial class Gambling
} }
if (options.Bet > 0) if (options.Bet > 0)
if (!await _cs.RemoveAsync(ctx.User.Id, "Connect4-bet", options.Bet, true)) if (!await _cs.RemoveAsync(ctx.User.Id, options.Bet, new("connect4", "bet")))
{ {
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
_service.Connect4Games.TryRemove(ctx.Channel.Id, out _); _service.Connect4Games.TryRemove(ctx.Channel.Id, out _);

View File

@@ -76,9 +76,9 @@ public class GameStatusEvent : ICurrencyEvent
try try
{ {
await _cs.AddBulkAsync(toAward, await _cs.AddBulkAsync(toAward,
toAward.Select(_ => "GameStatus Event"), _amount,
toAward.Select(_ => _amount), new("event", "gamestatus")
true); );
if (_isPotLimited) if (_isPotLimited)
await msg.ModifyAsync(m => await msg.ModifyAsync(m =>

View File

@@ -71,7 +71,7 @@ public class ReactionEvent : ICurrencyEvent
try try
{ {
await _cs.AddBulkAsync(toAward, toAward.Select(_ => "Reaction Event"), toAward.Select(_ => _amount), true); await _cs.AddBulkAsync(toAward, _amount, new("event", "reaction"));
if (_isPotLimited) if (_isPotLimited)
await msg.ModifyAsync(m => await msg.ModifyAsync(m =>

View File

@@ -80,7 +80,7 @@ public partial class Gambling
if (!await CheckBetMandatory(amount) || amount == 1) if (!await CheckBetMandatory(amount) || amount == 1)
return; return;
var removed = await _cs.RemoveAsync(ctx.User, "Betflip Gamble", amount, false, true); var removed = await _cs.RemoveAsync(ctx.User, amount, new("betflip", "bet"));
if (!removed) if (!removed)
{ {
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
@@ -106,7 +106,7 @@ public partial class Gambling
{ {
var toWin = (long)(amount * Config.BetFlip.Multiplier); var toWin = (long)(amount * Config.BetFlip.Multiplier);
str = Format.Bold(ctx.User.ToString()) + " " + GetText(strs.flip_guess(toWin + CurrencySign)); str = Format.Bold(ctx.User.ToString()) + " " + GetText(strs.flip_guess(toWin + CurrencySign));
await _cs.AddAsync(ctx.User, "Betflip Gamble", toWin, false, true); await _cs.AddAsync(ctx.User, toWin, new("betflip", "win"));
} }
else else
{ {

View File

@@ -3,12 +3,14 @@ using NadekoBot.Db;
using NadekoBot.Db.Models; using NadekoBot.Db.Models;
using NadekoBot.Modules.Gambling.Common; using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Modules.Gambling.Services; using NadekoBot.Modules.Gambling.Services;
using NadekoBot.Services.Currency;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using System.Globalization; using System.Globalization;
using System.Numerics; using System.Numerics;
namespace NadekoBot.Modules.Gambling; namespace NadekoBot.Modules.Gambling;
// todo leave empty servers
public partial class Gambling : GamblingModule<GamblingService> public partial class Gambling : GamblingModule<GamblingService>
{ {
public enum RpsPick public enum RpsPick
@@ -67,10 +69,11 @@ public partial class Gambling : GamblingModule<GamblingService>
return cur.ToString("C0", flowersCi); return cur.ToString("C0", flowersCi);
} }
public string GetCurrency(ulong id) public async Task<string> GetBalanceStringAsync(ulong userId)
{ {
using var uow = _db.GetDbContext(); var wallet = await _cs.GetWalletAsync(userId);
return n(uow.DiscordUser.GetUserCurrency(id)); var bal = await wallet.GetBalance();
return n(bal);
} }
[Cmd] [Cmd]
@@ -78,9 +81,11 @@ public partial class Gambling : GamblingModule<GamblingService>
{ {
var ec = _service.GetEconomy(); var ec = _service.GetEconomy();
decimal onePercent = 0; decimal onePercent = 0;
// This stops the top 1% from owning more than 100% of the money
if (ec.Cash > 0) if (ec.Cash > 0)
onePercent = onePercent = ec.OnePercent / (ec.Cash - ec.Bot);
ec.OnePercent / (ec.Cash - ec.Bot); // This stops the top 1% from owning more than 100% of the money
// [21:03] Bob Page: Kinda remids me of US economy // [21:03] Bob Page: Kinda remids me of US economy
var embed = _eb.Create() var embed = _eb.Create()
.WithTitle(GetText(strs.economy_state)) .WithTitle(GetText(strs.economy_state))
@@ -93,6 +98,7 @@ public partial class Gambling : GamblingModule<GamblingService>
.AddField(GetText(strs.total), .AddField(GetText(strs.total),
((BigInteger)(ec.Cash + ec.Planted + ec.Waifus)).ToString("N", Culture) + CurrencySign) ((BigInteger)(ec.Cash + ec.Planted + ec.Waifus)).ToString("N", Culture) + CurrencySign)
.WithOkColor(); .WithOkColor();
// ec.Cash already contains ec.Bot as it's the total of all values in the CurrencyAmount column of the DiscordUser table // ec.Cash already contains ec.Bot as it's the total of all values in the CurrencyAmount column of the DiscordUser table
await ctx.Channel.EmbedAsync(embed); await ctx.Channel.EmbedAsync(embed);
} }
@@ -114,7 +120,7 @@ public partial class Gambling : GamblingModule<GamblingService>
return; return;
} }
await _cs.AddAsync(ctx.User.Id, "Timely claim", val); await _cs.AddAsync(ctx.User.Id, val, new("timely", "claim"));
await ReplyConfirmLocalizedAsync(strs.timely(n(val), period)); await ReplyConfirmLocalizedAsync(strs.timely(n(val), period));
} }
@@ -225,14 +231,18 @@ public partial class Gambling : GamblingModule<GamblingService>
[Cmd] [Cmd]
[Priority(0)] [Priority(0)]
public async partial Task Cash(ulong userId) public async partial Task Cash(ulong userId)
=> await ReplyConfirmLocalizedAsync(strs.has(Format.Code(userId.ToString()), $"{GetCurrency(userId)}")); {
var cur = await GetBalanceStringAsync(userId);
await ReplyConfirmLocalizedAsync(strs.has(Format.Code(userId.ToString()), cur));
}
[Cmd] [Cmd]
[Priority(1)] [Priority(1)]
public async partial Task Cash([Leftover] IUser user = null) public async partial Task Cash([Leftover] IUser user = null)
{ {
user ??= ctx.User; user ??= ctx.User;
await ConfirmLocalizedAsync(strs.has(Format.Bold(user.ToString()), $"{GetCurrency(user.Id)}")); var cur = await GetBalanceStringAsync(user.Id);
await ConfirmLocalizedAsync(strs.has(Format.Bold(user.ToString()), cur));
} }
[Cmd] [Cmd]
@@ -242,15 +252,13 @@ public partial class Gambling : GamblingModule<GamblingService>
{ {
if (amount <= 0 || ctx.User.Id == receiver.Id || receiver.IsBot) if (amount <= 0 || ctx.User.Id == receiver.Id || receiver.IsBot)
return; return;
var success =
await _cs.RemoveAsync((IGuildUser)ctx.User, $"Gift to {receiver.Username} ({receiver.Id}).", amount); if (!await _cs.TransferAsync(ctx.User.Id, receiver.Id, amount, msg))
if (!success)
{ {
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return; return;
} }
await _cs.AddAsync(receiver, $"Gift from {ctx.User.Username} ({ctx.User.Id}) - {msg}.", amount, true);
await ReplyConfirmLocalizedAsync(strs.gifted(n(amount), Format.Bold(receiver.ToString()))); await ReplyConfirmLocalizedAsync(strs.gifted(n(amount), Format.Bold(receiver.ToString())));
} }
@@ -290,10 +298,10 @@ public partial class Gambling : GamblingModule<GamblingService>
return; return;
} }
await _cs.AddAsync(usr, await _cs.AddAsync(usr.Id,
$"Awarded by bot owner. ({ctx.User.Username}/{ctx.User.Id}) {msg ?? ""}",
amount, amount,
gamble: ctx.Client.CurrentUser.Id != usrId); new Extra("owner", "award", $"Awarded by bot owner. ({ctx.User.Username}/{ctx.User.Id}) {msg ?? ""}")
);
await ReplyConfirmLocalizedAsync(strs.awarded(n(amount), $"<@{usrId}>")); await ReplyConfirmLocalizedAsync(strs.awarded(n(amount), $"<@{usrId}>"));
} }
@@ -305,10 +313,11 @@ public partial class Gambling : GamblingModule<GamblingService>
{ {
var users = (await ctx.Guild.GetUsersAsync()).Where(u => u.GetRoles().Contains(role)).ToList(); var users = (await ctx.Guild.GetUsersAsync()).Where(u => u.GetRoles().Contains(role)).ToList();
await _cs.AddBulkAsync(users.Select(x => x.Id), await _cs.AddBulkAsync(users.Select(x => x.Id).ToList(),
users.Select(_ => $"Awarded by bot owner to **{role.Name}** role. ({ctx.User.Username}/{ctx.User.Id})"), amount,
users.Select(_ => amount), new("owner",
true); "award",
$"Awarded by bot owner to **{role.Name}** role. ({ctx.User.Username}/{ctx.User.Id})"));
await ReplyConfirmLocalizedAsync(strs.mass_award(n(amount), await ReplyConfirmLocalizedAsync(strs.mass_award(n(amount),
Format.Bold(users.Count.ToString()), Format.Bold(users.Count.ToString()),
@@ -323,10 +332,9 @@ public partial class Gambling : GamblingModule<GamblingService>
{ {
var users = (await role.GetMembersAsync()).ToList(); var users = (await role.GetMembersAsync()).ToList();
await _cs.RemoveBulkAsync(users.Select(x => x.Id), await _cs.RemoveBulkAsync(users.Select(x => x.Id).ToList(),
users.Select(_ => $"Taken by bot owner from **{role.Name}** role. ({ctx.User.Username}/{ctx.User.Id})"), amount,
users.Select(_ => amount), new("owner", "take", $"Taken by bot owner from **{role.Name}** role. ({ctx.User.Username}/{ctx.User.Id})"));
true);
await ReplyConfirmLocalizedAsync(strs.mass_take(n(amount), await ReplyConfirmLocalizedAsync(strs.mass_take(n(amount),
Format.Bold(users.Count.ToString()), Format.Bold(users.Count.ToString()),
@@ -342,10 +350,9 @@ public partial class Gambling : GamblingModule<GamblingService>
if (amount <= 0) if (amount <= 0)
return; return;
if (await _cs.RemoveAsync(user, if (await _cs.RemoveAsync(user.Id,
$"Taken by bot owner.({ctx.User.Username}/{ctx.User.Id})",
amount, amount,
gamble: ctx.Client.CurrentUser.Id != user.Id)) new("owner", "take", $"Taken by bot owner. ({ctx.User.Username}/{ctx.User.Id})")))
await ReplyConfirmLocalizedAsync(strs.take(n(amount), Format.Bold(user.ToString()))); await ReplyConfirmLocalizedAsync(strs.take(n(amount), Format.Bold(user.ToString())));
else else
await ReplyErrorLocalizedAsync(strs.take_fail(n(amount), Format.Bold(user.ToString()), CurrencySign)); await ReplyErrorLocalizedAsync(strs.take_fail(n(amount), Format.Bold(user.ToString()), CurrencySign));
@@ -360,9 +367,8 @@ public partial class Gambling : GamblingModule<GamblingService>
return; return;
if (await _cs.RemoveAsync(usrId, if (await _cs.RemoveAsync(usrId,
$"Taken by bot owner.({ctx.User.Username}/{ctx.User.Id})",
amount, amount,
ctx.Client.CurrentUser.Id != usrId)) new("owner", "take", $"Taken by bot owner. ({ctx.User.Username}/{ctx.User.Id})")))
await ReplyConfirmLocalizedAsync(strs.take(n(amount), $"<@{usrId}>")); await ReplyConfirmLocalizedAsync(strs.take(n(amount), $"<@{usrId}>"));
else else
await ReplyErrorLocalizedAsync(strs.take_fail(n(amount), Format.Code(usrId.ToString()), CurrencySign)); await ReplyErrorLocalizedAsync(strs.take_fail(n(amount), Format.Code(usrId.ToString()), CurrencySign));
@@ -467,7 +473,7 @@ public partial class Gambling : GamblingModule<GamblingService>
if (!await CheckBetMandatory(amount)) if (!await CheckBetMandatory(amount))
return; return;
if (!await _cs.RemoveAsync(ctx.User, "Betroll Gamble", amount, false, true)) if (!await _cs.RemoveAsync(ctx.User, amount, new("betroll", "bet")))
{ {
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return; return;
@@ -483,7 +489,7 @@ public partial class Gambling : GamblingModule<GamblingService>
{ {
var win = (long)(amount * result.Multiplier); var win = (long)(amount * result.Multiplier);
str += GetText(strs.br_win(n(win), result.Threshold + (result.Roll == 100 ? " 👑" : ""))); str += GetText(strs.br_win(n(win), result.Threshold + (result.Roll == 100 ? " 👑" : "")));
await _cs.AddAsync(ctx.User, "Betroll Gamble", win, false, true); await _cs.AddAsync(ctx.User, win, new("betroll", "win"));
} }
else else
{ {
@@ -600,16 +606,20 @@ public partial class Gambling : GamblingModule<GamblingService>
var nadekoPick = (RpsPick)new NadekoRandom().Next(0, 3); var nadekoPick = (RpsPick)new NadekoRandom().Next(0, 3);
if (amount > 0) if (amount > 0)
if (!await _cs.RemoveAsync(ctx.User.Id, "Rps-bet", amount, true)) {
if (!await _cs.RemoveAsync(ctx.User.Id,
amount,
new("rps", "bet", "")))
{ {
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return; return;
} }
}
string msg; string msg;
if (pick == nadekoPick) if (pick == nadekoPick)
{ {
await _cs.AddAsync(ctx.User.Id, "Rps-draw", amount, true); await _cs.AddAsync(ctx.User.Id, amount, new("rps", "draw"));
embed.WithOkColor(); embed.WithOkColor();
msg = GetText(strs.rps_draw(GetRpsPick(pick))); msg = GetText(strs.rps_draw(GetRpsPick(pick)));
} }
@@ -618,7 +628,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|| (pick == RpsPick.Scissors && nadekoPick == RpsPick.Paper)) || (pick == RpsPick.Scissors && nadekoPick == RpsPick.Paper))
{ {
amount = (long)(amount * Config.BetFlip.Multiplier); amount = (long)(amount * Config.BetFlip.Multiplier);
await _cs.AddAsync(ctx.User.Id, "Rps-win", amount, true); await _cs.AddAsync(ctx.User.Id, amount, new("rps", "win"));
embed.WithOkColor(); embed.WithOkColor();
embed.AddField(GetText(strs.won), n(amount)); embed.AddField(GetText(strs.won), n(amount));
msg = GetText(strs.rps_win(ctx.User.Mention, GetRpsPick(pick), GetRpsPick(nadekoPick))); msg = GetText(strs.rps_win(ctx.User.Mention, GetRpsPick(pick), GetRpsPick(nadekoPick)));

View File

@@ -83,7 +83,7 @@ WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentU
public async Task<SlotResponse> SlotAsync(ulong userId, long amount) public async Task<SlotResponse> SlotAsync(ulong userId, long amount)
{ {
var takeRes = await _cs.RemoveAsync(userId, "Slot Machine", amount, true); var takeRes = await _cs.RemoveAsync(userId, amount, new("slot", "bet"));
if (!takeRes) if (!takeRes)
return new() { Error = GamblingError.NotEnough }; return new() { Error = GamblingError.NotEnough };
@@ -96,7 +96,7 @@ WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentU
{ {
won = (long)(result.Multiplier * amount); won = (long)(result.Multiplier * amount);
await _cs.AddAsync(userId, $"Slot Machine x{result.Multiplier}", won, true); await _cs.AddAsync(userId, won, new("slot", "win", $"Slot Machine x{result.Multiplier}"));
} }
var toReturn = new SlotResponse { Multiplier = result.Multiplier, Won = won }; var toReturn = new SlotResponse { Multiplier = result.Multiplier, Won = won };

View File

@@ -270,7 +270,7 @@ public class PlantPickService : INService
if (amount > 0) if (amount > 0)
// give the picked currency to the user // give the picked currency to the user
await _cs.AddAsync(uid, "Picked currency", amount); await _cs.AddAsync(uid, amount, new("currency", "collect"));
uow.SaveChanges(); uow.SaveChanges();
} }
@@ -337,14 +337,14 @@ public class PlantPickService : INService
return false; return false;
// remove currency from the user who's planting // remove currency from the user who's planting
if (await _cs.RemoveAsync(uid, "Planted currency", amount)) if (await _cs.RemoveAsync(uid, amount, new("put/collect", "put")))
{ {
// try to send the message with the currency image // try to send the message with the currency image
var msgId = await SendPlantMessageAsync(gid, ch, user, amount, pass); var msgId = await SendPlantMessageAsync(gid, ch, user, amount, pass);
if (msgId is null) if (msgId is null)
{ {
// if it fails it will return null, if it returns null, refund // if it fails it will return null, if it returns null, refund
await _cs.AddAsync(uid, "Planted currency refund", amount); await _cs.AddAsync(uid, amount, new("put/collect", "refund"));
return false; return false;
} }

View File

@@ -38,7 +38,7 @@ public class CurrencyRaffleService : INService
//remove money, and stop the game if this //remove money, and stop the game if this
// user created it and doesn't have the money // user created it and doesn't have the money
if (!await _cs.RemoveAsync(user.Id, "Currency Raffle Join", amount)) if (!await _cs.RemoveAsync(user.Id, amount, new("raffle", "join")))
{ {
if (newGame) if (newGame)
Games.Remove(channelId); Games.Remove(channelId);
@@ -47,7 +47,7 @@ public class CurrencyRaffleService : INService
if (!crg.AddUser(user, amount)) if (!crg.AddUser(user, amount))
{ {
await _cs.AddAsync(user.Id, "Curency Raffle Refund", amount); await _cs.AddAsync(user.Id, amount, new("raffle", "refund"));
return (null, JoinErrorType.AlreadyJoinedOrInvalidAmount); return (null, JoinErrorType.AlreadyJoinedOrInvalidAmount);
} }
@@ -62,7 +62,7 @@ public class CurrencyRaffleService : INService
var winner = crg.GetWinner(); var winner = crg.GetWinner();
var won = crg.Users.Sum(x => x.Amount); var won = crg.Users.Sum(x => x.Amount);
await _cs.AddAsync(winner.DiscordUser.Id, "Currency Raffle Win", won); await _cs.AddAsync(winner.DiscordUser.Id, won, new("raffle", "win"));
Games.Remove(channelId, out _); Games.Remove(channelId, out _);
_ = onEnded(winner.DiscordUser, won); _ = onEnded(winner.DiscordUser, won);
} }

View File

@@ -115,7 +115,7 @@ public partial class Gambling
return; return;
} }
if (await _cs.RemoveAsync(ctx.User.Id, $"Shop purchase - {entry.Type}", entry.Price)) if (await _cs.RemoveAsync(ctx.User.Id, entry.Price, new("shop", "buy", entry.Type.ToString())))
{ {
try try
{ {
@@ -124,14 +124,14 @@ public partial class Gambling
catch (Exception ex) catch (Exception ex)
{ {
Log.Warning(ex, "Error adding shop role"); Log.Warning(ex, "Error adding shop role");
await _cs.AddAsync(ctx.User.Id, "Shop error refund", entry.Price); await _cs.AddAsync(ctx.User.Id, entry.Price, new("shop", "error-refund"));
await ReplyErrorLocalizedAsync(strs.shop_role_purchase_error); await ReplyErrorLocalizedAsync(strs.shop_role_purchase_error);
return; return;
} }
var profit = GetProfitAmount(entry.Price); var profit = GetProfitAmount(entry.Price);
await _cs.AddAsync(entry.AuthorId, $"Shop sell item - {entry.Type}", profit); await _cs.AddAsync(entry.AuthorId, profit, new("shop", "sell", $"Shop sell item - {entry.Type}"));
await _cs.AddAsync(ctx.Client.CurrentUser.Id, "Shop sell item - cut", entry.Price - profit); await _cs.AddAsync(ctx.Client.CurrentUser.Id, entry.Price - profit, new("shop", "cut"));
await ReplyConfirmLocalizedAsync(strs.shop_role_purchase(Format.Bold(role.Name))); await ReplyConfirmLocalizedAsync(strs.shop_role_purchase(Format.Bold(role.Name)));
return; return;
} }
@@ -150,7 +150,7 @@ public partial class Gambling
var item = entry.Items.ToArray()[new NadekoRandom().Next(0, entry.Items.Count)]; var item = entry.Items.ToArray()[new NadekoRandom().Next(0, entry.Items.Count)];
if (await _cs.RemoveAsync(ctx.User.Id, $"Shop purchase - {entry.Type}", entry.Price)) if (await _cs.RemoveAsync(ctx.User.Id, entry.Price, new("shop", "buy", entry.Type.ToString())))
{ {
await using (var uow = _db.GetDbContext()) await using (var uow = _db.GetDbContext())
{ {
@@ -168,12 +168,12 @@ public partial class Gambling
.AddField(GetText(strs.name), entry.Name, true)); .AddField(GetText(strs.name), entry.Name, true));
await _cs.AddAsync(entry.AuthorId, await _cs.AddAsync(entry.AuthorId,
$"Shop sell item - {entry.Name}", GetProfitAmount(entry.Price),
GetProfitAmount(entry.Price)); new("shop", "sell", entry.Name));
} }
catch catch
{ {
await _cs.AddAsync(ctx.User.Id, $"Shop error refund - {entry.Name}", entry.Price); await _cs.AddAsync(ctx.User.Id, entry.Price, new("shop", "error-refund", entry.Name));
await using (var uow = _db.GetDbContext()) await using (var uow = _db.GetDbContext())
{ {
var entries = new IndexedCollection<ShopEntry>(uow.GuildConfigsForId(ctx.Guild.Id, var entries = new IndexedCollection<ShopEntry>(uow.GuildConfigsForId(ctx.Guild.Id,
@@ -411,6 +411,7 @@ public partial class Gambling
var embed = _eb.Create().WithOkColor(); var embed = _eb.Create().WithOkColor();
if (entry.Type == ShopEntryType.Role) if (entry.Type == ShopEntryType.Role)
{
return embed return embed
.AddField(GetText(strs.name), .AddField(GetText(strs.name),
GetText(strs.shop_role(Format.Bold(ctx.Guild.GetRole(entry.RoleId)?.Name GetText(strs.shop_role(Format.Bold(ctx.Guild.GetRole(entry.RoleId)?.Name
@@ -418,10 +419,15 @@ public partial class Gambling
true) true)
.AddField(GetText(strs.price), entry.Price.ToString(), true) .AddField(GetText(strs.price), entry.Price.ToString(), true)
.AddField(GetText(strs.type), entry.Type.ToString(), true); .AddField(GetText(strs.type), entry.Type.ToString(), true);
}
if (entry.Type == ShopEntryType.List) if (entry.Type == ShopEntryType.List)
{
return embed.AddField(GetText(strs.name), entry.Name, true) return embed.AddField(GetText(strs.name), entry.Name, true)
.AddField(GetText(strs.price), entry.Price.ToString(), true) .AddField(GetText(strs.price), entry.Price.ToString(), true)
.AddField(GetText(strs.type), GetText(strs.random_unique_item), true); .AddField(GetText(strs.type), GetText(strs.random_unique_item), true);
}
//else if (entry.Type == ShopEntryType.Infinite_List) //else if (entry.Type == ShopEntryType.Infinite_List)
// return embed.AddField(GetText(strs.name), GetText(strs.shop_role(Format.Bold(entry.RoleName)), true)) // return embed.AddField(GetText(strs.name), GetText(strs.shop_role(Format.Bold(entry.RoleName)), true))
// .AddField(GetText(strs.price), entry.Price.ToString(), true) // .AddField(GetText(strs.price), entry.Price.ToString(), true)

View File

@@ -61,9 +61,8 @@ public class VoteRewardService : INService, IReadyExecutor
var ids = data.Select(x => x.UserId).ToList(); var ids = data.Select(x => x.UserId).ToList();
await _currencyService.AddBulkAsync(ids, await _currencyService.AddBulkAsync(ids,
data.Select(_ => "top.gg vote reward"), _gamb.Data.VoteReward,
data.Select(_ => _gamb.Data.VoteReward), new("vote", "top.gg", "top.gg vote reward"));
true);
Log.Information("Rewarding {Count} top.gg voters", ids.Count()); Log.Information("Rewarding {Count} top.gg voters", ids.Count());
} }
@@ -90,9 +89,8 @@ public class VoteRewardService : INService, IReadyExecutor
var ids = data.Select(x => x.UserId).ToList(); var ids = data.Select(x => x.UserId).ToList();
await _currencyService.AddBulkAsync(ids, await _currencyService.AddBulkAsync(ids,
data.Select(_ => "discords.com vote reward"), _gamb.Data.VoteReward,
data.Select(_ => _gamb.Data.VoteReward), new("vote", "discords", "discords.com vote reward"));
true);
Log.Information("Rewarding {Count} discords.com voters", ids.Count()); Log.Information("Rewarding {Count} discords.com voters", ids.Count());
} }

View File

@@ -45,7 +45,7 @@ public class WaifuService : INService
// if waifu likes the person, gotta pay the penalty // if waifu likes the person, gotta pay the penalty
if (waifu.AffinityId == ownerUser.Id) if (waifu.AffinityId == ownerUser.Id)
{ {
if (!await _cs.RemoveAsync(owner.Id, "Waifu Transfer - affinity penalty", (int)(waifu.Price * 0.6), true)) if (!await _cs.RemoveAsync(owner.Id, (int)(waifu.Price * 0.6), new("waifu", "affinity-penalty")))
// unable to pay 60% penalty // unable to pay 60% penalty
return false; return false;
@@ -55,7 +55,7 @@ public class WaifuService : INService
} }
else // if not, pay 10% fee else // if not, pay 10% fee
{ {
if (!await _cs.RemoveAsync(owner.Id, "Waifu Transfer", waifu.Price / 10, true)) return false; if (!await _cs.RemoveAsync(owner.Id, waifu.Price / 10, new("waifu", "transfer"))) return false;
waifu.Price = (int)(waifu.Price * 0.95); // half of 10% = 5% price reduction waifu.Price = (int)(waifu.Price * 0.95); // half of 10% = 5% price reduction
if (waifu.Price < settings.Waifu.MinPrice) if (waifu.Price < settings.Waifu.MinPrice)
@@ -97,7 +97,7 @@ public class WaifuService : INService
{ {
await using var uow = _db.GetDbContext(); await using var uow = _db.GetDbContext();
var price = GetResetPrice(user); var price = GetResetPrice(user);
if (!await _cs.RemoveAsync(user.Id, "Waifu Reset", price, true)) if (!await _cs.RemoveAsync(user.Id, price, new("waifu", "reset")))
return false; return false;
var affs = uow.WaifuUpdates.AsQueryable() var affs = uow.WaifuUpdates.AsQueryable()
@@ -144,7 +144,7 @@ public class WaifuService : INService
{ {
var claimer = uow.GetOrCreateUser(user); var claimer = uow.GetOrCreateUser(user);
var waifu = uow.GetOrCreateUser(target); var waifu = uow.GetOrCreateUser(target);
if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, true)) if (!await _cs.RemoveAsync(user.Id, amount, new("waifu", "claim")))
{ {
result = WaifuClaimResult.NotEnoughFunds; result = WaifuClaimResult.NotEnoughFunds;
} }
@@ -160,7 +160,7 @@ public class WaifuService : INService
} }
else if (isAffinity && amount > w.Price * settings.Waifu.Multipliers.CrushClaim) else if (isAffinity && amount > w.Price * settings.Waifu.Multipliers.CrushClaim)
{ {
if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, true)) if (!await _cs.RemoveAsync(user.Id, amount, new("waifu", "claim")))
{ {
result = WaifuClaimResult.NotEnoughFunds; result = WaifuClaimResult.NotEnoughFunds;
} }
@@ -179,7 +179,7 @@ public class WaifuService : INService
} }
else if (amount >= w.Price * settings.Waifu.Multipliers.NormalClaim) // if no affinity else if (amount >= w.Price * settings.Waifu.Multipliers.NormalClaim) // if no affinity
{ {
if (!await _cs.RemoveAsync(user.Id, "Claimed Waifu", amount, true)) if (!await _cs.RemoveAsync(user.Id, amount, new("waifu", "claim")))
{ {
result = WaifuClaimResult.NotEnoughFunds; result = WaifuClaimResult.NotEnoughFunds;
} }
@@ -288,13 +288,13 @@ public class WaifuService : INService
if (w.Affinity?.UserId == user.Id) if (w.Affinity?.UserId == user.Id)
{ {
await _cs.AddAsync(w.Waifu.UserId, "Waifu Compensation", amount, true); await _cs.AddAsync(w.Waifu.UserId, amount, new("waifu", "compensation"));
w.Price = (int)Math.Floor(w.Price * _gss.Data.Waifu.Multipliers.DivorceNewValue); w.Price = (int)Math.Floor(w.Price * _gss.Data.Waifu.Multipliers.DivorceNewValue);
result = DivorceResult.SucessWithPenalty; result = DivorceResult.SucessWithPenalty;
} }
else else
{ {
await _cs.AddAsync(user.Id, "Waifu Refund", amount, true); await _cs.AddAsync(user.Id, amount, new("waifu", "refund"));
result = DivorceResult.Success; result = DivorceResult.Success;
} }
@@ -316,7 +316,7 @@ public class WaifuService : INService
public async Task<bool> GiftWaifuAsync(IUser from, IUser giftedWaifu, WaifuItemModel itemObj) public async Task<bool> GiftWaifuAsync(IUser from, IUser giftedWaifu, WaifuItemModel itemObj)
{ {
if (!await _cs.RemoveAsync(from, "Bought waifu item", itemObj.Price, gamble: true)) return false; if (!await _cs.RemoveAsync(from, itemObj.Price, new("waifu", "item"))) return false;
await using var uow = _db.GetDbContext(); await using var uow = _db.GetDbContext();
var w = uow.WaifuInfo.ByWaifuUserId(giftedWaifu.Id, set => set.Include(x => x.Items).Include(x => x.Claimer)); var w = uow.WaifuInfo.ByWaifuUserId(giftedWaifu.Id, set => set.Include(x => x.Items).Include(x => x.Claimer));

View File

@@ -29,7 +29,7 @@ public class WheelOfFortuneGame
var amount = (long)(_bet * _config.WheelOfFortune.Multipliers[result]); var amount = (long)(_bet * _config.WheelOfFortune.Multipliers[result]);
if (amount > 0) if (amount > 0)
await _cs.AddAsync(_userId, "Wheel Of Fortune - won", amount, true); await _cs.AddAsync(_userId, amount, new("wheel", "win"));
return new() { Index = result, Amount = amount }; return new() { Index = result, Amount = amount };
} }

View File

@@ -29,7 +29,7 @@ public partial class Gambling
if (!await CheckBetMandatory(amount)) if (!await CheckBetMandatory(amount))
return; return;
if (!await _cs.RemoveAsync(ctx.User.Id, "Wheel Of Fortune - bet", amount, true)) if (!await _cs.RemoveAsync(ctx.User.Id, amount, new("wheel", "bet")))
{ {
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return; return;

View File

@@ -86,16 +86,16 @@ public class RollDuelGame
_locker.Release(); _locker.Release();
} }
if (!await _cs.RemoveAsync(P1, "Roll Duel", Amount)) if (!await _cs.RemoveAsync(P1, Amount, new("rollduel", "bet")))
{ {
await OnEnded?.Invoke(this, Reason.NoFunds); await OnEnded?.Invoke(this, Reason.NoFunds);
CurrentState = State.Ended; CurrentState = State.Ended;
return; return;
} }
if (!await _cs.RemoveAsync(P2, "Roll Duel", Amount)) if (!await _cs.RemoveAsync(P2, Amount, new("rollduel", "bet")))
{ {
await _cs.AddAsync(P1, "Roll Duel - refund", Amount); await _cs.AddAsync(P1, Amount, new("rollduel", "refund"));
await OnEnded?.Invoke(this, Reason.NoFunds); await OnEnded?.Invoke(this, Reason.NoFunds);
CurrentState = State.Ended; CurrentState = State.Ended;
return; return;
@@ -114,9 +114,9 @@ public class RollDuelGame
else else
Winner = P2; Winner = P2;
var won = (long)(Amount * 2 * 0.98f); var won = (long)(Amount * 2 * 0.98f);
await _cs.AddAsync(Winner, "Roll Duel win", won); await _cs.AddAsync(Winner, won, new("rollduel", "win"));
await _cs.AddAsync(_botId, "Roll Duel fee", (Amount * 2) - won); await _cs.AddAsync(_botId, (Amount * 2) - won, new("rollduel", "fee"));
} }
try { await OnGameTick?.Invoke(this); } try { await OnGameTick?.Invoke(this); }

View File

@@ -96,7 +96,7 @@ public sealed class HangmanService : IHangmanService, ILateExecutor
} }
if (rew > 0) if (rew > 0)
await _cs.AddAsync(msg.Author, "hangman win", rew, gamble: true); await _cs.AddAsync(msg.Author, rew, new("hangman", "win"));
await SendState((ITextChannel)msg.Channel, msg.Author, msg.Content, state); await SendState((ITextChannel)msg.Channel, msg.Author, msg.Content, state);
} }

View File

@@ -251,7 +251,7 @@ public class TriviaGame
var reward = _config.Trivia.CurrencyReward; var reward = _config.Trivia.CurrencyReward;
if (reward > 0) if (reward > 0)
await _cs.AddAsync(guildUser, "Won trivia", reward, true); await _cs.AddAsync(guildUser, reward, new("trivia", "win"));
return; return;
} }

View File

@@ -234,7 +234,7 @@ public class PatreonRewardsService : INService, IReadyExecutor
await uow.SaveChangesAsync(); await uow.SaveChangesAsync();
await _currency.AddAsync(userId, "Patreon reward - new", eligibleFor, true); await _currency.AddAsync(userId, eligibleFor, new("patreon", "new"));
Log.Information("Sending new currency reward to {UserId}", userId); Log.Information("Sending new currency reward to {UserId}", userId);
await SendMessageToUser(userId, await SendMessageToUser(userId,
@@ -249,7 +249,7 @@ public class PatreonRewardsService : INService, IReadyExecutor
await uow.SaveChangesAsync(); await uow.SaveChangesAsync();
await _currency.AddAsync(userId, "Patreon reward - recurring", eligibleFor, true); await _currency.AddAsync(userId, eligibleFor, new("patreon", "recurring"));
Log.Information("Sending recurring currency reward to {UserId}", userId); Log.Information("Sending recurring currency reward to {UserId}", userId);
await SendMessageToUser(userId, await SendMessageToUser(userId,
@@ -267,7 +267,7 @@ public class PatreonRewardsService : INService, IReadyExecutor
usr.AmountRewardedThisMonth = toAward; usr.AmountRewardedThisMonth = toAward;
await uow.SaveChangesAsync(); await uow.SaveChangesAsync();
await _currency.AddAsync(userId, "Patreon reward - update", toAward, true); await _currency.AddAsync(userId, toAward, new("patreon", "update"));
Log.Information("Sending updated currency reward to {UserId}", userId); Log.Information("Sending updated currency reward to {UserId}", userId);
await SendMessageToUser(userId, await SendMessageToUser(userId,

View File

@@ -210,7 +210,7 @@ public class XpService : INService
var crew = crews.FirstOrDefault(x => x.Level == i); var crew = crews.FirstOrDefault(x => x.Level == i);
if (crew is not null) if (crew is not null)
//give the user the reward if it exists //give the user the reward if it exists
await _cs.AddAsync(item.Key.User.Id, "Level-up Reward", crew.Amount); await _cs.AddAsync(item.Key.User.Id, crew.Amount, new("xp", "level-up"));
} }
} }
} }

View File

@@ -0,0 +1,123 @@
#nullable disable
using LinqToDB;
using NadekoBot.Services.Currency;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Services;
public class CurrencyService : ICurrencyService, INService
{
private readonly DbService _db;
public CurrencyService(DbService db)
=> _db = db;
public Task<IWallet> GetWalletAsync(ulong userId, CurrencyType type = CurrencyType.Default)
{
if (type == CurrencyType.Default)
{
return Task.FromResult<IWallet>(new DefaultWallet(userId, _db.GetDbContext()));
}
throw new ArgumentOutOfRangeException(nameof(type));
}
public async Task AddBulkAsync(
IReadOnlyCollection<ulong> userIds,
long amount,
Extra extra,
CurrencyType type = CurrencyType.Default)
{
if (type == CurrencyType.Default)
{
await using var ctx = _db.GetDbContext();
foreach (var userId in userIds)
{
var wallet = new DefaultWallet(userId, ctx);
await wallet.Add(amount, extra);
}
await ctx.SaveChangesAsync();
return;
}
throw new ArgumentOutOfRangeException(nameof(type));
}
public async Task RemoveBulkAsync(
IReadOnlyCollection<ulong> userIds,
long amount,
Extra extra,
CurrencyType type = CurrencyType.Default)
{
if (type == CurrencyType.Default)
{
await using var ctx = _db.GetDbContext();
await ctx.DiscordUser
.Where(x => userIds.Contains(x.UserId))
.UpdateAsync(du => new()
{
CurrencyAmount = du.CurrencyAmount >= amount
? du.CurrencyAmount - amount
: 0
});
return;
}
throw new ArgumentOutOfRangeException(nameof(type));
}
private CurrencyTransaction GetCurrencyTransaction(ulong userId, string reason, long amount)
=> new() { Amount = amount, UserId = userId, Reason = reason ?? "-" };
public async Task AddAsync(
ulong userId,
long amount,
Extra extra)
{
await using var wallet = await GetWalletAsync(userId);
await wallet.Add(amount, extra);
}
public async Task AddAsync(
IUser user,
long amount,
Extra extra)
{
await using var wallet = await GetWalletAsync(user.Id);
await wallet.Add(amount, extra);
}
public async Task<bool> RemoveAsync(
ulong userId,
long amount,
Extra extra)
{
await using var wallet = await GetWalletAsync(userId);
return await wallet.Take(amount, extra);
}
public async Task<bool> RemoveAsync(
IUser user,
long amount,
Extra extra)
{
await using var wallet = await GetWalletAsync(user.Id);
return await wallet.Take(amount, extra);
}
public async Task<bool> TransferAsync(
ulong from,
ulong to,
long amount,
string note)
{
await using var fromWallet = await GetWalletAsync(@from);
await using var toWallet = await GetWalletAsync(to);
var extra = new Extra("transfer", "gift", note);
return await fromWallet.Transfer(amount, toWallet, extra);
}
}

View File

@@ -0,0 +1,6 @@
namespace NadekoBot.Services.Currency;
public enum CurrencyType
{
Default,
}

View File

@@ -0,0 +1,104 @@
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Services.Currency;
public class DefaultWallet : IWallet
{
public ulong UserId { get; }
private readonly NadekoContext _ctx;
public DefaultWallet(ulong userId, NadekoContext ctx)
{
UserId = userId;
_ctx = ctx;
}
public Task<long> GetBalance()
=> _ctx.DiscordUser
.ToLinqToDBTable()
.Where(x => x.UserId == UserId)
.Select(x => x.CurrencyAmount)
.FirstOrDefaultAsync();
public async Task<bool> Take(long amount, Extra extra)
{
if (amount < 0)
throw new ArgumentOutOfRangeException(nameof(amount), "Amount to take must be non negative.");
var changed = await _ctx.DiscordUser
.Where(x => x.UserId == UserId && x.CurrencyAmount >= amount)
.UpdateAsync(x => new()
{
CurrencyAmount = x.CurrencyAmount - amount
});
if (changed == 0)
return false;
// todo type, subtype
// todo from? by?
await _ctx.CreateLinqToDbContext()
.InsertAsync(new CurrencyTransaction()
{
Amount = -amount,
Reason = extra.Note,
UserId = UserId,
});
return true;
}
public async Task Add(long amount, Extra extra)
{
if (amount <= 0)
throw new ArgumentOutOfRangeException(nameof(amount), "Amount must be greater than 0.");
await using (var tran = await _ctx.Database.BeginTransactionAsync())
{
var changed = await _ctx.DiscordUser
.UpdateAsync(x => new()
{
CurrencyAmount = x.CurrencyAmount + amount
});
if (changed == 0)
{
await _ctx.DiscordUser
.ToLinqToDBTable()
.Value(x => x.UserId, UserId)
.Value(x => x.Username, "Unknown")
.Value(x => x.Discriminator, "????")
.Value(x => x.CurrencyAmount, amount)
.InsertAsync();
}
await tran.CommitAsync();
}
var ct = new CurrencyTransaction()
{
Amount = amount,
Reason = extra.Note,
UserId = UserId,
};
await _ctx.CreateLinqToDbContext()
.InsertAsync(ct);
}
public void Dispose()
{
_ctx.SaveChanges();
_ctx.Dispose();
}
public async ValueTask DisposeAsync()
{
await _ctx.SaveChangesAsync();
await _ctx.DisposeAsync();
}
}

View File

@@ -0,0 +1,3 @@
namespace NadekoBot.Services.Currency;
public record class Extra(string Type, string Subtype, string Note = "", ulong OtherId = 0);

View File

@@ -0,0 +1,47 @@
using NadekoBot.Services.Currency;
#nullable disable
namespace NadekoBot.Services;
public interface ICurrencyService
{
Task<IWallet> GetWalletAsync(ulong userId, CurrencyType type = CurrencyType.Default);
Task AddBulkAsync(
IReadOnlyCollection<ulong> userIds,
long amount,
Extra extra,
CurrencyType type = CurrencyType.Default);
Task RemoveBulkAsync(
IReadOnlyCollection<ulong> userIds,
long amount,
Extra extra,
CurrencyType type = CurrencyType.Default);
Task AddAsync(
ulong userId,
long amount,
Extra extra);
Task AddAsync(
IUser user,
long amount,
Extra extra);
Task<bool> RemoveAsync(
ulong userId,
long amount,
Extra extra);
Task<bool> RemoveAsync(
IUser user,
long amount,
Extra extra);
Task<bool> TransferAsync(
ulong from,
ulong to,
long amount,
string note);
}

View File

@@ -0,0 +1,36 @@
namespace NadekoBot.Services.Currency;
public interface IWallet : IDisposable, IAsyncDisposable
{
public ulong UserId { get; }
public Task<long> GetBalance();
public Task<bool> Take(long amount, Extra extra);
public Task Add(long amount, Extra extra);
public async Task<bool> Transfer(
long amount,
IWallet to,
Extra extra)
{
if (amount <= 0)
throw new ArgumentOutOfRangeException(nameof(amount), "Amount must be greater than 0.");
var succ = await Take(amount,
extra with
{
OtherId = to.UserId
});
if (!succ)
return false;
await to.Add(amount,
extra with
{
OtherId = UserId
});
return true;
}
}

View File

@@ -1,43 +0,0 @@
#nullable disable
namespace NadekoBot.Services;
public interface ICurrencyService
{
Task AddAsync(
ulong userId,
string reason,
long amount,
bool gamble = false);
Task AddAsync(
IUser user,
string reason,
long amount,
bool sendMessage = false,
bool gamble = false);
Task AddBulkAsync(
IEnumerable<ulong> userIds,
IEnumerable<string> reasons,
IEnumerable<long> amounts,
bool gamble = false);
Task<bool> RemoveAsync(
ulong userId,
string reason,
long amount,
bool gamble = false);
Task<bool> RemoveAsync(
IUser userId,
string reason,
long amount,
bool sendMessage = false,
bool gamble = false);
Task RemoveBulkAsync(
IEnumerable<ulong> userIds,
IEnumerable<string> reasons,
IEnumerable<long> amounts,
bool gamble = false);
}

View File

@@ -1,3 +1,5 @@
using NadekoBot.Services.Currency;
#nullable disable #nullable disable
namespace NadekoBot.Services; namespace NadekoBot.Services;

View File

@@ -1,185 +0,0 @@
#nullable disable
using NadekoBot.Db;
using NadekoBot.Modules.Gambling.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Services;
public class CurrencyService : ICurrencyService, INService
{
private readonly DbService _db;
private readonly GamblingConfigService _gss;
private readonly IEmbedBuilderService _eb;
private readonly IUser _bot;
public CurrencyService(
DbService db,
DiscordSocketClient c,
GamblingConfigService gss,
IEmbedBuilderService eb)
{
_db = db;
_gss = gss;
_eb = eb;
_bot = c.CurrentUser;
}
private CurrencyTransaction GetCurrencyTransaction(ulong userId, string reason, long amount)
=> new() { Amount = amount, UserId = userId, Reason = reason ?? "-" };
private bool InternalChange(
ulong userId,
string userName,
string discrim,
string avatar,
string reason,
long amount,
bool gamble,
NadekoContext uow)
{
var result = uow.TryUpdateCurrencyState(userId, userName, discrim, avatar, amount);
if (result)
{
var t = GetCurrencyTransaction(userId, reason, amount);
uow.CurrencyTransactions.Add(t);
if (gamble)
{
var t2 = GetCurrencyTransaction(_bot.Id, reason, -amount);
uow.CurrencyTransactions.Add(t2);
uow.TryUpdateCurrencyState(_bot.Id, _bot.Username, _bot.Discriminator, _bot.AvatarId, -amount, true);
}
}
return result;
}
private async Task InternalAddAsync(
ulong userId,
string userName,
string discrim,
string avatar,
string reason,
long amount,
bool gamble)
{
if (amount < 0)
throw new ArgumentException("You can't add negative amounts. Use RemoveAsync method for that.",
nameof(amount));
await using var uow = _db.GetDbContext();
InternalChange(userId, userName, discrim, avatar, reason, amount, gamble, uow);
await uow.SaveChangesAsync();
}
public Task AddAsync(
ulong userId,
string reason,
long amount,
bool gamble = false)
=> InternalAddAsync(userId, null, null, null, reason, amount, gamble);
public async Task AddAsync(
IUser user,
string reason,
long amount,
bool sendMessage = false,
bool gamble = false)
{
await InternalAddAsync(user.Id, user.Username, user.Discriminator, user.AvatarId, reason, amount, gamble);
if (sendMessage)
try
{
var sign = _gss.Data.Currency.Sign;
await user.EmbedAsync(_eb.Create()
.WithOkColor()
.WithTitle("Received Currency")
.AddField("Amount", amount + sign)
.AddField("Reason", reason));
}
catch
{
// ignored
}
}
public async Task AddBulkAsync(
IEnumerable<ulong> userIds,
IEnumerable<string> reasons,
IEnumerable<long> amounts,
bool gamble = false)
{
var idArray = userIds as ulong[] ?? userIds.ToArray();
var reasonArray = reasons as string[] ?? reasons.ToArray();
var amountArray = amounts as long[] ?? amounts.ToArray();
if (idArray.Length != reasonArray.Length || reasonArray.Length != amountArray.Length)
throw new ArgumentException("Cannot perform bulk operation. Arrays are not of equal length.");
var userIdHashSet = new HashSet<ulong>(idArray.Length);
await using var uow = _db.GetDbContext();
for (var i = 0; i < idArray.Length; i++)
// i have to prevent same user changing more than once as it will cause db error
if (userIdHashSet.Add(idArray[i]))
InternalChange(idArray[i], null, null, null, reasonArray[i], amountArray[i], gamble, uow);
await uow.SaveChangesAsync();
}
public async Task RemoveBulkAsync(
IEnumerable<ulong> userIds,
IEnumerable<string> reasons,
IEnumerable<long> amounts,
bool gamble = false)
{
var idArray = userIds as ulong[] ?? userIds.ToArray();
var reasonArray = reasons as string[] ?? reasons.ToArray();
var amountArray = amounts as long[] ?? amounts.ToArray();
if (idArray.Length != reasonArray.Length || reasonArray.Length != amountArray.Length)
throw new ArgumentException("Cannot perform bulk operation. Arrays are not of equal length.");
var userIdHashSet = new HashSet<ulong>(idArray.Length);
await using var uow = _db.GetDbContext();
for (var i = 0; i < idArray.Length; i++)
// i have to prevent same user changing more than once as it will cause db error
if (userIdHashSet.Add(idArray[i]))
InternalChange(idArray[i], null, null, null, reasonArray[i], -amountArray[i], gamble, uow);
await uow.SaveChangesAsync();
}
private async Task<bool> InternalRemoveAsync(
ulong userId,
string userName,
string userDiscrim,
string avatar,
string reason,
long amount,
bool gamble = false)
{
if (amount < 0)
throw new ArgumentException("You can't remove negative amounts. Use AddAsync method for that.",
nameof(amount));
bool result;
await using var uow = _db.GetDbContext();
result = InternalChange(userId, userName, userDiscrim, avatar, reason, -amount, gamble, uow);
await uow.SaveChangesAsync();
return result;
}
public Task<bool> RemoveAsync(
ulong userId,
string reason,
long amount,
bool gamble = false)
=> InternalRemoveAsync(userId, null, null, null, reason, amount, gamble);
public Task<bool> RemoveAsync(
IUser user,
string reason,
long amount,
bool sendMessage = false,
bool gamble = false)
=> InternalRemoveAsync(user.Id, user.Username, user.Discriminator, user.AvatarId, reason, amount, gamble);
}