mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-11 09:48:26 -04:00
Added .betstats command, shows statistics for multiple gambling commands, .slotstats removed as it is obsolete
This commit is contained in:
9
src/NadekoBot/Db/Models/GamblingStats.cs
Normal file
9
src/NadekoBot/Db/Models/GamblingStats.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#nullable disable
|
||||||
|
namespace NadekoBot.Services.Database.Models;
|
||||||
|
|
||||||
|
public class GamblingStats : DbEntity
|
||||||
|
{
|
||||||
|
public string Feature { get; set; }
|
||||||
|
public decimal Bet { get; set; }
|
||||||
|
public decimal PaidOut { get; set; }
|
||||||
|
}
|
@@ -472,6 +472,14 @@ public abstract class NadekoContext : DbContext
|
|||||||
.IsUnique());
|
.IsUnique());
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region GamblingStats
|
||||||
|
|
||||||
|
modelBuilder.Entity<GamblingStats>(gs => gs
|
||||||
|
.HasIndex(x => x.Feature)
|
||||||
|
.IsUnique());
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
2898
src/NadekoBot/Migrations/Sqlite/20221003111019_gambling-stats.Designer.cs
generated
Normal file
2898
src/NadekoBot/Migrations/Sqlite/20221003111019_gambling-stats.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace NadekoBot.Migrations
|
||||||
|
{
|
||||||
|
public partial class gamblingstats : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "GamblingStats",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
Feature = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
Bet = table.Column<decimal>(type: "TEXT", nullable: false),
|
||||||
|
PaidOut = table.Column<decimal>(type: "TEXT", nullable: false),
|
||||||
|
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_GamblingStats", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_GamblingStats_Feature",
|
||||||
|
table: "GamblingStats",
|
||||||
|
column: "Feature",
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "GamblingStats");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -864,6 +864,32 @@ namespace NadekoBot.Migrations
|
|||||||
b.ToTable("FilterWordsChannelId");
|
b.ToTable("FilterWordsChannelId");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.GamblingStats", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<decimal>("Bet")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DateAdded")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Feature")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("PaidOut")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Feature")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("GamblingStats");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b =>
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
@@ -29,6 +29,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
private readonly IBankService _bank;
|
private readonly IBankService _bank;
|
||||||
private readonly IPatronageService _ps;
|
private readonly IPatronageService _ps;
|
||||||
private readonly RemindService _remind;
|
private readonly RemindService _remind;
|
||||||
|
private readonly GamblingTxTracker _gamblingTxTracker;
|
||||||
|
|
||||||
private IUserMessage rdMsg;
|
private IUserMessage rdMsg;
|
||||||
|
|
||||||
@@ -41,7 +42,8 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
GamblingConfigService configService,
|
GamblingConfigService configService,
|
||||||
IBankService bank,
|
IBankService bank,
|
||||||
IPatronageService ps,
|
IPatronageService ps,
|
||||||
RemindService remind)
|
RemindService remind,
|
||||||
|
GamblingTxTracker gamblingTxTracker)
|
||||||
: base(configService)
|
: base(configService)
|
||||||
{
|
{
|
||||||
_gs = gs;
|
_gs = gs;
|
||||||
@@ -51,6 +53,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
_bank = bank;
|
_bank = bank;
|
||||||
_ps = ps;
|
_ps = ps;
|
||||||
_remind = remind;
|
_remind = remind;
|
||||||
|
_gamblingTxTracker = gamblingTxTracker;
|
||||||
|
|
||||||
_enUsCulture = new CultureInfo("en-US", false).NumberFormat;
|
_enUsCulture = new CultureInfo("en-US", false).NumberFormat;
|
||||||
_enUsCulture.NumberDecimalDigits = 0;
|
_enUsCulture.NumberDecimalDigits = 0;
|
||||||
@@ -65,6 +68,26 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
return N(bal);
|
return N(bal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task BetStats()
|
||||||
|
{
|
||||||
|
var stats = await _gamblingTxTracker.GetAllAsync();
|
||||||
|
|
||||||
|
var eb = _eb.Create(ctx)
|
||||||
|
.WithOkColor();
|
||||||
|
|
||||||
|
var str = "**Feature | Bet | Paid Out | Payout %** \n";
|
||||||
|
foreach (var stat in stats)
|
||||||
|
{
|
||||||
|
var perc = (stat.PaidOut / stat.Bet).ToString("P2", Culture);
|
||||||
|
str += $"{stat.Feature, 8} | {stat.Bet, 15} | {stat.PaidOut, 15} | {perc}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
eb.WithDescription(str);
|
||||||
|
|
||||||
|
await ctx.Channel.EmbedAsync(eb);
|
||||||
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
public async Task Economy()
|
public async Task Economy()
|
||||||
{
|
{
|
||||||
|
@@ -47,27 +47,6 @@ public partial class Gambling
|
|||||||
public Task Test()
|
public Task Test()
|
||||||
=> Task.CompletedTask;
|
=> Task.CompletedTask;
|
||||||
|
|
||||||
[Cmd]
|
|
||||||
[OwnerOnly]
|
|
||||||
public async Task SlotStats()
|
|
||||||
{
|
|
||||||
//i remembered to not be a moron
|
|
||||||
var paid = totalPaidOut;
|
|
||||||
var bet = totalBet;
|
|
||||||
|
|
||||||
if (bet <= 0)
|
|
||||||
bet = 1;
|
|
||||||
|
|
||||||
var embed = _eb.Create()
|
|
||||||
.WithOkColor()
|
|
||||||
.WithTitle("Slot Stats")
|
|
||||||
.AddField("Total Bet", N(bet), true)
|
|
||||||
.AddField("Paid Out", N(paid), true)
|
|
||||||
.WithFooter($"Payout Rate: {paid * 1.0M / bet * 100:f4}%");
|
|
||||||
|
|
||||||
await ctx.Channel.EmbedAsync(embed);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
public async Task Slot(ShmartNumber amount)
|
public async Task Slot(ShmartNumber amount)
|
||||||
{
|
{
|
||||||
|
@@ -85,10 +85,6 @@
|
|||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.5" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.5" />
|
||||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.1" />
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.1" />
|
||||||
|
|
||||||
<!-- <PackageReference Include="System.Runtime.Experimental" Version="6.0.2" />-->
|
|
||||||
|
|
||||||
<!-- Used by .crypto command -->
|
|
||||||
|
|
||||||
<!-- Used by stream notifications -->
|
<!-- Used by stream notifications -->
|
||||||
<PackageReference Include="TwitchLib.Api" Version="3.4.1" />
|
<PackageReference Include="TwitchLib.Api" Version="3.4.1" />
|
||||||
|
|
||||||
|
@@ -4,12 +4,16 @@ using NadekoBot.Services.Currency;
|
|||||||
|
|
||||||
namespace NadekoBot.Services;
|
namespace NadekoBot.Services;
|
||||||
|
|
||||||
public class CurrencyService : ICurrencyService, INService
|
public sealed class CurrencyService : ICurrencyService, INService
|
||||||
{
|
{
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
|
private readonly ITxTracker _txTracker;
|
||||||
|
|
||||||
public CurrencyService(DbService db)
|
public CurrencyService(DbService db, ITxTracker txTracker)
|
||||||
=> _db = db;
|
{
|
||||||
|
_db = db;
|
||||||
|
_txTracker = txTracker;
|
||||||
|
}
|
||||||
|
|
||||||
public Task<IWallet> GetWalletAsync(ulong userId, CurrencyType type = CurrencyType.Default)
|
public Task<IWallet> GetWalletAsync(ulong userId, CurrencyType type = CurrencyType.Default)
|
||||||
{
|
{
|
||||||
@@ -70,16 +74,14 @@ public class CurrencyService : ICurrencyService, INService
|
|||||||
{
|
{
|
||||||
var wallet = await GetWalletAsync(userId);
|
var wallet = await GetWalletAsync(userId);
|
||||||
await wallet.Add(amount, txData);
|
await wallet.Add(amount, txData);
|
||||||
|
await _txTracker.TrackAdd(amount, txData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddAsync(
|
public async Task AddAsync(
|
||||||
IUser user,
|
IUser user,
|
||||||
long amount,
|
long amount,
|
||||||
TxData txData)
|
TxData txData)
|
||||||
{
|
=> await AddAsync(user.Id, amount, txData);
|
||||||
var wallet = await GetWalletAsync(user.Id);
|
|
||||||
await wallet.Add(amount, txData);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> RemoveAsync(
|
public async Task<bool> RemoveAsync(
|
||||||
ulong userId,
|
ulong userId,
|
||||||
@@ -87,15 +89,14 @@ public class CurrencyService : ICurrencyService, INService
|
|||||||
TxData txData)
|
TxData txData)
|
||||||
{
|
{
|
||||||
var wallet = await GetWalletAsync(userId);
|
var wallet = await GetWalletAsync(userId);
|
||||||
return await wallet.Take(amount, txData);
|
var result = await wallet.Take(amount, txData);
|
||||||
|
await _txTracker.TrackRemove(amount, txData);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> RemoveAsync(
|
public async Task<bool> RemoveAsync(
|
||||||
IUser user,
|
IUser user,
|
||||||
long amount,
|
long amount,
|
||||||
TxData txData)
|
TxData txData)
|
||||||
{
|
=> await RemoveAsync(user.Id, amount, txData);
|
||||||
var wallet = await GetWalletAsync(user.Id);
|
|
||||||
return await wallet.Take(amount, txData);
|
|
||||||
}
|
|
||||||
}
|
}
|
105
src/NadekoBot/Services/Currency/GamblingTxTracker.cs
Normal file
105
src/NadekoBot/Services/Currency/GamblingTxTracker.cs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
|
using NadekoBot.Services.Currency;
|
||||||
|
using NadekoBot.Services.Database.Models;
|
||||||
|
|
||||||
|
namespace NadekoBot.Services;
|
||||||
|
|
||||||
|
public sealed class GamblingTxTracker : ITxTracker, INService, IReadyExecutor
|
||||||
|
{
|
||||||
|
private static readonly IReadOnlySet<string> _gamblingTypes = new HashSet<string>(new[]
|
||||||
|
{
|
||||||
|
"lula",
|
||||||
|
"betroll",
|
||||||
|
"betflip",
|
||||||
|
"blackjack",
|
||||||
|
"betdraw",
|
||||||
|
"slot",
|
||||||
|
});
|
||||||
|
|
||||||
|
private ConcurrentDictionary<string, (decimal Bet, decimal PaidOut)> _stats = new();
|
||||||
|
|
||||||
|
private readonly DbService _db;
|
||||||
|
|
||||||
|
public GamblingTxTracker(DbService db)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnReadyAsync()
|
||||||
|
{
|
||||||
|
using var timer = new PeriodicTimer(TimeSpan.FromHours(1));
|
||||||
|
while (await timer.WaitForNextTickAsync())
|
||||||
|
{
|
||||||
|
await using var ctx = _db.GetDbContext();
|
||||||
|
await using var trans = await ctx.Database.BeginTransactionAsync();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var keys = _stats.Keys;
|
||||||
|
foreach (var key in keys)
|
||||||
|
{
|
||||||
|
if (_stats.TryRemove(key, out var stat))
|
||||||
|
{
|
||||||
|
await ctx.GetTable<GamblingStats>()
|
||||||
|
.InsertOrUpdateAsync(() => new()
|
||||||
|
{
|
||||||
|
Feature = key,
|
||||||
|
Bet = stat.Bet,
|
||||||
|
PaidOut = stat.PaidOut,
|
||||||
|
DateAdded = DateTime.UtcNow
|
||||||
|
}, old => new()
|
||||||
|
{
|
||||||
|
Bet = old.Bet + stat.Bet,
|
||||||
|
PaidOut = old.PaidOut + stat.PaidOut,
|
||||||
|
}, () => new()
|
||||||
|
{
|
||||||
|
Feature = key
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "An error occurred in gambling tx tracker");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await trans.CommitAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task TrackAdd(long amount, TxData txData)
|
||||||
|
{
|
||||||
|
if (_gamblingTypes.Contains(txData.Type))
|
||||||
|
{
|
||||||
|
_stats.AddOrUpdate(txData.Type,
|
||||||
|
_ => (0, amount),
|
||||||
|
(_, old) => (old.Bet, old.PaidOut + amount));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task TrackRemove(long amount, TxData txData)
|
||||||
|
{
|
||||||
|
if (_gamblingTypes.Contains(txData.Type))
|
||||||
|
{
|
||||||
|
_stats.AddOrUpdate(txData.Type,
|
||||||
|
_ => (amount, 0),
|
||||||
|
(_, old) => (old.Bet + amount, old.PaidOut));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyCollection<GamblingStats>> GetAllAsync()
|
||||||
|
{
|
||||||
|
await using var ctx = _db.GetDbContext();
|
||||||
|
return await ctx
|
||||||
|
.GetTable<GamblingStats>()
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
}
|
9
src/NadekoBot/Services/Currency/ITxTracker.cs
Normal file
9
src/NadekoBot/Services/Currency/ITxTracker.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using NadekoBot.Services.Currency;
|
||||||
|
|
||||||
|
namespace NadekoBot.Services;
|
||||||
|
|
||||||
|
public interface ITxTracker
|
||||||
|
{
|
||||||
|
Task TrackAdd(long amount, TxData txData);
|
||||||
|
Task TrackRemove(long amount, TxData txData);
|
||||||
|
}
|
@@ -852,8 +852,8 @@ antispamignore:
|
|||||||
- antispamignore
|
- antispamignore
|
||||||
eventstart:
|
eventstart:
|
||||||
- eventstart
|
- eventstart
|
||||||
slotstats:
|
betstats:
|
||||||
- slotstats
|
- betstats
|
||||||
bettest:
|
bettest:
|
||||||
- bettest
|
- bettest
|
||||||
slot:
|
slot:
|
||||||
|
@@ -1458,8 +1458,8 @@ eventstart:
|
|||||||
args:
|
args:
|
||||||
- "reaction"
|
- "reaction"
|
||||||
- "reaction -d 1 -a 50 --pot-size 1500"
|
- "reaction -d 1 -a 50 --pot-size 1500"
|
||||||
slotstats:
|
betstats:
|
||||||
desc: "Shows the total stats of the slot command for this bot's session."
|
desc: "Shows the total stats of several gambling features. Updates once an hour."
|
||||||
args:
|
args:
|
||||||
- ""
|
- ""
|
||||||
slottest:
|
slottest:
|
||||||
|
Reference in New Issue
Block a user