mirror of
				https://gitlab.com/Kwoth/nadekobot.git
				synced 2025-11-03 08:14:28 -05:00 
			
		
		
		
	- Improved .curtrs (It will now have a lot more useful data in the database, show Tx ids, and be partially localized)
- Added .curtr command which lets you see full information about one of your own transactions with the specified id - Added .WithAuthor(IUser) extension for embedbuilder
This commit is contained in:
		@@ -8,6 +8,7 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
 | 
			
		||||
## Changes
 | 
			
		||||
 | 
			
		||||
### Added
 | 
			
		||||
 | 
			
		||||
- Added `.deleteemptyservers` command
 | 
			
		||||
 | 
			
		||||
### Fixed
 | 
			
		||||
@@ -19,6 +20,7 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
 | 
			
		||||
- Fixed reference to non-existent command in bot.yml
 | 
			
		||||
 | 
			
		||||
### Changed
 | 
			
		||||
 | 
			
		||||
- CustomReactions module (and customreactions db table) has been renamed to Expressions.
 | 
			
		||||
  - This was done to remove confusion about how it relates to discord Reactions (it doesn't, it was created and named before discord reactions existed) 
 | 
			
		||||
  - Expression command now start with ex/expr and end with the name of the action or setting. 
 | 
			
		||||
@@ -35,6 +37,7 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
 | 
			
		||||
- [dev] Moved FilterWordsChannelId to a separate table
 | 
			
		||||
 | 
			
		||||
### Removed
 | 
			
		||||
 | 
			
		||||
- Removed `.bce` - use `.config` or `.config bot` specifically for bot config  
 | 
			
		||||
- Removed obsolete placeholders: %users% %servers% %userfull% %username% %userdiscrim% %useravatar% %id% %uid% %chname% %cid% %sid% %members% %server_time% %shardid% %time% %mention%  
 | 
			
		||||
- Removed some obsolete commands and strings  
 | 
			
		||||
 
 | 
			
		||||
@@ -4,9 +4,9 @@ namespace NadekoBot.Services.Database.Models;
 | 
			
		||||
public class CurrencyTransaction : DbEntity
 | 
			
		||||
{
 | 
			
		||||
    public long Amount { get; set; }
 | 
			
		||||
    public string Reason { get; set; }
 | 
			
		||||
    public string Note { get; set; }
 | 
			
		||||
    public ulong UserId { get; set; }
 | 
			
		||||
 | 
			
		||||
    public CurrencyTransaction Clone()
 | 
			
		||||
        => new() { Amount = Amount, Reason = Reason, UserId = UserId };
 | 
			
		||||
    public string Type { get; set; }
 | 
			
		||||
    public string Extra { get; set; }
 | 
			
		||||
    public ulong? OtherId { get; set; }
 | 
			
		||||
}
 | 
			
		||||
@@ -14,7 +14,7 @@ public class FilterChannelId : DbEntity
 | 
			
		||||
    public override int GetHashCode()
 | 
			
		||||
        => ChannelId.GetHashCode();
 | 
			
		||||
}
 | 
			
		||||
// todo check if filterwords migration works
 | 
			
		||||
 | 
			
		||||
public class FilterWordsChannelId : DbEntity
 | 
			
		||||
{
 | 
			
		||||
    public ulong ChannelId { get; set; }
 | 
			
		||||
 
 | 
			
		||||
@@ -146,13 +146,23 @@ public class NadekoContext : DbContext
 | 
			
		||||
 | 
			
		||||
        modelBuilder.Entity<DiscordUser>(du =>
 | 
			
		||||
        {
 | 
			
		||||
            du.Property(x => x.IsClubAdmin).HasDefaultValue(false);
 | 
			
		||||
            du.Property(x => x.IsClubAdmin)
 | 
			
		||||
              .HasDefaultValue(false);
 | 
			
		||||
 | 
			
		||||
            du.Property(x => x.NotifyOnLevelUp).HasDefaultValue(XpNotificationLocation.None);
 | 
			
		||||
            du.Property(x => x.NotifyOnLevelUp)
 | 
			
		||||
              .HasDefaultValue(XpNotificationLocation.None);
 | 
			
		||||
 | 
			
		||||
            du.Property(x => x.LastXpGain).HasDefaultValueSql("datetime('now', '-1 years')");
 | 
			
		||||
            du.Property(x => x.LastXpGain)
 | 
			
		||||
              .HasDefaultValueSql("datetime('now', '-1 years')");
 | 
			
		||||
 | 
			
		||||
            du.Property(x => x.LastLevelUp).HasDefaultValueSql("datetime('now')");
 | 
			
		||||
            du.Property(x => x.LastLevelUp)
 | 
			
		||||
              .HasDefaultValueSql("datetime('now')");
 | 
			
		||||
 | 
			
		||||
            du.Property(x => x.TotalXp)
 | 
			
		||||
              .HasDefaultValue(0);
 | 
			
		||||
            
 | 
			
		||||
            du.Property(x => x.CurrencyAmount)
 | 
			
		||||
              .HasDefaultValue(0);
 | 
			
		||||
 | 
			
		||||
            du.HasAlternateKey(w => w.UserId);
 | 
			
		||||
            du.HasOne(x => x.Club).WithMany(x => x.Users).IsRequired(false);
 | 
			
		||||
@@ -244,7 +254,22 @@ public class NadekoContext : DbContext
 | 
			
		||||
 | 
			
		||||
        #region CurrencyTransactions
 | 
			
		||||
 | 
			
		||||
        modelBuilder.Entity<CurrencyTransaction>().HasIndex(x => x.UserId).IsUnique(false);
 | 
			
		||||
        modelBuilder.Entity<CurrencyTransaction>(e =>
 | 
			
		||||
        {
 | 
			
		||||
            e.HasIndex(x => x.UserId)
 | 
			
		||||
             .IsUnique(false);
 | 
			
		||||
 | 
			
		||||
            e.Property(x => x.OtherId)
 | 
			
		||||
             .HasDefaultValueSql("NULL");
 | 
			
		||||
 | 
			
		||||
            e.Property(x => x.Type)
 | 
			
		||||
             .IsRequired();
 | 
			
		||||
            
 | 
			
		||||
            e.Property(x => x.Extra)
 | 
			
		||||
             .IsRequired();
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
                    
 | 
			
		||||
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
@@ -321,6 +346,7 @@ public class NadekoContext : DbContext
 | 
			
		||||
        atch.HasMany(x => x.Users).WithOne(x => x.Channel).OnDelete(DeleteBehavior.Cascade);
 | 
			
		||||
 | 
			
		||||
        modelBuilder.Entity<AutoTranslateUser>(atu => atu.HasAlternateKey(x => new { x.ChannelId, x.UserId }));
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#if DEBUG
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2763
									
								
								src/NadekoBot/Migrations/20220125044401_curtrs-rework-discorduser-defaults.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										2763
									
								
								src/NadekoBot/Migrations/20220125044401_curtrs-rework-discorduser-defaults.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Migrations
 | 
			
		||||
{
 | 
			
		||||
    public partial class curtrsreworkdiscorduserdefaults : Migration
 | 
			
		||||
    {
 | 
			
		||||
        protected override void Up(MigrationBuilder migrationBuilder)
 | 
			
		||||
        {
 | 
			
		||||
            migrationBuilder.RenameColumn(
 | 
			
		||||
                name: "Reason",
 | 
			
		||||
                table: "CurrencyTransactions",
 | 
			
		||||
                newName: "Note");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.AlterColumn<int>(
 | 
			
		||||
                name: "TotalXp",
 | 
			
		||||
                table: "DiscordUser",
 | 
			
		||||
                type: "INTEGER",
 | 
			
		||||
                nullable: false,
 | 
			
		||||
                defaultValue: 0,
 | 
			
		||||
                oldClrType: typeof(int),
 | 
			
		||||
                oldType: "INTEGER");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.AlterColumn<long>(
 | 
			
		||||
                name: "CurrencyAmount",
 | 
			
		||||
                table: "DiscordUser",
 | 
			
		||||
                type: "INTEGER",
 | 
			
		||||
                nullable: false,
 | 
			
		||||
                defaultValue: 0L,
 | 
			
		||||
                oldClrType: typeof(long),
 | 
			
		||||
                oldType: "INTEGER");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.AddColumn<string>(
 | 
			
		||||
                name: "Extra",
 | 
			
		||||
                table: "CurrencyTransactions",
 | 
			
		||||
                type: "TEXT",
 | 
			
		||||
                nullable: false,
 | 
			
		||||
                defaultValue: "");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.AddColumn<ulong>(
 | 
			
		||||
                name: "OtherId",
 | 
			
		||||
                table: "CurrencyTransactions",
 | 
			
		||||
                type: "INTEGER",
 | 
			
		||||
                nullable: true,
 | 
			
		||||
                defaultValueSql: "NULL");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.AddColumn<string>(
 | 
			
		||||
                name: "Type",
 | 
			
		||||
                table: "CurrencyTransactions",
 | 
			
		||||
                type: "TEXT",
 | 
			
		||||
                nullable: false,
 | 
			
		||||
                defaultValue: "");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected override void Down(MigrationBuilder migrationBuilder)
 | 
			
		||||
        {
 | 
			
		||||
            migrationBuilder.DropColumn(
 | 
			
		||||
                name: "Extra",
 | 
			
		||||
                table: "CurrencyTransactions");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.DropColumn(
 | 
			
		||||
                name: "OtherId",
 | 
			
		||||
                table: "CurrencyTransactions");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.DropColumn(
 | 
			
		||||
                name: "Type",
 | 
			
		||||
                table: "CurrencyTransactions");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.RenameColumn(
 | 
			
		||||
                name: "Note",
 | 
			
		||||
                table: "CurrencyTransactions",
 | 
			
		||||
                newName: "Reason");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.AlterColumn<int>(
 | 
			
		||||
                name: "TotalXp",
 | 
			
		||||
                table: "DiscordUser",
 | 
			
		||||
                type: "INTEGER",
 | 
			
		||||
                nullable: false,
 | 
			
		||||
                oldClrType: typeof(int),
 | 
			
		||||
                oldType: "INTEGER",
 | 
			
		||||
                oldDefaultValue: 0);
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.AlterColumn<long>(
 | 
			
		||||
                name: "CurrencyAmount",
 | 
			
		||||
                table: "DiscordUser",
 | 
			
		||||
                type: "INTEGER",
 | 
			
		||||
                nullable: false,
 | 
			
		||||
                oldClrType: typeof(long),
 | 
			
		||||
                oldType: "INTEGER",
 | 
			
		||||
                oldDefaultValue: 0L);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -102,7 +102,9 @@ namespace NadekoBot.Migrations
 | 
			
		||||
                        .HasColumnType("INTEGER");
 | 
			
		||||
 | 
			
		||||
                    b.Property<long>("CurrencyAmount")
 | 
			
		||||
                        .HasColumnType("INTEGER");
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("INTEGER")
 | 
			
		||||
                        .HasDefaultValue(0L);
 | 
			
		||||
 | 
			
		||||
                    b.Property<DateTime?>("DateAdded")
 | 
			
		||||
                        .HasColumnType("TEXT");
 | 
			
		||||
@@ -131,7 +133,9 @@ namespace NadekoBot.Migrations
 | 
			
		||||
                        .HasDefaultValue(0);
 | 
			
		||||
 | 
			
		||||
                    b.Property<int>("TotalXp")
 | 
			
		||||
                        .HasColumnType("INTEGER");
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("INTEGER")
 | 
			
		||||
                        .HasDefaultValue(0);
 | 
			
		||||
 | 
			
		||||
                    b.Property<ulong>("UserId")
 | 
			
		||||
                        .HasColumnType("INTEGER");
 | 
			
		||||
@@ -502,7 +506,20 @@ namespace NadekoBot.Migrations
 | 
			
		||||
                    b.Property<DateTime?>("DateAdded")
 | 
			
		||||
                        .HasColumnType("TEXT");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Reason")
 | 
			
		||||
                    b.Property<string>("Extra")
 | 
			
		||||
                        .IsRequired()
 | 
			
		||||
                        .HasColumnType("TEXT");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Note")
 | 
			
		||||
                        .HasColumnType("TEXT");
 | 
			
		||||
 | 
			
		||||
                    b.Property<ulong?>("OtherId")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("INTEGER")
 | 
			
		||||
                        .HasDefaultValueSql("NULL");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Type")
 | 
			
		||||
                        .IsRequired()
 | 
			
		||||
                        .HasColumnType("TEXT");
 | 
			
		||||
 | 
			
		||||
                    b.Property<ulong>("UserId")
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
#nullable disable
 | 
			
		||||
using LinqToDB;
 | 
			
		||||
using LinqToDB.EntityFrameworkCore;
 | 
			
		||||
using NadekoBot.Db;
 | 
			
		||||
using NadekoBot.Db.Models;
 | 
			
		||||
using NadekoBot.Modules.Gambling.Common;
 | 
			
		||||
@@ -7,10 +9,10 @@ using NadekoBot.Services.Currency;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using System.Globalization;
 | 
			
		||||
using System.Numerics;
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Gambling;
 | 
			
		||||
 | 
			
		||||
// todo leave empty servers
 | 
			
		||||
public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
{
 | 
			
		||||
    public enum RpsPick
 | 
			
		||||
@@ -66,6 +68,9 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
    {
 | 
			
		||||
        var flowersCi = (CultureInfo)Culture.Clone();
 | 
			
		||||
        flowersCi.NumberFormat.CurrencySymbol = CurrencySign;
 | 
			
		||||
        flowersCi.NumberFormat.CurrencyNegativePattern = 5;
 | 
			
		||||
        // if (cur < 0)
 | 
			
		||||
        //     cur = -cur;
 | 
			
		||||
        return cur.ToString("C0", flowersCi);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -199,6 +204,8 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
    public partial Task CurrencyTransactions(IUser usr, int page)
 | 
			
		||||
        => InternalCurrencyTransactions(usr.Id, page);
 | 
			
		||||
 | 
			
		||||
    // todo curtrs max lifetime
 | 
			
		||||
    // todo waifu decay
 | 
			
		||||
    private async Task InternalCurrencyTransactions(ulong userId, int page)
 | 
			
		||||
    {
 | 
			
		||||
        if (--page < 0)
 | 
			
		||||
@@ -215,19 +222,85 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
                                                            ?? $"{userId}")))
 | 
			
		||||
                       .WithOkColor();
 | 
			
		||||
 | 
			
		||||
        var desc = string.Empty;
 | 
			
		||||
        var sb = new StringBuilder();
 | 
			
		||||
        foreach (var tr in trs)
 | 
			
		||||
        {
 | 
			
		||||
            var type = tr.Amount > 0 ? "🔵" : "🔴";
 | 
			
		||||
            var date = Format.Code($"〖{tr.DateAdded:HH:mm yyyy-MM-dd}〗");
 | 
			
		||||
            desc += $"\\{type} {date} {Format.Bold(n(tr.Amount))}\n\t{tr.Reason?.Trim()}\n";
 | 
			
		||||
            var change = tr.Amount >= 0 ? "🔵" : "🔴";
 | 
			
		||||
            var kwumId = new kwum(tr.Id).ToString();
 | 
			
		||||
            var date = $"#{Format.Code(kwumId)} `〖{GetFormattedCurtrDate(tr)}〗`";
 | 
			
		||||
            
 | 
			
		||||
            
 | 
			
		||||
            sb.AppendLine($"\\{change} {date} {Format.Bold(n(tr.Amount))}");
 | 
			
		||||
            var transactionString = GetHumanReadableTransaction(tr.Type, tr.Extra, tr.OtherId);
 | 
			
		||||
            if(transactionString is not null)
 | 
			
		||||
                sb.AppendLine(transactionString);
 | 
			
		||||
            
 | 
			
		||||
            if (!string.IsNullOrWhiteSpace(tr.Note))
 | 
			
		||||
                sb.AppendLine($"\t`Note:` {tr.Note.TrimTo(50)}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        embed.WithDescription(desc);
 | 
			
		||||
        embed.WithDescription(sb.ToString());
 | 
			
		||||
        embed.WithFooter(GetText(strs.page(page + 1)));
 | 
			
		||||
        await ctx.Channel.EmbedAsync(embed);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static string GetFormattedCurtrDate(CurrencyTransaction ct)
 | 
			
		||||
        => $"{ct.DateAdded:HH:mm yyyy-MM-dd}";
 | 
			
		||||
 | 
			
		||||
    [Cmd]
 | 
			
		||||
    public async partial Task CurrencyTransaction(kwum id)
 | 
			
		||||
    {
 | 
			
		||||
        int intId = id;
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
 | 
			
		||||
        var tr = await uow.CurrencyTransactions
 | 
			
		||||
                          .ToLinqToDBTable()
 | 
			
		||||
                          .Where(x => x.Id == intId && x.UserId == ctx.User.Id)
 | 
			
		||||
                          .FirstOrDefaultAsync();
 | 
			
		||||
 | 
			
		||||
        if (tr is null)
 | 
			
		||||
        {
 | 
			
		||||
            await ReplyErrorLocalizedAsync(strs.not_found);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var eb = _eb.Create(ctx)
 | 
			
		||||
           .WithOkColor();
 | 
			
		||||
 | 
			
		||||
        eb.WithAuthor(ctx.User);
 | 
			
		||||
        eb.WithTitle(GetText(strs.transaction));
 | 
			
		||||
        eb.WithDescription(new kwum(tr.Id).ToString());
 | 
			
		||||
        eb.AddField("Amount", n(tr.Amount), false);
 | 
			
		||||
        eb.AddField("Type", tr.Type, true);
 | 
			
		||||
        eb.AddField("Extra", tr.Extra, true);
 | 
			
		||||
        
 | 
			
		||||
        if (tr.OtherId is ulong other)
 | 
			
		||||
            eb.AddField("From Id", other);
 | 
			
		||||
        
 | 
			
		||||
        if (!string.IsNullOrWhiteSpace(tr.Note))
 | 
			
		||||
            eb.AddField("Note", tr.Note);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        eb.WithFooter(GetFormattedCurtrDate(tr));
 | 
			
		||||
        
 | 
			
		||||
        await ctx.Channel.EmbedAsync(eb);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private string GetHumanReadableTransaction(string type, string subType, ulong? maybeUserId)
 | 
			
		||||
        => (type, subType, maybeUserId) switch
 | 
			
		||||
        {
 | 
			
		||||
            ("gift", var name, ulong userId) => GetText(strs.curtr_gift(name, userId)),
 | 
			
		||||
            ("award", var name, ulong userId) => GetText(strs.curtr_award(name, userId)),
 | 
			
		||||
            ("take", var name, ulong userId) => GetText(strs.curtr_take(name, userId)),
 | 
			
		||||
            ("blackjack", _, _) => $"Blackjack - {subType}",
 | 
			
		||||
            ("wheel", _, _) => $"Wheel Of Fortune - {subType}",
 | 
			
		||||
            ("rps", _, _) => $"Rock Paper Scissors - {subType}",
 | 
			
		||||
            (null, _, _) => null,
 | 
			
		||||
            (_, null, _) => null,
 | 
			
		||||
            (_, _, ulong userId) => $"{type.Titleize()} - {subType.Titleize()} | [{userId}]",
 | 
			
		||||
            _ => $"{type.Titleize()} - {subType.Titleize()}"
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
    [Cmd]
 | 
			
		||||
    [Priority(0)]
 | 
			
		||||
    public async partial Task Cash(ulong userId)
 | 
			
		||||
@@ -253,7 +326,7 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
        if (amount <= 0 || ctx.User.Id == receiver.Id || receiver.IsBot)
 | 
			
		||||
            return;
 | 
			
		||||
        
 | 
			
		||||
        if (!await _cs.TransferAsync(ctx.User.Id, receiver.Id, amount, msg))
 | 
			
		||||
        if (!await _cs.TransferAsync(ctx.User.Id, receiver.Id, amount, ctx.User.ToString(), msg))
 | 
			
		||||
        {
 | 
			
		||||
            await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
 | 
			
		||||
            return;
 | 
			
		||||
@@ -300,7 +373,7 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
 | 
			
		||||
        await _cs.AddAsync(usr.Id,
 | 
			
		||||
            amount,
 | 
			
		||||
            new Extra("owner", "award", $"Awarded by bot owner. ({ctx.User.Username}/{ctx.User.Id}) {msg ?? ""}")
 | 
			
		||||
            new TxData("award", ctx.User.ToString()!, msg, ctx.User.Id)
 | 
			
		||||
        );
 | 
			
		||||
        await ReplyConfirmLocalizedAsync(strs.awarded(n(amount), $"<@{usrId}>"));
 | 
			
		||||
    }
 | 
			
		||||
@@ -315,9 +388,10 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
 | 
			
		||||
        await _cs.AddBulkAsync(users.Select(x => x.Id).ToList(),
 | 
			
		||||
            amount,
 | 
			
		||||
            new("owner",
 | 
			
		||||
                "award",
 | 
			
		||||
                $"Awarded by bot owner to **{role.Name}** role. ({ctx.User.Username}/{ctx.User.Id})"));
 | 
			
		||||
            new("award",
 | 
			
		||||
                ctx.User.ToString()!,
 | 
			
		||||
                role.Name,
 | 
			
		||||
                ctx.User.Id));
 | 
			
		||||
 | 
			
		||||
        await ReplyConfirmLocalizedAsync(strs.mass_award(n(amount),
 | 
			
		||||
            Format.Bold(users.Count.ToString()),
 | 
			
		||||
@@ -334,7 +408,10 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
 | 
			
		||||
        await _cs.RemoveBulkAsync(users.Select(x => x.Id).ToList(),
 | 
			
		||||
            amount,
 | 
			
		||||
            new("owner", "take", $"Taken by bot owner from **{role.Name}** role. ({ctx.User.Username}/{ctx.User.Id})"));
 | 
			
		||||
            new("take",
 | 
			
		||||
                ctx.User.ToString()!,
 | 
			
		||||
                null,
 | 
			
		||||
                ctx.User.Id));
 | 
			
		||||
 | 
			
		||||
        await ReplyConfirmLocalizedAsync(strs.mass_take(n(amount),
 | 
			
		||||
            Format.Bold(users.Count.ToString()),
 | 
			
		||||
@@ -350,9 +427,12 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
        if (amount <= 0)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        if (await _cs.RemoveAsync(user.Id,
 | 
			
		||||
                amount,
 | 
			
		||||
                new("owner", "take", $"Taken by bot owner. ({ctx.User.Username}/{ctx.User.Id})")))
 | 
			
		||||
        var extra = new TxData("take",
 | 
			
		||||
            ctx.User.ToString()!,
 | 
			
		||||
            null,
 | 
			
		||||
            ctx.User.Id);
 | 
			
		||||
        
 | 
			
		||||
        if (await _cs.RemoveAsync(user.Id, amount, extra))
 | 
			
		||||
            await ReplyConfirmLocalizedAsync(strs.take(n(amount), Format.Bold(user.ToString())));
 | 
			
		||||
        else
 | 
			
		||||
            await ReplyErrorLocalizedAsync(strs.take_fail(n(amount), Format.Bold(user.ToString()), CurrencySign));
 | 
			
		||||
@@ -366,9 +446,12 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
        if (amount <= 0)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        if (await _cs.RemoveAsync(usrId,
 | 
			
		||||
                amount,
 | 
			
		||||
                new("owner", "take", $"Taken by bot owner. ({ctx.User.Username}/{ctx.User.Id})")))
 | 
			
		||||
        var extra = new TxData("take",
 | 
			
		||||
            ctx.User.ToString()!,
 | 
			
		||||
            null,
 | 
			
		||||
            ctx.User.Id);
 | 
			
		||||
        
 | 
			
		||||
        if (await _cs.RemoveAsync(usrId, amount, extra))
 | 
			
		||||
            await ReplyConfirmLocalizedAsync(strs.take(n(amount), $"<@{usrId}>"));
 | 
			
		||||
        else
 | 
			
		||||
            await ReplyErrorLocalizedAsync(strs.take_fail(n(amount), Format.Code(usrId.ToString()), CurrencySign));
 | 
			
		||||
@@ -383,7 +466,8 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
 | 
			
		||||
        //since the challenge is created by another user, we need to reverse the ids
 | 
			
		||||
        //if it gets removed, means challenge is accepted
 | 
			
		||||
        if (_service.Duels.TryRemove((ctx.User.Id, u.Id), out var game)) await game.StartGame();
 | 
			
		||||
        if (_service.Duels.TryRemove((ctx.User.Id, u.Id), out var game))
 | 
			
		||||
            await game.StartGame();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Cmd]
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ public class CurrencyService : ICurrencyService, INService
 | 
			
		||||
    public async Task AddBulkAsync(
 | 
			
		||||
        IReadOnlyCollection<ulong> userIds,
 | 
			
		||||
        long amount,
 | 
			
		||||
        Extra extra,
 | 
			
		||||
        TxData txData,
 | 
			
		||||
        CurrencyType type = CurrencyType.Default)
 | 
			
		||||
    {
 | 
			
		||||
        if (type == CurrencyType.Default)
 | 
			
		||||
@@ -34,7 +34,7 @@ public class CurrencyService : ICurrencyService, INService
 | 
			
		||||
            foreach (var userId in userIds)
 | 
			
		||||
            {
 | 
			
		||||
                var wallet = new DefaultWallet(userId, ctx);
 | 
			
		||||
                await wallet.Add(amount, extra);
 | 
			
		||||
                await wallet.Add(amount, txData);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await ctx.SaveChangesAsync();
 | 
			
		||||
@@ -47,7 +47,7 @@ public class CurrencyService : ICurrencyService, INService
 | 
			
		||||
    public async Task RemoveBulkAsync(
 | 
			
		||||
        IReadOnlyCollection<ulong> userIds,
 | 
			
		||||
        long amount,
 | 
			
		||||
        Extra extra,
 | 
			
		||||
        TxData txData,
 | 
			
		||||
        CurrencyType type = CurrencyType.Default)
 | 
			
		||||
    {
 | 
			
		||||
        if (type == CurrencyType.Default)
 | 
			
		||||
@@ -68,54 +68,55 @@ public class CurrencyService : ICurrencyService, INService
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private CurrencyTransaction GetCurrencyTransaction(ulong userId, string reason, long amount)
 | 
			
		||||
        => new() { Amount = amount, UserId = userId, Reason = reason ?? "-" };
 | 
			
		||||
        => new() { Amount = amount, UserId = userId, Note = reason ?? "-" };
 | 
			
		||||
 | 
			
		||||
    public async Task AddAsync(
 | 
			
		||||
        ulong userId,
 | 
			
		||||
        long amount,
 | 
			
		||||
        Extra extra)
 | 
			
		||||
        TxData txData)
 | 
			
		||||
    {
 | 
			
		||||
        await using var wallet = await GetWalletAsync(userId);
 | 
			
		||||
        await wallet.Add(amount, extra);
 | 
			
		||||
        await wallet.Add(amount, txData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task AddAsync(
 | 
			
		||||
        IUser user,
 | 
			
		||||
        long amount,
 | 
			
		||||
        Extra extra)
 | 
			
		||||
        TxData txData)
 | 
			
		||||
    {
 | 
			
		||||
        await using var wallet = await GetWalletAsync(user.Id);
 | 
			
		||||
        await wallet.Add(amount, extra);
 | 
			
		||||
        await wallet.Add(amount, txData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> RemoveAsync(
 | 
			
		||||
        ulong userId,
 | 
			
		||||
        long amount,
 | 
			
		||||
        Extra extra)
 | 
			
		||||
        TxData txData)
 | 
			
		||||
    {
 | 
			
		||||
        await using var wallet = await GetWalletAsync(userId);
 | 
			
		||||
        return await wallet.Take(amount, extra);
 | 
			
		||||
        return await wallet.Take(amount, txData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> RemoveAsync(
 | 
			
		||||
        IUser user,
 | 
			
		||||
        long amount,
 | 
			
		||||
        Extra extra)
 | 
			
		||||
        TxData txData)
 | 
			
		||||
    {
 | 
			
		||||
        await using var wallet = await GetWalletAsync(user.Id);
 | 
			
		||||
        return await wallet.Take(amount, extra);
 | 
			
		||||
        return await wallet.Take(amount, txData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> TransferAsync(
 | 
			
		||||
        ulong from,
 | 
			
		||||
        ulong to,
 | 
			
		||||
        ulong fromId,
 | 
			
		||||
        ulong toId,
 | 
			
		||||
        long amount,
 | 
			
		||||
        string fromName,
 | 
			
		||||
        string note)
 | 
			
		||||
    {
 | 
			
		||||
        await using var fromWallet = await GetWalletAsync(@from);
 | 
			
		||||
        await using var toWallet = await GetWalletAsync(to);
 | 
			
		||||
        await using var fromWallet = await GetWalletAsync(fromId);
 | 
			
		||||
        await using var toWallet = await GetWalletAsync(toId);
 | 
			
		||||
        
 | 
			
		||||
        var extra = new Extra("transfer", "gift", note);
 | 
			
		||||
        var extra = new TxData("gift", fromName, note, fromId);
 | 
			
		||||
        
 | 
			
		||||
        return await fromWallet.Transfer(amount, toWallet, extra);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ public class DefaultWallet : IWallet
 | 
			
		||||
               .Select(x => x.CurrencyAmount)
 | 
			
		||||
               .FirstOrDefaultAsync();
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> Take(long amount, Extra extra)
 | 
			
		||||
    public async Task<bool> Take(long amount, TxData txData)
 | 
			
		||||
    {
 | 
			
		||||
        if (amount < 0)
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(amount), "Amount to take must be non negative.");
 | 
			
		||||
@@ -39,20 +39,21 @@ public class DefaultWallet : IWallet
 | 
			
		||||
        if (changed == 0)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        // todo type, subtype
 | 
			
		||||
        // todo from? by?
 | 
			
		||||
        await _ctx.CreateLinqToDbContext()
 | 
			
		||||
                  .InsertAsync(new CurrencyTransaction()
 | 
			
		||||
                  {
 | 
			
		||||
                      Amount = -amount,
 | 
			
		||||
                      Reason = extra.Note,
 | 
			
		||||
                      Note = txData.Note,
 | 
			
		||||
                      UserId = UserId,
 | 
			
		||||
                      Type = txData.Type,
 | 
			
		||||
                      Extra = txData.Extra,
 | 
			
		||||
                      OtherId = txData.OtherId
 | 
			
		||||
                  });
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task Add(long amount, Extra extra)
 | 
			
		||||
    public async Task Add(long amount, TxData txData)
 | 
			
		||||
    {
 | 
			
		||||
        if (amount <= 0)
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(amount), "Amount must be greater than 0.");
 | 
			
		||||
@@ -60,6 +61,7 @@ public class DefaultWallet : IWallet
 | 
			
		||||
        await using (var tran = await _ctx.Database.BeginTransactionAsync())
 | 
			
		||||
        {
 | 
			
		||||
            var changed = await _ctx.DiscordUser
 | 
			
		||||
                                    .Where(x => x.UserId == UserId)
 | 
			
		||||
                                    .UpdateAsync(x => new()
 | 
			
		||||
                                    {
 | 
			
		||||
                                        CurrencyAmount = x.CurrencyAmount + amount
 | 
			
		||||
@@ -82,8 +84,11 @@ public class DefaultWallet : IWallet
 | 
			
		||||
        var ct = new CurrencyTransaction()
 | 
			
		||||
        {
 | 
			
		||||
            Amount = amount,
 | 
			
		||||
            Reason = extra.Note,
 | 
			
		||||
            UserId = UserId,
 | 
			
		||||
            Note = txData.Note,
 | 
			
		||||
            Type = txData.Type,
 | 
			
		||||
            Extra = txData.Extra,
 | 
			
		||||
            OtherId = txData.OtherId,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        await _ctx.CreateLinqToDbContext()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +0,0 @@
 | 
			
		||||
namespace NadekoBot.Services.Currency;
 | 
			
		||||
 | 
			
		||||
public record class Extra(string Type, string Subtype, string Note = "", ulong OtherId = 0);
 | 
			
		||||
@@ -10,38 +10,39 @@ public interface ICurrencyService
 | 
			
		||||
    Task AddBulkAsync(
 | 
			
		||||
        IReadOnlyCollection<ulong> userIds,
 | 
			
		||||
        long amount,
 | 
			
		||||
        Extra extra,
 | 
			
		||||
        TxData txData,
 | 
			
		||||
        CurrencyType type = CurrencyType.Default);
 | 
			
		||||
 | 
			
		||||
    Task RemoveBulkAsync(
 | 
			
		||||
        IReadOnlyCollection<ulong> userIds,
 | 
			
		||||
        long amount,
 | 
			
		||||
        Extra extra,
 | 
			
		||||
        TxData txData,
 | 
			
		||||
        CurrencyType type = CurrencyType.Default);
 | 
			
		||||
 | 
			
		||||
    Task AddAsync(
 | 
			
		||||
        ulong userId,
 | 
			
		||||
        long amount,
 | 
			
		||||
        Extra extra);
 | 
			
		||||
        TxData txData);
 | 
			
		||||
 | 
			
		||||
    Task AddAsync(
 | 
			
		||||
        IUser user,
 | 
			
		||||
        long amount,
 | 
			
		||||
        Extra extra);
 | 
			
		||||
        TxData txData);
 | 
			
		||||
 | 
			
		||||
    Task<bool> RemoveAsync(
 | 
			
		||||
        ulong userId,
 | 
			
		||||
        long amount,
 | 
			
		||||
        Extra extra);
 | 
			
		||||
        TxData txData);
 | 
			
		||||
 | 
			
		||||
    Task<bool> RemoveAsync(
 | 
			
		||||
        IUser user,
 | 
			
		||||
        long amount,
 | 
			
		||||
        Extra extra);
 | 
			
		||||
        TxData txData);
 | 
			
		||||
 | 
			
		||||
    Task<bool> TransferAsync(
 | 
			
		||||
        ulong from,
 | 
			
		||||
        ulong to,
 | 
			
		||||
        long amount,
 | 
			
		||||
        string fromName,
 | 
			
		||||
        string note);
 | 
			
		||||
}
 | 
			
		||||
@@ -5,19 +5,19 @@ 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 Task<bool> Take(long amount, TxData txData);
 | 
			
		||||
    public Task Add(long amount, TxData txData);
 | 
			
		||||
    
 | 
			
		||||
    public async Task<bool> Transfer(
 | 
			
		||||
        long amount,
 | 
			
		||||
        IWallet to,
 | 
			
		||||
        Extra extra)
 | 
			
		||||
        TxData txData)
 | 
			
		||||
    {
 | 
			
		||||
        if (amount <= 0)
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(amount), "Amount must be greater than 0.");
 | 
			
		||||
 | 
			
		||||
        var succ = await Take(amount,
 | 
			
		||||
            extra with
 | 
			
		||||
            txData with
 | 
			
		||||
            {
 | 
			
		||||
                OtherId = to.UserId
 | 
			
		||||
            });
 | 
			
		||||
@@ -26,7 +26,7 @@ public interface IWallet : IDisposable, IAsyncDisposable
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        await to.Add(amount,
 | 
			
		||||
            extra with
 | 
			
		||||
            txData with
 | 
			
		||||
            {
 | 
			
		||||
                OtherId = UserId
 | 
			
		||||
            });
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								src/NadekoBot/Services/Currency/TxData.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/NadekoBot/Services/Currency/TxData.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
namespace NadekoBot.Services.Currency;
 | 
			
		||||
 | 
			
		||||
public record class TxData(string Type, string Extra, string? Note = "", ulong? OtherId = null);
 | 
			
		||||
@@ -11,6 +11,9 @@ public static class Extensions
 | 
			
		||||
    private static readonly Regex _urlRegex =
 | 
			
		||||
        new(@"^(https?|ftp)://(?<path>[^\s/$.?#].[^\s]*)$", RegexOptions.Compiled);
 | 
			
		||||
 | 
			
		||||
    public static IEmbedBuilder WithAuthor(this IEmbedBuilder eb, IUser author)
 | 
			
		||||
        => eb.WithAuthor(author.ToString(), author.RealAvatarUrl().ToString());
 | 
			
		||||
    
 | 
			
		||||
    public static Task EditAsync(this IUserMessage msg, SmartText text)
 | 
			
		||||
        => text switch
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -674,6 +674,8 @@ cash:
 | 
			
		||||
- cur
 | 
			
		||||
currencytransactions:
 | 
			
		||||
- curtrs
 | 
			
		||||
currencytransaction:
 | 
			
		||||
  - curtr
 | 
			
		||||
listperms:
 | 
			
		||||
- listperms
 | 
			
		||||
- lp
 | 
			
		||||
 
 | 
			
		||||
@@ -1168,6 +1168,10 @@ currencytransactions:
 | 
			
		||||
  args:
 | 
			
		||||
    - "2"
 | 
			
		||||
    - "@SomeUser 2"
 | 
			
		||||
currencytransaction:
 | 
			
		||||
  desc: "Shows full details about a currency transaction with the specified ID. You can only check your own transactions."
 | 
			
		||||
  args:
 | 
			
		||||
    - "3yvd"
 | 
			
		||||
listperms:
 | 
			
		||||
  desc: "Lists whole permission chain with their indexes. You can specify an optional page number if there are a lot of permissions."
 | 
			
		||||
  args:
 | 
			
		||||
 
 | 
			
		||||
@@ -242,6 +242,7 @@
 | 
			
		||||
  "take": "successfully took {0} from {1}",
 | 
			
		||||
  "take_fail": "was unable to take {0} from {1} because the user doesn't have that much {2}!",
 | 
			
		||||
  "transactions": "Transactions of user {0}",
 | 
			
		||||
  "transaction": "Currency Transaction",
 | 
			
		||||
  "commandlist_regen": "Commandlist regenerated.",
 | 
			
		||||
  "commands_instr": "Type `{0}h CommandName` to see the help for that specified command. e.g. `{0}h {0}8ball`",
 | 
			
		||||
  "command_not_found": "I can't find that command. Please verify that the command exists before trying again.",
 | 
			
		||||
@@ -967,5 +968,8 @@
 | 
			
		||||
  "tags": "Tags",
 | 
			
		||||
  "imageonly_enable": "This channel is now image-only.",
 | 
			
		||||
  "imageonly_disable": "This channel is no longer image-only.",
 | 
			
		||||
  "deleted_x_servers": "Deleted {0} servers."
 | 
			
		||||
  "deleted_x_servers": "Deleted {0} servers.",
 | 
			
		||||
  "curtr_gift": "Gift from {0} [{1}]",
 | 
			
		||||
  "curtr_award": "Awarded by bot owner {0} [{1}]",  
 | 
			
		||||
  "curtr_take": "Taken by bot owner {0} [{1}]"  
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user