Compare commits
No commits in common. "v5" and "5.1.18" have entirely different histories.
69 changed files with 5195 additions and 20467 deletions
45
CHANGELOG.md
45
CHANGELOG.md
|
@ -2,51 +2,6 @@
|
||||||
|
|
||||||
Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except date format. a-c-f-r-o
|
Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except date format. a-c-f-r-o
|
||||||
|
|
||||||
## [5.1.20] - 13.11.2024
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Added `.rakeback` command, get a % of house edge back as claimable currency
|
|
||||||
- Added `.snipe` command to quickly get a copy of a posted message as an embed
|
|
||||||
- You can reply to a message to snipe that message
|
|
||||||
- Or just type .snipe and the bot will snipe the last message in the channel with content or image
|
|
||||||
- Added `.betstatsreset` / `.bsreset` command to reset your stats for a fee
|
|
||||||
- Added `.gamblestatsreset` / `.gsreset` owner-only command to reset bot stats for all games
|
|
||||||
- Added `.waifuclaims` command which lists all of your claimed waifus
|
|
||||||
- Added and changed `%bot.time%` and `%bot.date%` placeholders. They use timestamp tags now
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- `.divorce` no longer has a cooldown
|
|
||||||
- `.betroll` has a 2% better payout
|
|
||||||
- `.slot` payout balanced out (less volatile), reduced jackpot win but increased other wins,
|
|
||||||
- now has a new symbol, wheat
|
|
||||||
- worse around 1% in total (now shares the top spot with .bf)
|
|
||||||
|
|
||||||
## [5.1.19] - 05.11.2024
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Added `.betstats`
|
|
||||||
- See your own stats with .betstats
|
|
||||||
- Target someone else: .betstats @mai_lanfiel
|
|
||||||
- You can also specify a game .betstats lula
|
|
||||||
- Or both! .betstats mai_lanfiel br
|
|
||||||
- `.timely` can now have a server boost bonus
|
|
||||||
- Configure server ids and reward amount in data/gambling.yml
|
|
||||||
- anyone who boosts one of the sepcified servers gets the amount as base timely bonus
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- `.plant/pick` password font size will be slightly bigger
|
|
||||||
- `.race` will now have 82-94% payout rate based on the number of players playing (1-12, x0.01 per player).
|
|
||||||
- Any player over 12 won't increase payout
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- `.xplb` and `.xpglb` now have proper ranks after page 1
|
|
||||||
- Fixed boost bonus on shards different than the specified servers' shard
|
|
||||||
|
|
||||||
## [5.1.18] - 04.11.2024
|
## [5.1.18] - 04.11.2024
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -11,7 +11,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||||
Dockerfile = Dockerfile
|
Dockerfile = Dockerfile
|
||||||
ellie-menu.ps1 = ellie-menu.ps1
|
ellie-menu.ps1 = ellie-menu.ps1
|
||||||
LICENSE = LICENSE
|
LICENSE = LICENSE
|
||||||
|
migrate.ps1 = migrate.ps1
|
||||||
README.md = README.md
|
README.md = README.md
|
||||||
|
remove-migrations.ps1 = remove-migrations.ps1
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EllieBot", "src\EllieBot\EllieBot.csproj", "{4D9001F7-B3E8-48FE-97AA-CFD36DA65A64}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EllieBot", "src\EllieBot\EllieBot.csproj", "{4D9001F7-B3E8-48FE-97AA-CFD36DA65A64}"
|
||||||
|
@ -28,7 +30,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ellie.Marmalade", "src\Elli
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EllieBot.Voice", "src\EllieBot.Voice\EllieBot.Voice.csproj", "{1D93CE3C-80B4-49C7-A9A2-99988920AAEC}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EllieBot.Voice", "src\EllieBot.Voice\EllieBot.Voice.csproj", "{1D93CE3C-80B4-49C7-A9A2-99988920AAEC}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EllieBot.GrpcApiBase", "src\EllieBot.GrpcApiBase\EllieBot.GrpcApiBase.csproj", "{3B71F0BF-AE6C-480C-AB88-FCE23EDC7D91}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EllieBot.GrpcApiBase", "src\EllieBot.GrpcApiBase\EllieBot.GrpcApiBase.csproj", "{3B71F0BF-AE6C-480C-AB88-FCE23EDC7D91}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
|
8
migrate.ps1
Normal file
8
migrate.ps1
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
if ($args.Length -eq 0) {
|
||||||
|
Write-Host "Please provide a migration name." -ForegroundColor Red
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$migrationName = $args[0]
|
||||||
|
dotnet ef migrations add $migrationName -o Migrations/Mysql -c SqliteContext -p src/EllieBot/EllieBot.csproj
|
||||||
|
dotnet ef migrations add $migrationName -o Migrations/PostgreSql -c PostgreSqlContext -p src/EllieBot/EllieBot.csproj
|
||||||
|
}
|
|
@ -15,17 +15,6 @@ service GrpcXp {
|
||||||
|
|
||||||
rpc AddReward(AddRewardRequest) returns (AddRewardReply);
|
rpc AddReward(AddRewardRequest) returns (AddRewardReply);
|
||||||
rpc DeleteReward(DeleteRewardRequest) returns (DeleteRewardReply);
|
rpc DeleteReward(DeleteRewardRequest) returns (DeleteRewardReply);
|
||||||
|
|
||||||
rpc SetServerExclusion(SetServerExclusionRequest) returns (SetServerExclusionReply);
|
|
||||||
}
|
|
||||||
|
|
||||||
message SetServerExclusionRequest {
|
|
||||||
uint64 guildId = 1;
|
|
||||||
bool serverExcluded = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SetServerExclusionReply {
|
|
||||||
bool success = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetXpLbRequest {
|
message GetXpLbRequest {
|
||||||
|
@ -43,8 +32,7 @@ message XpLbUserReply {
|
||||||
string username = 2;
|
string username = 2;
|
||||||
int64 xp = 3;
|
int64 xp = 3;
|
||||||
int64 level = 4;
|
int64 level = 4;
|
||||||
int64 levelPercent = 5;
|
string avatar = 5;
|
||||||
string avatar = 6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message ResetUserXpRequest {
|
message ResetUserXpRequest {
|
||||||
|
|
|
@ -62,7 +62,6 @@ public abstract class EllieContext : DbContext
|
||||||
public DbSet<ArchivedTodoListModel> TodosArchive { get; set; }
|
public DbSet<ArchivedTodoListModel> TodosArchive { get; set; }
|
||||||
public DbSet<HoneypotChannel> HoneyPotChannels { get; set; }
|
public DbSet<HoneypotChannel> HoneyPotChannels { get; set; }
|
||||||
|
|
||||||
|
|
||||||
// public DbSet<GuildColors> GuildColors { get; set; }
|
// public DbSet<GuildColors> GuildColors { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
@ -74,23 +73,7 @@ public abstract class EllieContext : DbContext
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
#region Rakeback
|
#region Flag Translate
|
||||||
|
|
||||||
modelBuilder.Entity<Rakeback>()
|
|
||||||
.HasKey(x => x.UserId);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region UserBetStats
|
|
||||||
|
|
||||||
modelBuilder.Entity<UserBetStats>()
|
|
||||||
.HasIndex(x => new { x.UserId, x.Game })
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Flag Translate
|
|
||||||
|
|
||||||
modelBuilder.Entity<FlagTranslateChannel>()
|
modelBuilder.Entity<FlagTranslateChannel>()
|
||||||
.HasIndex(x => new { x.GuildId, x.ChannelId })
|
.HasIndex(x => new { x.GuildId, x.ChannelId })
|
||||||
|
@ -102,12 +85,12 @@ public abstract class EllieContext : DbContext
|
||||||
|
|
||||||
modelBuilder.Entity<NCPixel>()
|
modelBuilder.Entity<NCPixel>()
|
||||||
.HasAlternateKey(x => x.Position);
|
.HasAlternateKey(x => x.Position);
|
||||||
|
|
||||||
modelBuilder.Entity<NCPixel>()
|
modelBuilder.Entity<NCPixel>()
|
||||||
.HasIndex(x => x.OwnerId);
|
.HasIndex(x => x.OwnerId);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region QUOTES
|
#region QUOTES
|
||||||
|
|
||||||
var quoteEntity = modelBuilder.Entity<Quote>();
|
var quoteEntity = modelBuilder.Entity<Quote>();
|
||||||
|
@ -324,10 +307,10 @@ public abstract class EllieContext : DbContext
|
||||||
var selfassignableRolesEntity = modelBuilder.Entity<SelfAssignedRole>();
|
var selfassignableRolesEntity = modelBuilder.Entity<SelfAssignedRole>();
|
||||||
|
|
||||||
selfassignableRolesEntity.HasIndex(s => new
|
selfassignableRolesEntity.HasIndex(s => new
|
||||||
{
|
{
|
||||||
s.GuildId,
|
s.GuildId,
|
||||||
s.RoleId
|
s.RoleId
|
||||||
})
|
})
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
selfassignableRolesEntity.Property(x => x.Group).HasDefaultValue(0);
|
selfassignableRolesEntity.Property(x => x.Group).HasDefaultValue(0);
|
||||||
|
@ -401,10 +384,10 @@ public abstract class EllieContext : DbContext
|
||||||
|
|
||||||
var xps = modelBuilder.Entity<UserXpStats>();
|
var xps = modelBuilder.Entity<UserXpStats>();
|
||||||
xps.HasIndex(x => new
|
xps.HasIndex(x => new
|
||||||
{
|
{
|
||||||
x.UserId,
|
x.UserId,
|
||||||
x.GuildId
|
x.GuildId
|
||||||
})
|
})
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
xps.HasIndex(x => x.UserId);
|
xps.HasIndex(x => x.UserId);
|
||||||
|
@ -450,9 +433,9 @@ public abstract class EllieContext : DbContext
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
ci.HasIndex(x => new
|
ci.HasIndex(x => new
|
||||||
{
|
{
|
||||||
x.Name
|
x.Name
|
||||||
})
|
})
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -571,10 +554,10 @@ public abstract class EllieContext : DbContext
|
||||||
.IsUnique(false);
|
.IsUnique(false);
|
||||||
|
|
||||||
rr2.HasIndex(x => new
|
rr2.HasIndex(x => new
|
||||||
{
|
{
|
||||||
x.MessageId,
|
x.MessageId,
|
||||||
x.Emote
|
x.Emote
|
||||||
})
|
})
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -649,11 +632,11 @@ public abstract class EllieContext : DbContext
|
||||||
{
|
{
|
||||||
// user can own only one of each item
|
// user can own only one of each item
|
||||||
x.HasIndex(model => new
|
x.HasIndex(model => new
|
||||||
{
|
{
|
||||||
model.UserId,
|
model.UserId,
|
||||||
model.ItemType,
|
model.ItemType,
|
||||||
model.ItemKey
|
model.ItemKey
|
||||||
})
|
})
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -678,10 +661,10 @@ public abstract class EllieContext : DbContext
|
||||||
#region Sticky Roles
|
#region Sticky Roles
|
||||||
|
|
||||||
modelBuilder.Entity<StickyRole>(sr => sr.HasIndex(x => new
|
modelBuilder.Entity<StickyRole>(sr => sr.HasIndex(x => new
|
||||||
{
|
{
|
||||||
x.GuildId,
|
x.GuildId,
|
||||||
x.UserId
|
x.UserId
|
||||||
})
|
})
|
||||||
.IsUnique());
|
.IsUnique());
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -726,10 +709,10 @@ public abstract class EllieContext : DbContext
|
||||||
|
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.Entity<GreetSettings>(gs => gs.HasIndex(x => new
|
.Entity<GreetSettings>(gs => gs.HasIndex(x => new
|
||||||
{
|
{
|
||||||
x.GuildId,
|
x.GuildId,
|
||||||
x.GreetType
|
x.GreetType
|
||||||
})
|
})
|
||||||
.IsUnique());
|
.IsUnique());
|
||||||
|
|
||||||
modelBuilder.Entity<GreetSettings>(gs =>
|
modelBuilder.Entity<GreetSettings>(gs =>
|
||||||
|
@ -737,7 +720,7 @@ public abstract class EllieContext : DbContext
|
||||||
gs
|
gs
|
||||||
.Property(x => x.IsEnabled)
|
.Property(x => x.IsEnabled)
|
||||||
.HasDefaultValue(false);
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
gs
|
gs
|
||||||
.Property(x => x.AutoDeleteTimer)
|
.Property(x => x.AutoDeleteTimer)
|
||||||
.HasDefaultValue(0);
|
.HasDefaultValue(0);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Microsoft.EntityFrameworkCore;
|
#nullable disable
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
|
||||||
namespace EllieBot.Db;
|
namespace EllieBot.Db;
|
||||||
|
@ -16,8 +17,6 @@ public static class SelfAssignableRolesExtensions
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IReadOnlyCollection<SelfAssignedRole> GetFromGuild(
|
public static IReadOnlyCollection<SelfAssignedRole> GetFromGuild(this DbSet<SelfAssignedRole> roles, ulong guildId)
|
||||||
this DbSet<SelfAssignedRole> roles,
|
|
||||||
ulong guildId)
|
|
||||||
=> roles.AsQueryable().Where(s => s.GuildId == guildId).ToArray();
|
=> roles.AsQueryable().Where(s => s.GuildId == guildId).ToArray();
|
||||||
}
|
}
|
|
@ -1,12 +1,8 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
|
|
||||||
namespace EllieBot.Db.Models;
|
namespace EllieBot.Db.Models;
|
||||||
|
|
||||||
public class PatronUser
|
public class PatronUser
|
||||||
{
|
{
|
||||||
// [Key]
|
|
||||||
// public int Id { get; set; }
|
|
||||||
public string UniquePlatformUserId { get; set; }
|
public string UniquePlatformUserId { get; set; }
|
||||||
public ulong UserId { get; set; }
|
public ulong UserId { get; set; }
|
||||||
public int AmountCents { get; set; }
|
public int AmountCents { get; set; }
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>true</ImplicitUsings>
|
<ImplicitUsings>true</ImplicitUsings>
|
||||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||||
<Version>5.1.20</Version>
|
<Version>5.1.18</Version>
|
||||||
|
|
||||||
<!-- Output/build -->
|
<!-- Output/build -->
|
||||||
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
||||||
|
|
|
@ -7,7 +7,7 @@ public static class MigrationQueries
|
||||||
{
|
{
|
||||||
public static void UpdateUsernames(MigrationBuilder migrationBuilder)
|
public static void UpdateUsernames(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
migrationBuilder.Sql("UPDATE DiscordUser SET Username = '??' || Username WHERE Discriminator = '????';");
|
migrationBuilder.Sql("UPDATE DiscordUser SET Username = '??' + Username WHERE Discriminator = '????';");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MigrateRero(MigrationBuilder migrationBuilder)
|
public static void MigrateRero(MigrationBuilder migrationBuilder)
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,48 +0,0 @@
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace EllieBot.Migrations.PostgreSql
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class betstats : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "userbetstats",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
id = table.Column<int>(type: "integer", nullable: false)
|
|
||||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
|
||||||
userid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
|
||||||
game = table.Column<int>(type: "integer", nullable: false),
|
|
||||||
wincount = table.Column<long>(type: "bigint", nullable: false),
|
|
||||||
losecount = table.Column<long>(type: "bigint", nullable: false),
|
|
||||||
totalbet = table.Column<decimal>(type: "numeric", nullable: false),
|
|
||||||
paidout = table.Column<decimal>(type: "numeric", nullable: false),
|
|
||||||
maxwin = table.Column<long>(type: "bigint", nullable: false),
|
|
||||||
maxbet = table.Column<long>(type: "bigint", nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("pk_userbetstats", x => x.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "ix_userbetstats_userid_game",
|
|
||||||
table: "userbetstats",
|
|
||||||
columns: new[] { "userid", "game" },
|
|
||||||
unique: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "userbetstats");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,33 +0,0 @@
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace EllieBot.Migrations.PostgreSql
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class rakeback : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "rakeback",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
userid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
|
||||||
amount = table.Column<decimal>(type: "numeric", nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("pk_rakeback", x => x.userid);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "rakeback");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,47 +0,0 @@
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace EllieBot.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class betstats : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "UserBetStats",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
|
||||||
.Annotation("Sqlite:Autoincrement", true),
|
|
||||||
UserId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
|
||||||
Game = table.Column<int>(type: "INTEGER", nullable: false),
|
|
||||||
WinCount = table.Column<long>(type: "INTEGER", nullable: false),
|
|
||||||
LoseCount = table.Column<long>(type: "INTEGER", nullable: false),
|
|
||||||
TotalBet = table.Column<decimal>(type: "TEXT", nullable: false),
|
|
||||||
PaidOut = table.Column<decimal>(type: "TEXT", nullable: false),
|
|
||||||
MaxWin = table.Column<long>(type: "INTEGER", nullable: false),
|
|
||||||
MaxBet = table.Column<long>(type: "INTEGER", nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_UserBetStats", x => x.Id);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_UserBetStats_UserId_Game",
|
|
||||||
table: "UserBetStats",
|
|
||||||
columns: new[] { "UserId", "Game" },
|
|
||||||
unique: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "UserBetStats");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,34 +0,0 @@
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace EllieBot.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class rakeback : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "Rakeback",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
UserId = table.Column<ulong>(type: "INTEGER", nullable: false)
|
|
||||||
.Annotation("Sqlite:Autoincrement", true),
|
|
||||||
Amount = table.Column<decimal>(type: "TEXT", nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_Rakeback", x => x.UserId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "Rakeback");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
|
@ -71,6 +71,7 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, IEService
|
||||||
if (server.OwnerId != _client.CurrentUser.Id)
|
if (server.OwnerId != _client.CurrentUser.Id)
|
||||||
{
|
{
|
||||||
await server.LeaveAsync();
|
await server.LeaveAsync();
|
||||||
|
Log.Information("Left server {Name} [{Id}]", server.Name, server.Id);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,10 +6,6 @@ namespace EllieBot.Modules.Gambling.Common.AnimalRacing;
|
||||||
|
|
||||||
public sealed class AnimalRace : IDisposable
|
public sealed class AnimalRace : IDisposable
|
||||||
{
|
{
|
||||||
public const double BASE_MULTIPLIER = 0.82;
|
|
||||||
public const double MAX_MULTIPLIER = 0.94;
|
|
||||||
public const double MULTI_PER_USER = 0.01;
|
|
||||||
|
|
||||||
public enum Phase
|
public enum Phase
|
||||||
{
|
{
|
||||||
WaitingForPlayers,
|
WaitingForPlayers,
|
||||||
|
@ -104,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, (long)(user.Bet + BASE_MULTIPLIER), new("animalrace", "refund"));
|
await _currency.AddAsync(user.UserId, user.Bet, new("animalrace", "refund"));
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = OnStartingFailed?.Invoke(this);
|
_ = OnStartingFailed?.Invoke(this);
|
||||||
|
@ -135,10 +131,8 @@ public sealed class AnimalRace : IDisposable
|
||||||
|
|
||||||
if (FinishedUsers[0].Bet > 0)
|
if (FinishedUsers[0].Bet > 0)
|
||||||
{
|
{
|
||||||
Multi = FinishedUsers.Count
|
|
||||||
* Math.Min(MAX_MULTIPLIER, BASE_MULTIPLIER + (MULTI_PER_USER * FinishedUsers.Count));
|
|
||||||
await _currency.AddAsync(FinishedUsers[0].UserId,
|
await _currency.AddAsync(FinishedUsers[0].UserId,
|
||||||
(long)(FinishedUsers[0].Bet * Multi),
|
FinishedUsers[0].Bet * (_users.Count - 1),
|
||||||
new("animalrace", "win"));
|
new("animalrace", "win"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,8 +140,6 @@ public sealed class AnimalRace : IDisposable
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public double Multi { get; set; } = BASE_MULTIPLIER;
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
CurrentPhase = Phase.Ended;
|
CurrentPhase = Phase.Ended;
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class AnimalRacingCommands : GamblingModule<AnimalRaceService>
|
public partial class AnimalRacingCommands : GamblingSubmodule<AnimalRaceService>
|
||||||
{
|
{
|
||||||
private readonly ICurrencyService _cs;
|
private readonly ICurrencyService _cs;
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
|
@ -74,14 +74,10 @@ public partial class Gambling
|
||||||
if (race.FinishedUsers[0].Bet > 0)
|
if (race.FinishedUsers[0].Bet > 0)
|
||||||
{
|
{
|
||||||
return Response()
|
return Response()
|
||||||
.Embed(_sender.CreateEmbed()
|
.Confirm(GetText(strs.animal_race),
|
||||||
.WithOkColor()
|
GetText(strs.animal_race_won_money(Format.Bold(winner.Username),
|
||||||
.WithTitle(GetText(strs.animal_race))
|
winner.Animal.Icon,
|
||||||
.WithDescription(GetText(strs.animal_race_won_money(
|
(race.FinishedUsers[0].Bet * (race.Users.Count - 1)) + CurrencySign)))
|
||||||
Format.Bold(winner.Username),
|
|
||||||
winner.Animal.Icon,
|
|
||||||
N(race.FinishedUsers[0].Bet * race.Multi))))
|
|
||||||
.WithFooter($"x{race.Multi:F2}"))
|
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,14 +113,14 @@ public partial class Gambling
|
||||||
|
|
||||||
private async Task Ar_OnStateUpdate(AnimalRace race)
|
private async Task Ar_OnStateUpdate(AnimalRace race)
|
||||||
{
|
{
|
||||||
var text = $@"|🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁🔚|
|
var text = $@"|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|
|
||||||
{string.Join("\n", race.Users.Select(p =>
|
{string.Join("\n", race.Users.Select(p =>
|
||||||
{
|
{
|
||||||
var index = race.FinishedUsers.IndexOf(p);
|
var index = race.FinishedUsers.IndexOf(p);
|
||||||
var extra = index == -1 ? "" : $"#{index + 1} {(index == 0 ? "🏆" : "")}";
|
var extra = index == -1 ? "" : $"#{index + 1} {(index == 0 ? "🏆" : "")}";
|
||||||
return $"{(int)(p.Progress / 60f * 100),-2}%|{new string('‣', p.Progress) + p.Animal.Icon + extra}";
|
return $"{(int)(p.Progress / 60f * 100),-2}%|{new string('‣', p.Progress) + p.Animal.Icon + extra}";
|
||||||
}))}
|
}))}
|
||||||
|🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁🔚|";
|
|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|";
|
||||||
|
|
||||||
var msg = raceMessage;
|
var msg = raceMessage;
|
||||||
|
|
||||||
|
@ -133,10 +129,10 @@ public partial class Gambling
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await msg.ModifyAsync(x => x.Embed = _sender.CreateEmbed()
|
await msg.ModifyAsync(x => x.Embed = _sender.CreateEmbed()
|
||||||
.WithTitle(GetText(strs.animal_race))
|
.WithTitle(GetText(strs.animal_race))
|
||||||
.WithDescription(text)
|
.WithDescription(text)
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.Build());
|
.Build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,175 +0,0 @@
|
||||||
#nullable disable
|
|
||||||
using EllieBot.Modules.Gambling.Common;
|
|
||||||
using EllieBot.Modules.Gambling.Services;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Gambling;
|
|
||||||
|
|
||||||
public partial class Gambling
|
|
||||||
{
|
|
||||||
[Group]
|
|
||||||
public sealed class BetStatsCommands : GamblingModule<UserBetStatsService>
|
|
||||||
{
|
|
||||||
private readonly GamblingTxTracker _gamblingTxTracker;
|
|
||||||
|
|
||||||
public BetStatsCommands(
|
|
||||||
GamblingTxTracker gamblingTxTracker,
|
|
||||||
GamblingConfigService gcs)
|
|
||||||
: base(gcs)
|
|
||||||
{
|
|
||||||
_gamblingTxTracker = gamblingTxTracker;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Cmd]
|
|
||||||
public async Task BetStatsReset(GamblingGame? game = null)
|
|
||||||
{
|
|
||||||
var price = await _service.GetResetStatsPriceAsync(ctx.User.Id, game);
|
|
||||||
|
|
||||||
var result = await PromptUserConfirmAsync(_sender.CreateEmbed()
|
|
||||||
.WithDescription(
|
|
||||||
$"""
|
|
||||||
Are you sure you want to reset your bet stats for **{GetGameName(game)}**?
|
|
||||||
|
|
||||||
It will cost you {N(price)}
|
|
||||||
"""));
|
|
||||||
|
|
||||||
if (!result)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var success = await _service.ResetStatsAsync(ctx.User.Id, game);
|
|
||||||
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
await ctx.OkAsync();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await Response()
|
|
||||||
.Error(strs.not_enough(CurrencySign))
|
|
||||||
.SendAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetGameName(GamblingGame? game)
|
|
||||||
{
|
|
||||||
if (game is null)
|
|
||||||
return "all games";
|
|
||||||
|
|
||||||
return game.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Cmd]
|
|
||||||
[Priority(3)]
|
|
||||||
public async Task BetStats()
|
|
||||||
=> await BetStats(ctx.User, null);
|
|
||||||
|
|
||||||
[Cmd]
|
|
||||||
[Priority(2)]
|
|
||||||
public async Task BetStats(GamblingGame game)
|
|
||||||
=> await BetStats(ctx.User, game);
|
|
||||||
|
|
||||||
[Cmd]
|
|
||||||
[Priority(1)]
|
|
||||||
public async Task BetStats([Leftover] IUser user)
|
|
||||||
=> await BetStats(user, null);
|
|
||||||
|
|
||||||
[Cmd]
|
|
||||||
[Priority(0)]
|
|
||||||
public async Task BetStats(IUser user, GamblingGame? game)
|
|
||||||
{
|
|
||||||
var stats = await _gamblingTxTracker.GetUserStatsAsync(user.Id, game);
|
|
||||||
|
|
||||||
if (stats.Count == 0)
|
|
||||||
stats = new()
|
|
||||||
{
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
TotalBet = 1
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var eb = _sender.CreateEmbed()
|
|
||||||
.WithOkColor()
|
|
||||||
.WithAuthor(user)
|
|
||||||
.AddField("Total Won", N(stats.Sum(x => x.PaidOut)), true)
|
|
||||||
.AddField("Biggest Win", N(stats.Max(x => x.MaxWin)), true)
|
|
||||||
.AddField("Biggest Bet", N(stats.Max(x => x.MaxBet)), true)
|
|
||||||
.AddField("# Bets", stats.Sum(x => x.WinCount + x.LoseCount), true)
|
|
||||||
.AddField("Payout",
|
|
||||||
(stats.Sum(x => x.PaidOut) / stats.Sum(x => x.TotalBet)).ToString("P2", Culture),
|
|
||||||
true);
|
|
||||||
if (game == null)
|
|
||||||
{
|
|
||||||
var favGame = stats.MaxBy(x => x.WinCount + x.LoseCount);
|
|
||||||
eb.AddField("Favorite Game",
|
|
||||||
favGame.Game + "\n" + Format.Italics((favGame.WinCount + favGame.LoseCount) + " plays"),
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
eb.WithDescription(game.ToString())
|
|
||||||
.AddField("# Wins", stats.Sum(x => x.WinCount), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
await Response()
|
|
||||||
.Embed(eb)
|
|
||||||
.SendAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Cmd]
|
|
||||||
public async Task GambleStats()
|
|
||||||
{
|
|
||||||
var stats = await _gamblingTxTracker.GetAllAsync();
|
|
||||||
|
|
||||||
var eb = _sender.CreateEmbed()
|
|
||||||
.WithOkColor();
|
|
||||||
|
|
||||||
var str = "` Feature `|` Bet `|`Paid Out`|` RoI `\n";
|
|
||||||
str += "――――――――――――――――――――\n";
|
|
||||||
foreach (var stat in stats)
|
|
||||||
{
|
|
||||||
var perc = (stat.PaidOut / stat.Bet).ToString("P2", Culture);
|
|
||||||
str += $"`{stat.Feature.PadBoth(9)}`"
|
|
||||||
+ $"|`{stat.Bet.ToString("N0").PadLeft(8, ' ')}`"
|
|
||||||
+ $"|`{stat.PaidOut.ToString("N0").PadLeft(8, ' ')}`"
|
|
||||||
+ $"|`{perc.PadLeft(6, ' ')}`\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
var bet = stats.Sum(x => x.Bet);
|
|
||||||
var paidOut = stats.Sum(x => x.PaidOut);
|
|
||||||
|
|
||||||
if (bet == 0)
|
|
||||||
bet = 1;
|
|
||||||
|
|
||||||
var tPerc = (paidOut / bet).ToString("P2", Culture);
|
|
||||||
str += "――――――――――――――――――――\n";
|
|
||||||
str += $"` {("TOTAL").PadBoth(7)}` "
|
|
||||||
+ $"|**{N(bet).PadLeft(8, ' ')}**"
|
|
||||||
+ $"|**{N(paidOut).PadLeft(8, ' ')}**"
|
|
||||||
+ $"|`{tPerc.PadLeft(6, ' ')}`";
|
|
||||||
|
|
||||||
eb.WithDescription(str);
|
|
||||||
|
|
||||||
await Response().Embed(eb).SendAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Cmd]
|
|
||||||
[OwnerOnly]
|
|
||||||
public async Task GambleStatsReset()
|
|
||||||
{
|
|
||||||
if (!await PromptUserConfirmAsync(_sender.CreateEmbed()
|
|
||||||
.WithDescription(
|
|
||||||
"""
|
|
||||||
Are you sure?
|
|
||||||
This will completely reset Gambling Stats.
|
|
||||||
|
|
||||||
This action is irreversible.
|
|
||||||
""")))
|
|
||||||
return;
|
|
||||||
|
|
||||||
await GambleStats();
|
|
||||||
await _service.ResetGamblingStatsAsync();
|
|
||||||
|
|
||||||
await ctx.OkAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,7 +8,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
|
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
public partial class BlackJackCommands : GamblingModule<BlackJackService>
|
public partial class BlackJackCommands : GamblingSubmodule<BlackJackService>
|
||||||
{
|
{
|
||||||
public enum BjAction
|
public enum BjAction
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class Connect4Commands : GamblingModule<GamblingService>
|
public partial class Connect4Commands : GamblingSubmodule<GamblingService>
|
||||||
{
|
{
|
||||||
private static readonly string[] _numbers =
|
private static readonly string[] _numbers =
|
||||||
[
|
[
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class DrawCommands : GamblingModule<IGamblingService>
|
public partial class DrawCommands : GamblingSubmodule<IGamblingService>
|
||||||
{
|
{
|
||||||
private static readonly ConcurrentDictionary<IGuild, Deck> _allDecks = new();
|
private static readonly ConcurrentDictionary<IGuild, Deck> _allDecks = new();
|
||||||
private readonly IImageCache _images;
|
private readonly IImageCache _images;
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class CurrencyEventsCommands : GamblingModule<CurrencyEventsService>
|
public partial class CurrencyEventsCommands : GamblingSubmodule<CurrencyEventsService>
|
||||||
{
|
{
|
||||||
public CurrencyEventsCommands(GamblingConfigService gamblingConf)
|
public CurrencyEventsCommands(GamblingConfigService gamblingConf)
|
||||||
: base(gamblingConf)
|
: base(gamblingConf)
|
||||||
|
|
|
@ -11,7 +11,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class FlipCoinCommands : GamblingModule<IGamblingService>
|
public partial class FlipCoinCommands : GamblingSubmodule<IGamblingService>
|
||||||
{
|
{
|
||||||
public enum BetFlipGuess : byte
|
public enum BetFlipGuess : byte
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,13 +14,6 @@ using System.Text;
|
||||||
using EllieBot.Modules.Gambling.Rps;
|
using EllieBot.Modules.Gambling.Rps;
|
||||||
using EllieBot.Common.TypeReaders;
|
using EllieBot.Common.TypeReaders;
|
||||||
using EllieBot.Modules.Patronage;
|
using EllieBot.Modules.Patronage;
|
||||||
using SixLabors.Fonts;
|
|
||||||
using SixLabors.Fonts.Unicode;
|
|
||||||
using SixLabors.ImageSharp;
|
|
||||||
using SixLabors.ImageSharp.Drawing.Processing;
|
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
|
||||||
using SixLabors.ImageSharp.Processing;
|
|
||||||
using Color = SixLabors.ImageSharp.Color;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Gambling;
|
namespace EllieBot.Modules.Gambling;
|
||||||
|
|
||||||
|
@ -38,7 +31,6 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
private readonly IRemindService _remind;
|
private readonly IRemindService _remind;
|
||||||
private readonly GamblingTxTracker _gamblingTxTracker;
|
private readonly GamblingTxTracker _gamblingTxTracker;
|
||||||
private readonly IPatronageService _ps;
|
private readonly IPatronageService _ps;
|
||||||
private readonly RakebackService _rb;
|
|
||||||
|
|
||||||
public Gambling(
|
public Gambling(
|
||||||
IGamblingService gs,
|
IGamblingService gs,
|
||||||
|
@ -51,8 +43,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
IBankService bank,
|
IBankService bank,
|
||||||
IRemindService remind,
|
IRemindService remind,
|
||||||
IPatronageService patronage,
|
IPatronageService patronage,
|
||||||
GamblingTxTracker gamblingTxTracker,
|
GamblingTxTracker gamblingTxTracker)
|
||||||
RakebackService rb)
|
|
||||||
: base(configService)
|
: base(configService)
|
||||||
{
|
{
|
||||||
_gs = gs;
|
_gs = gs;
|
||||||
|
@ -62,7 +53,6 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
_bank = bank;
|
_bank = bank;
|
||||||
_remind = remind;
|
_remind = remind;
|
||||||
_gamblingTxTracker = gamblingTxTracker;
|
_gamblingTxTracker = gamblingTxTracker;
|
||||||
_rb = rb;
|
|
||||||
_ps = patronage;
|
_ps = patronage;
|
||||||
_rng = new EllieRandom();
|
_rng = new EllieRandom();
|
||||||
|
|
||||||
|
@ -80,6 +70,42 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
return N(bal);
|
return N(bal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task BetStats()
|
||||||
|
{
|
||||||
|
var stats = await _gamblingTxTracker.GetAllAsync();
|
||||||
|
|
||||||
|
var eb = _sender.CreateEmbed()
|
||||||
|
.WithOkColor();
|
||||||
|
|
||||||
|
var str = "` Feature `|` Bet `|`Paid Out`|` RoI `\n";
|
||||||
|
str += "――――――――――――――――――――\n";
|
||||||
|
foreach (var stat in stats)
|
||||||
|
{
|
||||||
|
var perc = (stat.PaidOut / stat.Bet).ToString("P2", Culture);
|
||||||
|
str += $"`{stat.Feature.PadBoth(9)}`"
|
||||||
|
+ $"|`{stat.Bet.ToString("N0").PadLeft(8, ' ')}`"
|
||||||
|
+ $"|`{stat.PaidOut.ToString("N0").PadLeft(8, ' ')}`"
|
||||||
|
+ $"|`{perc.PadLeft(6, ' ')}`\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
var bet = stats.Sum(x => x.Bet);
|
||||||
|
var paidOut = stats.Sum(x => x.PaidOut);
|
||||||
|
|
||||||
|
if (bet == 0)
|
||||||
|
bet = 1;
|
||||||
|
|
||||||
|
var tPerc = (paidOut / bet).ToString("P2", Culture);
|
||||||
|
str += "――――――――――――――――――――\n";
|
||||||
|
str += $"` {("TOTAL").PadBoth(7)}` "
|
||||||
|
+ $"|**{N(bet).PadLeft(8, ' ')}**"
|
||||||
|
+ $"|**{N(paidOut).PadLeft(8, ' ')}**"
|
||||||
|
+ $"|`{tPerc.PadLeft(6, ' ')}`";
|
||||||
|
|
||||||
|
eb.WithDescription(str);
|
||||||
|
|
||||||
|
await Response().Embed(eb).SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task RemindTimelyAction(SocketMessageComponent smc, DateTime when)
|
private async Task RemindTimelyAction(SocketMessageComponent smc, DateTime when)
|
||||||
{
|
{
|
||||||
|
@ -127,12 +153,10 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
customId: "timely:" + _rng.Next(123456, 999999)),
|
customId: "timely:" + _rng.Next(123456, 999999)),
|
||||||
async (smc) =>
|
async (smc) =>
|
||||||
{
|
{
|
||||||
await smc.DeferAsync();
|
|
||||||
await ClaimTimely();
|
await ClaimTimely();
|
||||||
});
|
});
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
|
||||||
public async Task Timely()
|
public async Task Timely()
|
||||||
{
|
{
|
||||||
var val = Config.Timely.Amount;
|
var val = Config.Timely.Amount;
|
||||||
|
@ -143,64 +167,62 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Config.Timely.ProtType == TimelyProt.Button)
|
if (Config.Timely.RequirePassword)
|
||||||
{
|
{
|
||||||
|
// var password = _service.GeneratePassword();
|
||||||
|
//
|
||||||
|
// var img = new Image<Rgba32>(100, 40);
|
||||||
|
//
|
||||||
|
// var font = _fonts.NotoSans.CreateFont(30);
|
||||||
|
// var outlinePen = new SolidPen(Color.Black, 1f);
|
||||||
|
// var strikeoutRun = new RichTextRun
|
||||||
|
// {
|
||||||
|
// Start = 0,
|
||||||
|
// End = password.GetGraphemeCount(),
|
||||||
|
// Font = font,
|
||||||
|
// StrikeoutPen = new SolidPen(Color.White, 3),
|
||||||
|
// TextDecorations = TextDecorations.Strikeout
|
||||||
|
// };
|
||||||
|
// // draw password on the image
|
||||||
|
// img.Mutate(x =>
|
||||||
|
// {
|
||||||
|
// x.DrawText(new RichTextOptions(font)
|
||||||
|
// {
|
||||||
|
// HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
// VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
// FallbackFontFamilies = _fonts.FallBackFonts,
|
||||||
|
// Origin = new(50, 20),
|
||||||
|
// TextRuns = [strikeoutRun]
|
||||||
|
// },
|
||||||
|
// password,
|
||||||
|
// Brushes.Solid(Color.White),
|
||||||
|
// outlinePen);
|
||||||
|
// });
|
||||||
|
// using var stream = await img.ToStreamAsync();
|
||||||
|
// var captcha = await Response()
|
||||||
|
// .Embed(_sender.CreateEmbed()
|
||||||
|
// .WithOkColor()
|
||||||
|
// .WithImageUrl("attachment://timely.png"))
|
||||||
|
// .File(stream, "timely.png")
|
||||||
|
// .SendAsync();
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// var userInput = await GetUserInputAsync(ctx.User.Id, ctx.Channel.Id);
|
||||||
|
// if (userInput?.ToLowerInvariant() != password?.ToLowerInvariant())
|
||||||
|
// {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// finally
|
||||||
|
// {
|
||||||
|
// _ = captcha.DeleteAsync();
|
||||||
|
// }
|
||||||
|
|
||||||
var interaction = CreateTimelyInteraction();
|
var interaction = CreateTimelyInteraction();
|
||||||
var msg = await Response().Pending(strs.timely_button).Interaction(interaction).SendAsync();
|
var msg = await Response().Pending(strs.timely_button).Interaction(interaction).SendAsync();
|
||||||
await msg.DeleteAsync();
|
await msg.DeleteAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (Config.Timely.ProtType == TimelyProt.Captcha)
|
|
||||||
{
|
|
||||||
var password = _service.GeneratePassword();
|
|
||||||
|
|
||||||
var img = new Image<Rgba32>(70, 35);
|
|
||||||
|
|
||||||
var font = _fonts.NotoSans.CreateFont(30);
|
|
||||||
var outlinePen = new SolidPen(Color.Black, 1f);
|
|
||||||
var strikeoutRun = new RichTextRun
|
|
||||||
{
|
|
||||||
Start = 0,
|
|
||||||
End = password.GetGraphemeCount(),
|
|
||||||
Font = font,
|
|
||||||
StrikeoutPen = new SolidPen(Color.White, 3),
|
|
||||||
TextDecorations = TextDecorations.Strikeout
|
|
||||||
};
|
|
||||||
// draw password on the image
|
|
||||||
img.Mutate(x =>
|
|
||||||
{
|
|
||||||
x.DrawText(new RichTextOptions(font)
|
|
||||||
{
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Center,
|
|
||||||
VerticalAlignment = VerticalAlignment.Center,
|
|
||||||
FallbackFontFamilies = _fonts.FallBackFonts,
|
|
||||||
Origin = new(35, 17),
|
|
||||||
TextRuns = [strikeoutRun]
|
|
||||||
},
|
|
||||||
password,
|
|
||||||
Brushes.Solid(Color.White),
|
|
||||||
outlinePen);
|
|
||||||
});
|
|
||||||
using var stream = await img.ToStreamAsync();
|
|
||||||
var captcha = await Response()
|
|
||||||
// .Embed(_sender.CreateEmbed()
|
|
||||||
// .WithOkColor()
|
|
||||||
// .WithImageUrl("attachment://timely.png"))
|
|
||||||
.File(stream, "timely.png")
|
|
||||||
.SendAsync();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var userInput = await GetUserInputAsync(ctx.User.Id, ctx.Channel.Id);
|
|
||||||
if (userInput?.ToLowerInvariant() != password?.ToLowerInvariant())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_ = captcha.DeleteAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await ClaimTimely();
|
await ClaimTimely();
|
||||||
}
|
}
|
||||||
|
@ -227,29 +249,6 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
|
|
||||||
|
|
||||||
var val = Config.Timely.Amount;
|
var val = Config.Timely.Amount;
|
||||||
var boostGuilds = Config.BoostBonus.GuildIds ?? new();
|
|
||||||
var guildUsers = await boostGuilds
|
|
||||||
.Select(async gid =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var guild = await _client.Rest.GetGuildAsync(gid, false);
|
|
||||||
var user = await _client.Rest.GetGuildUserAsync(gid, ctx.User.Id);
|
|
||||||
return (guild, user);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.WhenAll();
|
|
||||||
|
|
||||||
var userInfo = guildUsers.FirstOrDefault(x => x.user?.PremiumSince is not null);
|
|
||||||
var booster = userInfo != default;
|
|
||||||
|
|
||||||
if (booster)
|
|
||||||
val += Config.BoostBonus.BaseTimelyBonus;
|
|
||||||
|
|
||||||
var patron = await _ps.GetPatronAsync(ctx.User.Id);
|
var patron = await _ps.GetPatronAsync(ctx.User.Id);
|
||||||
|
|
||||||
var percentBonus = (_ps.PercentBonus(patron) / 100f);
|
var percentBonus = (_ps.PercentBonus(patron) / 100f);
|
||||||
|
@ -260,21 +259,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
|
|
||||||
await _cs.AddAsync(ctx.User.Id, val, new("timely", "claim"));
|
await _cs.AddAsync(ctx.User.Id, val, new("timely", "claim"));
|
||||||
|
|
||||||
var msg = GetText(strs.timely(N(val), period));
|
await Response().Confirm(strs.timely(N(val), period)).Interaction(inter).SendAsync();
|
||||||
if (booster || percentBonus > float.Epsilon)
|
|
||||||
{
|
|
||||||
msg += "\n\n";
|
|
||||||
if (booster)
|
|
||||||
msg += $"*+{N(Config.BoostBonus.BaseTimelyBonus)} bonus for boosting {userInfo.guild}!*\n";
|
|
||||||
|
|
||||||
if (percentBonus > float.Epsilon)
|
|
||||||
msg +=
|
|
||||||
$"*+{percentBonus:P0} bonus for the [Patreon](https://patreon.com/elliebot) pledge! <:hart:746995901758832712>*";
|
|
||||||
|
|
||||||
await Response().Confirm(msg).Interaction(inter).SendAsync();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
await Response().Confirm(strs.timely(N(val), period)).Interaction(inter).SendAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
|
@ -385,9 +370,8 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
}
|
}
|
||||||
|
|
||||||
var embed = _sender.CreateEmbed()
|
var embed = _sender.CreateEmbed()
|
||||||
.WithTitle(GetText(strs.transactions(
|
.WithTitle(GetText(strs.transactions(((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString()
|
||||||
((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString()
|
?? $"{userId}")))
|
||||||
?? $"{userId}")))
|
|
||||||
.WithOkColor();
|
.WithOkColor();
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
@ -643,9 +627,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await Response()
|
await Response().Error(strs.take_fail(N(amount), Format.Bold(user.ToString()), CurrencySign)).SendAsync();
|
||||||
.Error(strs.take_fail(N(amount), Format.Bold(user.ToString()), CurrencySign))
|
|
||||||
.SendAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -666,9 +648,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await Response()
|
await Response().Error(strs.take_fail(N(amount), Format.Code(usrId.ToString()), CurrencySign)).SendAsync();
|
||||||
.Error(strs.take_fail(N(amount), Format.Code(usrId.ToString()), CurrencySign))
|
|
||||||
.SendAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1002,45 +982,4 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
footer: $"Total Bet: {tests} | Payout: {payout:F0} | {payout * 1.0M / tests * 100}%")
|
footer: $"Total Bet: {tests} | Payout: {payout:F0} | {payout * 1.0M / tests * 100}%")
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private EllieInteractionBase CreateRakebackInteraction()
|
|
||||||
=> _inter.Create(ctx.User.Id,
|
|
||||||
new ButtonBuilder(
|
|
||||||
customId: "cash:rakeback",
|
|
||||||
emote: new Emoji("💸")),
|
|
||||||
RakebackAction);
|
|
||||||
|
|
||||||
private async Task RakebackAction(SocketMessageComponent arg)
|
|
||||||
{
|
|
||||||
var rb = await _rb.ClaimRakebackAsync(ctx.User.Id);
|
|
||||||
|
|
||||||
if (rb == 0)
|
|
||||||
{
|
|
||||||
await arg.DeferAsync();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await arg.RespondAsync(_sender, GetText(strs.rakeback_claimed(N(rb))), MsgType.Ok);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Cmd]
|
|
||||||
public async Task Rakeback()
|
|
||||||
{
|
|
||||||
var rb = await _rb.GetRakebackAsync(ctx.User.Id);
|
|
||||||
|
|
||||||
if (rb < 1)
|
|
||||||
{
|
|
||||||
await Response()
|
|
||||||
.Error(strs.rakeback_none)
|
|
||||||
.SendAsync();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var inter = CreateRakebackInteraction();
|
|
||||||
await Response()
|
|
||||||
.Pending(strs.rakeback_available(N(rb)))
|
|
||||||
.Interaction(inter)
|
|
||||||
.SendAsync();
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -11,7 +11,7 @@ namespace EllieBot.Modules.Gambling.Common;
|
||||||
public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
|
public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
|
||||||
{
|
{
|
||||||
[Comment("""DO NOT CHANGE""")]
|
[Comment("""DO NOT CHANGE""")]
|
||||||
public int Version { get; set; } = 12;
|
public int Version { get; set; } = 9;
|
||||||
|
|
||||||
[Comment("""Currency settings""")]
|
[Comment("""Currency settings""")]
|
||||||
public CurrencyConfig Currency { get; set; }
|
public CurrencyConfig Currency { get; set; }
|
||||||
|
@ -67,11 +67,6 @@ public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
|
||||||
[Comment("""Slot config""")]
|
[Comment("""Slot config""")]
|
||||||
public SlotsConfig Slots { get; set; }
|
public SlotsConfig Slots { get; set; }
|
||||||
|
|
||||||
[Comment("""
|
|
||||||
Bonus config for server boosts
|
|
||||||
""")]
|
|
||||||
public BoostBonusConfig BoostBonus { get; set; }
|
|
||||||
|
|
||||||
public GamblingConfig()
|
public GamblingConfig()
|
||||||
{
|
{
|
||||||
BetRoll = new();
|
BetRoll = new();
|
||||||
|
@ -84,7 +79,6 @@ public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
|
||||||
Slots = new();
|
Slots = new();
|
||||||
LuckyLadder = new();
|
LuckyLadder = new();
|
||||||
BotCuts = new();
|
BotCuts = new();
|
||||||
BoostBonus = new();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +104,7 @@ public partial class TimelyConfig
|
||||||
How much currency will the users get every time they run .timely command
|
How much currency will the users get every time they run .timely command
|
||||||
setting to 0 or less will disable this feature
|
setting to 0 or less will disable this feature
|
||||||
""")]
|
""")]
|
||||||
public long Amount { get; set; } = 0;
|
public int Amount { get; set; } = 0;
|
||||||
|
|
||||||
[Comment("""
|
[Comment("""
|
||||||
How often (in hours) can users claim currency with .timely command
|
How often (in hours) can users claim currency with .timely command
|
||||||
|
@ -119,17 +113,9 @@ public partial class TimelyConfig
|
||||||
public int Cooldown { get; set; } = 24;
|
public int Cooldown { get; set; } = 24;
|
||||||
|
|
||||||
[Comment("""
|
[Comment("""
|
||||||
How will timely be protected?
|
Whether the users are required to type a password when they do timely.
|
||||||
None, Button (users have to click the button) or Captcha (users have to type the captcha from an image)
|
|
||||||
""")]
|
""")]
|
||||||
public TimelyProt ProtType { get; set; } = TimelyProt.Button;
|
public bool RequirePassword { get; set; } = true;
|
||||||
}
|
|
||||||
|
|
||||||
public enum TimelyProt
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
Button,
|
|
||||||
Captcha
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cloneable]
|
[Cloneable]
|
||||||
|
@ -164,7 +150,7 @@ public partial class BetRollConfig
|
||||||
},
|
},
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
WhenAbove = 65,
|
WhenAbove = 66,
|
||||||
MultiplyBy = 2
|
MultiplyBy = 2
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
@ -226,7 +212,7 @@ public partial class LuckyLadderSettings
|
||||||
public decimal[] Multipliers { get; set; }
|
public decimal[] Multipliers { get; set; }
|
||||||
|
|
||||||
public LuckyLadderSettings()
|
public LuckyLadderSettings()
|
||||||
=> Multipliers = [2.4M, 1.7M, 1.5M, 1.1M, 0.5M, 0.3M, 0.2M, 0.1M];
|
=> Multipliers = [2.4M, 1.7M, 1.5M, 1.2M, 0.5M, 0.3M, 0.2M, 0.1M];
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cloneable]
|
[Cloneable]
|
||||||
|
@ -427,15 +413,4 @@ public sealed partial class BotCutConfig
|
||||||
Default 0.1 (10%).
|
Default 0.1 (10%).
|
||||||
""")]
|
""")]
|
||||||
public decimal ShopSaleCut { get; set; } = 0.1m;
|
public decimal ShopSaleCut { get; set; } = 0.1m;
|
||||||
}
|
|
||||||
|
|
||||||
[Cloneable]
|
|
||||||
public sealed partial class BoostBonusConfig
|
|
||||||
{
|
|
||||||
[Comment("Users will receive a bonus if they boost any of these servers")]
|
|
||||||
public List<ulong> GuildIds { get; set; } = new();
|
|
||||||
|
|
||||||
[Comment("This bonus will be added before any other multiplier is applied to the .timely command")]
|
|
||||||
|
|
||||||
public long BaseTimelyBonus { get; set; } = 50;
|
|
||||||
}
|
}
|
|
@ -144,9 +144,9 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
|
||||||
ConfigPrinters.ToString,
|
ConfigPrinters.ToString,
|
||||||
val => val >= 0);
|
val => val >= 0);
|
||||||
|
|
||||||
AddParsedProp("timely.prot",
|
AddParsedProp("timely.pass",
|
||||||
gs => gs.Timely.ProtType,
|
gs => gs.Timely.RequirePassword,
|
||||||
ConfigParsers.InsensitiveEnum,
|
bool.TryParse,
|
||||||
ConfigPrinters.ToString);
|
ConfigPrinters.ToString);
|
||||||
|
|
||||||
Migrate();
|
Migrate();
|
||||||
|
@ -189,16 +189,11 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.Version < 12)
|
if (data.Version < 9)
|
||||||
{
|
{
|
||||||
ModifyConfig(c =>
|
ModifyConfig(c =>
|
||||||
{
|
{
|
||||||
c.Version = 12;
|
c.Version = 9;
|
||||||
|
|
||||||
if (c.BetRoll.Pairs.Length == 3 && c.BetRoll.Pairs[2].WhenAbove == 66)
|
|
||||||
{
|
|
||||||
c.BetRoll.Pairs[2].WhenAbove = 65;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,4 +57,12 @@ public abstract class GamblingModule<TService> : EllieModule<TService>
|
||||||
return Task.FromResult(true);
|
return Task.FromResult(true);
|
||||||
return InternalCheckBet(amount);
|
return InternalCheckBet(amount);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class GamblingSubmodule<TService> : GamblingModule<TService>
|
||||||
|
{
|
||||||
|
protected GamblingSubmodule(GamblingConfigService gamblingConfService)
|
||||||
|
: base(gamblingConfService)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -8,7 +8,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class PlantPickCommands : GamblingModule<PlantPickService>
|
public partial class PlantPickCommands : GamblingSubmodule<PlantPickService>
|
||||||
{
|
{
|
||||||
private readonly ILogCommandService _logService;
|
private readonly ILogCommandService _logService;
|
||||||
|
|
||||||
|
|
|
@ -147,7 +147,7 @@ public class PlantPickService : IEService, IExecNoCommand
|
||||||
pass = pass.TrimTo(10, true).ToLowerInvariant();
|
pass = pass.TrimTo(10, true).ToLowerInvariant();
|
||||||
using var img = Image.Load<Rgba32>(curImg);
|
using var img = Image.Load<Rgba32>(curImg);
|
||||||
// choose font size based on the image height, so that it's visible
|
// choose font size based on the image height, so that it's visible
|
||||||
var font = _fonts.NotoSans.CreateFont(img.Height / 11.0f, FontStyle.Bold);
|
var font = _fonts.NotoSans.CreateFont(img.Height / 12.0f, FontStyle.Bold);
|
||||||
img.Mutate(x =>
|
img.Mutate(x =>
|
||||||
{
|
{
|
||||||
// measure the size of the text to be drawing
|
// measure the size of the text to be drawing
|
||||||
|
@ -159,7 +159,7 @@ public class PlantPickService : IEService, IExecNoCommand
|
||||||
|
|
||||||
// fill the background with black, add 5 pixels on each side to make it look better
|
// fill the background with black, add 5 pixels on each side to make it look better
|
||||||
x.FillPolygon(Color.ParseHex("00000080"),
|
x.FillPolygon(Color.ParseHex("00000080"),
|
||||||
new PointF(1, 1),
|
new PointF(0, 0),
|
||||||
new PointF(size.Width + 5, 0),
|
new PointF(size.Width + 5, 0),
|
||||||
new PointF(size.Width + 5, size.Height + 10),
|
new PointF(size.Width + 5, size.Height + 10),
|
||||||
new PointF(0, size.Height + 10));
|
new PointF(0, size.Height + 10));
|
||||||
|
@ -169,7 +169,7 @@ public class PlantPickService : IEService, IExecNoCommand
|
||||||
Start = 0,
|
Start = 0,
|
||||||
End = pass.GetGraphemeCount(),
|
End = pass.GetGraphemeCount(),
|
||||||
Font = font,
|
Font = font,
|
||||||
StrikeoutPen = new SolidPen(Color.White, 2),
|
StrikeoutPen = new SolidPen(Color.White, 5),
|
||||||
TextDecorations = TextDecorations.Strikeout
|
TextDecorations = TextDecorations.Strikeout
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class ShopCommands : GamblingModule<IShopService>
|
public partial class ShopCommands : GamblingSubmodule<IShopService>
|
||||||
{
|
{
|
||||||
public enum List
|
public enum List
|
||||||
{
|
{
|
||||||
|
|
|
@ -21,7 +21,7 @@ public enum GamblingError
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class SlotCommands : GamblingModule<IGamblingService>
|
public partial class SlotCommands : GamblingSubmodule<IGamblingService>
|
||||||
{
|
{
|
||||||
private readonly IImageCache _images;
|
private readonly IImageCache _images;
|
||||||
private readonly FontProvider _fonts;
|
private readonly FontProvider _fonts;
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
#nullable disable
|
|
||||||
using LinqToDB;
|
|
||||||
using LinqToDB.EntityFrameworkCore;
|
|
||||||
using EllieBot.Db.Models;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Gambling.Services;
|
|
||||||
|
|
||||||
public sealed class UserBetStatsService : IEService
|
|
||||||
{
|
|
||||||
private const long RESET_MIN_PRICE = 1000;
|
|
||||||
private const decimal RESET_TOTAL_MULTIPLIER = 0.002m;
|
|
||||||
|
|
||||||
private readonly DbService _db;
|
|
||||||
private readonly ICurrencyService _cs;
|
|
||||||
|
|
||||||
public UserBetStatsService(DbService db, ICurrencyService cs)
|
|
||||||
{
|
|
||||||
_db = db;
|
|
||||||
_cs = cs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<long> GetResetStatsPriceAsync(ulong userId, GamblingGame? game)
|
|
||||||
{
|
|
||||||
await using var ctx = _db.GetDbContext();
|
|
||||||
|
|
||||||
var totalBet = await ctx.GetTable<UserBetStats>()
|
|
||||||
.Where(x => x.UserId == userId && (game == null || x.Game == game))
|
|
||||||
.SumAsyncLinqToDB(x => x.TotalBet);
|
|
||||||
|
|
||||||
return Math.Max(RESET_MIN_PRICE, (long)Math.Ceiling(totalBet * RESET_TOTAL_MULTIPLIER));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> ResetStatsAsync(ulong userId, GamblingGame? game)
|
|
||||||
{
|
|
||||||
var price = await GetResetStatsPriceAsync(userId, game);
|
|
||||||
|
|
||||||
if (!await _cs.RemoveAsync(userId, price, new("betstats", "reset")))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await using var ctx = _db.GetDbContext();
|
|
||||||
await ctx.GetTable<UserBetStats>()
|
|
||||||
.DeleteAsync(x => x.UserId == userId && (game == null || x.Game == game));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ResetGamblingStatsAsync()
|
|
||||||
{
|
|
||||||
await using var ctx = _db.GetDbContext();
|
|
||||||
await ctx.GetTable<GamblingStats>()
|
|
||||||
.DeleteAsync();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,7 +10,7 @@ namespace EllieBot.Modules.Gambling;
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class WaifuClaimCommands : GamblingModule<WaifuService>
|
public partial class WaifuClaimCommands : GamblingSubmodule<WaifuService>
|
||||||
{
|
{
|
||||||
public WaifuClaimCommands(GamblingConfigService gamblingConfService)
|
public WaifuClaimCommands(GamblingConfigService gamblingConfService)
|
||||||
: base(gamblingConfService)
|
: base(gamblingConfService)
|
||||||
|
@ -37,45 +37,6 @@ public partial class Gambling
|
||||||
await Response().Error(strs.waifu_reset_fail).SendAsync();
|
await Response().Error(strs.waifu_reset_fail).SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
|
||||||
[RequireContext(ContextType.Guild)]
|
|
||||||
public async Task WaifuClaims()
|
|
||||||
{
|
|
||||||
await Response()
|
|
||||||
.Paginated()
|
|
||||||
.PageItems(async (page) => await _service.GetClaimsAsync(ctx.User.Id, page))
|
|
||||||
.Page((items, page) =>
|
|
||||||
{
|
|
||||||
var eb = _sender.CreateEmbed()
|
|
||||||
.WithOkColor()
|
|
||||||
.WithTitle("Waifus");
|
|
||||||
|
|
||||||
if (items.Count == 0)
|
|
||||||
{
|
|
||||||
eb
|
|
||||||
.WithPendingColor()
|
|
||||||
.WithDescription(GetText(strs.empty_page));
|
|
||||||
|
|
||||||
return eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < items.Count; i++)
|
|
||||||
{
|
|
||||||
var item = items[i];
|
|
||||||
eb.AddField($"`#{(page * 9) + 1 + i}` {N(item.Price)}",
|
|
||||||
$"""
|
|
||||||
{item.Username}
|
|
||||||
||{item.UserId}||
|
|
||||||
""",
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return eb;
|
|
||||||
})
|
|
||||||
.SendAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
public async Task WaifuClaim(long amount, [Leftover] IUser target)
|
public async Task WaifuClaim(long amount, [Leftover] IUser target)
|
||||||
|
@ -183,7 +144,7 @@ public partial class Gambling
|
||||||
if (targetId == ctx.User.Id)
|
if (targetId == ctx.User.Id)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var (w, result, amount) = await _service.DivorceWaifuAsync(ctx.User, targetId);
|
var (w, result, amount, remaining) = await _service.DivorceWaifuAsync(ctx.User, targetId);
|
||||||
|
|
||||||
if (result == DivorceResult.SucessWithPenalty)
|
if (result == DivorceResult.SucessWithPenalty)
|
||||||
{
|
{
|
||||||
|
@ -196,6 +157,14 @@ public partial class Gambling
|
||||||
await Response().Confirm(strs.waifu_divorced_notlike(N(amount))).SendAsync();
|
await Response().Confirm(strs.waifu_divorced_notlike(N(amount))).SendAsync();
|
||||||
else if (result == DivorceResult.NotYourWife)
|
else if (result == DivorceResult.NotYourWife)
|
||||||
await Response().Error(strs.waifu_not_yours).SendAsync();
|
await Response().Error(strs.waifu_not_yours).SendAsync();
|
||||||
|
else if (remaining is { } rem)
|
||||||
|
{
|
||||||
|
await Response()
|
||||||
|
.Error(strs.waifu_recent_divorce(
|
||||||
|
Format.Bold(((int)rem.TotalHours).ToString()),
|
||||||
|
Format.Bold(rem.Minutes.ToString())))
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
|
|
|
@ -318,20 +318,25 @@ public class WaifuService : IEService, IReadyExecutor
|
||||||
private static TypedKey<long> GetAffinityKey(ulong userId)
|
private static TypedKey<long> GetAffinityKey(ulong userId)
|
||||||
=> new($"waifu:affinity:{userId}");
|
=> new($"waifu:affinity:{userId}");
|
||||||
|
|
||||||
public async Task<(WaifuInfo, DivorceResult, long)> DivorceWaifuAsync(IUser user, ulong targetId)
|
public async Task<(WaifuInfo, DivorceResult, long, TimeSpan?)> DivorceWaifuAsync(IUser user, ulong targetId)
|
||||||
{
|
{
|
||||||
DivorceResult result;
|
DivorceResult result;
|
||||||
|
TimeSpan? remaining = null;
|
||||||
long amount = 0;
|
long amount = 0;
|
||||||
WaifuInfo w;
|
WaifuInfo w;
|
||||||
await using (var uow = _db.GetDbContext())
|
await using (var uow = _db.GetDbContext())
|
||||||
{
|
{
|
||||||
w = uow.Set<WaifuInfo>().ByWaifuUserId(targetId);
|
w = uow.Set<WaifuInfo>().ByWaifuUserId(targetId);
|
||||||
if (w?.Claimer is null || w.Claimer.UserId != user.Id)
|
if (w?.Claimer is null || w.Claimer.UserId != user.Id)
|
||||||
{
|
|
||||||
result = DivorceResult.NotYourWife;
|
result = DivorceResult.NotYourWife;
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
remaining = await _cache.GetRatelimitAsync(GetDivorceKey(user.Id), 6.Hours());
|
||||||
|
if (remaining is TimeSpan rem)
|
||||||
|
{
|
||||||
|
result = DivorceResult.Cooldown;
|
||||||
|
return (w, result, amount, rem);
|
||||||
|
}
|
||||||
|
|
||||||
amount = w.Price / 2;
|
amount = w.Price / 2;
|
||||||
|
|
||||||
|
@ -364,7 +369,7 @@ public class WaifuService : IEService, IReadyExecutor
|
||||||
await uow.SaveChangesAsync();
|
await uow.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (w, result, amount);
|
return (w, result, amount, remaining);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> GiftWaifuAsync(
|
public async Task<bool> GiftWaifuAsync(
|
||||||
|
@ -625,38 +630,4 @@ public class WaifuService : IEService, IReadyExecutor
|
||||||
.FirstOrDefault())
|
.FirstOrDefault())
|
||||||
.ToListAsyncEF();
|
.ToListAsyncEF();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IReadOnlyCollection<WaifuClaimsResult>> GetClaimsAsync(ulong userId, int page)
|
|
||||||
{
|
|
||||||
await using var ctx = _db.GetDbContext();
|
|
||||||
|
|
||||||
var wid = ctx.GetTable<DiscordUser>()
|
|
||||||
.Where(x => x.UserId == userId)
|
|
||||||
.Select(x => x.Id)
|
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
if (wid == 0)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
return await ctx.GetTable<WaifuInfo>()
|
|
||||||
.Where(x => x.ClaimerId == wid)
|
|
||||||
.LeftJoin(ctx.GetTable<DiscordUser>(),
|
|
||||||
(wi, du) => wi.WaifuId == du.Id,
|
|
||||||
(wi, du) => new WaifuClaimsResult(
|
|
||||||
du.Username,
|
|
||||||
du.UserId,
|
|
||||||
wi.Price
|
|
||||||
))
|
|
||||||
.OrderByDescending(x => x.Price)
|
|
||||||
.Skip(page * 9)
|
|
||||||
.Take(9)
|
|
||||||
.ToListAsyncLinqToDB();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class WaifuClaimsResult(string username, ulong userId, long price)
|
|
||||||
{
|
|
||||||
public string Username { get; } = username;
|
|
||||||
public ulong UserId { get; } = userId;
|
|
||||||
public long Price { get; } = price;
|
|
||||||
}
|
}
|
|
@ -13,10 +13,5 @@ public interface IGamblingService
|
||||||
Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount);
|
Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount);
|
||||||
Task<FlipResult[]> FlipAsync(int count);
|
Task<FlipResult[]> FlipAsync(int count);
|
||||||
Task<OneOf<RpsResult, GamblingError>> RpsAsync(ulong userId, long amount, byte pick);
|
Task<OneOf<RpsResult, GamblingError>> RpsAsync(ulong userId, long amount, byte pick);
|
||||||
|
Task<OneOf<BetdrawResult, GamblingError>> BetDrawAsync(ulong userId, long amount, byte? maybeGuessValue, byte? maybeGuessColor);
|
||||||
Task<OneOf<BetdrawResult, GamblingError>> BetDrawAsync(
|
|
||||||
ulong userId,
|
|
||||||
long amount,
|
|
||||||
byte? maybeGuessValue,
|
|
||||||
byte? maybeGuessColor);
|
|
||||||
}
|
}
|
|
@ -1,6 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using LinqToDB;
|
|
||||||
using LinqToDB.EntityFrameworkCore;
|
|
||||||
using EllieBot.Modules.Gambling.Betdraw;
|
using EllieBot.Modules.Gambling.Betdraw;
|
||||||
using EllieBot.Modules.Gambling.Rps;
|
using EllieBot.Modules.Gambling.Rps;
|
||||||
using EllieBot.Modules.Gambling.Services;
|
using EllieBot.Modules.Gambling.Services;
|
||||||
|
@ -10,15 +8,15 @@ namespace EllieBot.Modules.Gambling;
|
||||||
|
|
||||||
public sealed class NewGamblingService : IGamblingService, IEService
|
public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
{
|
{
|
||||||
private readonly GamblingConfigService _gcs;
|
private readonly GamblingConfigService _bcs;
|
||||||
private readonly ICurrencyService _cs;
|
private readonly ICurrencyService _cs;
|
||||||
|
|
||||||
public NewGamblingService(GamblingConfigService gcs, ICurrencyService cs)
|
public NewGamblingService(GamblingConfigService bcs, ICurrencyService cs)
|
||||||
{
|
{
|
||||||
_gcs = gcs;
|
_bcs = bcs;
|
||||||
_cs = cs;
|
_cs = cs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<OneOf<LuLaResult, GamblingError>> LulaAsync(ulong userId, long amount)
|
public async Task<OneOf<LuLaResult, GamblingError>> LulaAsync(ulong userId, long amount)
|
||||||
{
|
{
|
||||||
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
||||||
|
@ -33,13 +31,13 @@ public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var game = new LulaGame(_gcs.Data.LuckyLadder.Multipliers);
|
var game = new LulaGame(_bcs.Data.LuckyLadder.Multipliers);
|
||||||
var result = game.Spin(amount);
|
var result = game.Spin(amount);
|
||||||
|
|
||||||
var won = (long)result.Won;
|
var won = (long)result.Won;
|
||||||
if (won > 0)
|
if (won > 0)
|
||||||
{
|
{
|
||||||
await _cs.AddAsync(userId, won, new("lula", result.Multiplier >= 1 ? "win" : "lose"));
|
await _cs.AddAsync(userId, won, new("lula", "win"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -59,9 +57,9 @@ public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var game = new BetrollGame(_gcs.Data.BetRoll.Pairs
|
var game = new BetrollGame(_bcs.Data.BetRoll.Pairs
|
||||||
.Select(x => (x.WhenAbove, (decimal)x.MultiplyBy))
|
.Select(x => (x.WhenAbove, (decimal)x.MultiplyBy))
|
||||||
.ToList());
|
.ToList());
|
||||||
|
|
||||||
var result = game.Roll(amount);
|
var result = game.Roll(amount);
|
||||||
|
|
||||||
|
@ -90,23 +88,19 @@ public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var game = new BetflipGame(_gcs.Data.BetFlip.Multiplier);
|
var game = new BetflipGame(_bcs.Data.BetFlip.Multiplier);
|
||||||
var result = game.Flip(guess, amount);
|
var result = game.Flip(guess, amount);
|
||||||
|
|
||||||
var won = (long)result.Won;
|
var won = (long)result.Won;
|
||||||
if (won > 0)
|
if (won > 0)
|
||||||
{
|
{
|
||||||
await _cs.AddAsync(userId, won, new("betflip", "win"));
|
await _cs.AddAsync(userId, won, new("betflip", "win"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<OneOf<BetdrawResult, GamblingError>> BetDrawAsync(
|
public async Task<OneOf<BetdrawResult, GamblingError>> BetDrawAsync(ulong userId, long amount, byte? maybeGuessValue, byte? maybeGuessColor)
|
||||||
ulong userId,
|
|
||||||
long amount,
|
|
||||||
byte? maybeGuessValue,
|
|
||||||
byte? maybeGuessColor)
|
|
||||||
{
|
{
|
||||||
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
||||||
|
|
||||||
|
@ -115,7 +109,7 @@ public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
|
|
||||||
if (maybeGuessColor > 1)
|
if (maybeGuessColor > 1)
|
||||||
throw new ArgumentOutOfRangeException(nameof(maybeGuessColor));
|
throw new ArgumentOutOfRangeException(nameof(maybeGuessColor));
|
||||||
|
|
||||||
if (maybeGuessValue > 1)
|
if (maybeGuessValue > 1)
|
||||||
throw new ArgumentOutOfRangeException(nameof(maybeGuessValue));
|
throw new ArgumentOutOfRangeException(nameof(maybeGuessValue));
|
||||||
|
|
||||||
|
@ -131,13 +125,13 @@ public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
|
|
||||||
var game = new BetdrawGame();
|
var game = new BetdrawGame();
|
||||||
var result = game.Draw((BetdrawValueGuess?)maybeGuessValue, (BetdrawColorGuess?)maybeGuessColor, amount);
|
var result = game.Draw((BetdrawValueGuess?)maybeGuessValue, (BetdrawColorGuess?)maybeGuessColor, amount);
|
||||||
|
|
||||||
var won = (long)result.Won;
|
var won = (long)result.Won;
|
||||||
if (won > 0)
|
if (won > 0)
|
||||||
{
|
{
|
||||||
await _cs.AddAsync(userId, won, new("betdraw", "win"));
|
await _cs.AddAsync(userId, won, new("betdraw", "win"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +155,7 @@ public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
var won = (long)result.Won;
|
var won = (long)result.Won;
|
||||||
if (won > 0)
|
if (won > 0)
|
||||||
{
|
{
|
||||||
await _cs.AddAsync(userId, won, new("slot", "win"));
|
await _cs.AddAsync(userId, won, new("slot", "won"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -184,7 +178,7 @@ public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
|
|
||||||
return Task.FromResult(results);
|
return Task.FromResult(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// private readonly ConcurrentDictionary<ulong, Deck> _decks = new ConcurrentDictionary<ulong, Deck>();
|
// private readonly ConcurrentDictionary<ulong, Deck> _decks = new ConcurrentDictionary<ulong, Deck>();
|
||||||
|
@ -242,7 +236,7 @@ public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
{
|
{
|
||||||
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
||||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(pick, 2);
|
ArgumentOutOfRangeException.ThrowIfGreaterThan(pick, 2);
|
||||||
|
|
||||||
if (amount > 0)
|
if (amount > 0)
|
||||||
{
|
{
|
||||||
var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("rps", "bet"));
|
var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("rps", "bet"));
|
||||||
|
@ -255,7 +249,7 @@ public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
|
|
||||||
var rps = new RpsGame();
|
var rps = new RpsGame();
|
||||||
var result = rps.Play((RpsPick)pick, amount);
|
var result = rps.Play((RpsPick)pick, amount);
|
||||||
|
|
||||||
var won = (long)result.Won;
|
var won = (long)result.Won;
|
||||||
if (won > 0)
|
if (won > 0)
|
||||||
{
|
{
|
||||||
|
@ -271,46 +265,4 @@ public sealed class NewGamblingService : IGamblingService, IEService
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class RakebackService : IEService
|
|
||||||
{
|
|
||||||
private readonly DbService _db;
|
|
||||||
private readonly ICurrencyService _cs;
|
|
||||||
|
|
||||||
public RakebackService(DbService db, ICurrencyService cs)
|
|
||||||
{
|
|
||||||
_db = db;
|
|
||||||
_cs = cs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<long> GetRakebackAsync(ulong userId)
|
|
||||||
{
|
|
||||||
await using var uow = _db.GetDbContext();
|
|
||||||
|
|
||||||
var rb = uow.GetTable<Rakeback>()
|
|
||||||
.Where(x => x.UserId == userId)
|
|
||||||
.Select(x => x.Amount)
|
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
return (long)rb;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<long> ClaimRakebackAsync(ulong userId)
|
|
||||||
{
|
|
||||||
await using var uow = _db.GetDbContext();
|
|
||||||
|
|
||||||
var rbs = await uow.GetTable<Rakeback>()
|
|
||||||
.Where(x => x.UserId == userId)
|
|
||||||
.DeleteWithOutputAsync((x) => x.Amount);
|
|
||||||
|
|
||||||
if (rbs.Length == 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
var rb = (long)rbs[0];
|
|
||||||
|
|
||||||
await _cs.AddAsync(userId, rb, new("rakeback", "claim"));
|
|
||||||
|
|
||||||
return rb;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -122,11 +122,11 @@ public sealed class CurrencyRewardService : IEService, IReadyExecutor
|
||||||
var dollarValue = pledgeCents / 100;
|
var dollarValue = pledgeCents / 100;
|
||||||
percentBonus = dollarValue switch
|
percentBonus = dollarValue switch
|
||||||
{
|
{
|
||||||
>= 100 => 25,
|
>= 100 => 20,
|
||||||
>= 50 => 20,
|
>= 50 => 10,
|
||||||
>= 20 => 15,
|
>= 20 => 5,
|
||||||
>= 10 => 10,
|
>= 10 => 3,
|
||||||
>= 5 => 5,
|
>= 5 => 1,
|
||||||
_ => 0
|
_ => 0
|
||||||
};
|
};
|
||||||
return (long)(modifiedAmount * (1 + (percentBonus / 100.0f)));
|
return (long)(modifiedAmount * (1 + (percentBonus / 100.0f)));
|
||||||
|
|
|
@ -404,9 +404,9 @@ public sealed class PatronageService
|
||||||
{
|
{
|
||||||
>= 10_000 => 100,
|
>= 10_000 => 100,
|
||||||
>= 5000 => 50,
|
>= 5000 => 50,
|
||||||
>= 2000 => 30,
|
>= 2000 => 20,
|
||||||
>= 1000 => 20,
|
>= 1000 => 10,
|
||||||
>= 500 => 10,
|
>= 500 => 5,
|
||||||
_ => 0
|
_ => 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,39 +18,39 @@ public partial class Permissions
|
||||||
{
|
{
|
||||||
ArgumentOutOfRangeException.ThrowIfNegative(page);
|
ArgumentOutOfRangeException.ThrowIfNegative(page);
|
||||||
|
|
||||||
var list = await _service.GetBlacklist(type);
|
var list = _service.GetBlacklist();
|
||||||
var allItems = await list
|
var allItems = await list.Where(x => x.Type == type)
|
||||||
.Select(i =>
|
.Select(i =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return Task.FromResult(type switch
|
return Task.FromResult(i.Type switch
|
||||||
{
|
{
|
||||||
BlacklistType.Channel => Format.Code(i.ItemId.ToString())
|
BlacklistType.Channel => Format.Code(i.ItemId.ToString())
|
||||||
|
+ " "
|
||||||
|
+ (_client.GetChannel(i.ItemId)?.ToString()
|
||||||
|
?? ""),
|
||||||
|
BlacklistType.User => Format.Code(i.ItemId.ToString())
|
||||||
|
+ " "
|
||||||
|
+ ((_client.GetUser(i.ItemId))
|
||||||
|
?.ToString()
|
||||||
|
?? ""),
|
||||||
|
BlacklistType.Server => Format.Code(i.ItemId.ToString())
|
||||||
+ " "
|
+ " "
|
||||||
+ (_client.GetChannel(i.ItemId)?.ToString()
|
+ (_client.GetGuild(i.ItemId)?.ToString() ?? ""),
|
||||||
?? ""),
|
_ => Format.Code(i.ItemId.ToString())
|
||||||
BlacklistType.User => Format.Code(i.ItemId.ToString())
|
});
|
||||||
+ " "
|
}
|
||||||
+ ((_client.GetUser(i.ItemId))
|
catch
|
||||||
?.ToString()
|
{
|
||||||
?? ""),
|
Log.Warning("Can't get {BlacklistType} [{BlacklistItemId}]",
|
||||||
BlacklistType.Server => Format.Code(i.ItemId.ToString())
|
i.Type,
|
||||||
+ " "
|
i.ItemId);
|
||||||
+ (_client.GetGuild(i.ItemId)?.ToString() ?? ""),
|
|
||||||
_ => Format.Code(i.ItemId.ToString())
|
return Task.FromResult(Format.Code(i.ItemId.ToString()));
|
||||||
});
|
}
|
||||||
}
|
})
|
||||||
catch
|
.WhenAll();
|
||||||
{
|
|
||||||
Log.Warning("Can't get {BlacklistType} [{BlacklistItemId}]",
|
|
||||||
i.Type,
|
|
||||||
i.ItemId);
|
|
||||||
|
|
||||||
return Task.FromResult(Format.Code(i.ItemId.ToString()));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.WhenAll();
|
|
||||||
|
|
||||||
await Response()
|
await Response()
|
||||||
.Paginated()
|
.Paginated()
|
||||||
|
@ -61,14 +61,14 @@ public partial class Permissions
|
||||||
{
|
{
|
||||||
if (pageItems.Count == 0)
|
if (pageItems.Count == 0)
|
||||||
return _sender.CreateEmbed()
|
return _sender.CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithTitle(title)
|
.WithTitle(title)
|
||||||
.WithDescription(GetText(strs.empty_page));
|
.WithDescription(GetText(strs.empty_page));
|
||||||
|
|
||||||
return _sender.CreateEmbed()
|
return _sender.CreateEmbed()
|
||||||
.WithTitle(title)
|
.WithTitle(title)
|
||||||
.WithDescription(pageItems.Join('\n'))
|
.WithDescription(pageItems.Join('\n'))
|
||||||
.WithOkColor();
|
.WithOkColor();
|
||||||
})
|
})
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,13 +69,5 @@ public partial class FlagTranslateService
|
||||||
YE ar
|
YE ar
|
||||||
AL sq
|
AL sq
|
||||||
AE ar
|
AE ar
|
||||||
AU en
|
|
||||||
NZ en
|
|
||||||
KZ kz
|
|
||||||
NO no
|
|
||||||
SE sv
|
|
||||||
DK da
|
|
||||||
FI fi
|
|
||||||
HU hu
|
|
||||||
""";
|
""";
|
||||||
}
|
}
|
|
@ -783,28 +783,4 @@ public partial class Utility : EllieModule
|
||||||
await Response().Error(ex.Message).SendAsync();
|
await Response().Error(ex.Message).SendAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
|
||||||
public async Task Snipe()
|
|
||||||
{
|
|
||||||
if (ctx.Message.ReferencedMessage is not { } msg)
|
|
||||||
{
|
|
||||||
var msgs = await ctx.Channel.GetMessagesAsync(ctx.Message, Direction.Before, 3).FlattenAsync();
|
|
||||||
msg = msgs.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.Content) || (x.Attachments.FirstOrDefault()?.Width is not null)) as IUserMessage;
|
|
||||||
|
|
||||||
if (msg is null)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var eb = _sender.CreateEmbed()
|
|
||||||
.WithOkColor()
|
|
||||||
.WithDescription(msg.Content)
|
|
||||||
.WithAuthor(msg.Author)
|
|
||||||
.WithTimestamp(msg.Timestamp)
|
|
||||||
.WithImageUrl(msg.Attachments.FirstOrDefault()?.Url)
|
|
||||||
.WithFooter(GetText(strs.sniped_by(ctx.User.ToString())), ctx.User.GetDisplayAvatarUrl());
|
|
||||||
|
|
||||||
ctx.Message.DeleteAfter(1);
|
|
||||||
await Response().Embed(eb).SendAsync();
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -225,7 +225,7 @@ public partial class Xp : EllieModule<XpService>
|
||||||
else if (userXpData.AwardedXp < 0)
|
else if (userXpData.AwardedXp < 0)
|
||||||
awardStr = $"({userXpData.AwardedXp})";
|
awardStr = $"({userXpData.AwardedXp})";
|
||||||
|
|
||||||
embed.AddField($"#{i + 1 + (curPage * 10)} {user?.ToString() ?? users[i].UserId.ToString()}",
|
embed.AddField($"#{i + 1 + (curPage * 9)} {user?.ToString() ?? users[i].UserId.ToString()}",
|
||||||
$"{GetText(strs.level_x(levelStats.Level))} - {levelStats.TotalXp}xp {awardStr}");
|
$"{GetText(strs.level_x(levelStats.Level))} - {levelStats.TotalXp}xp {awardStr}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,7 +278,7 @@ public partial class Xp : EllieModule<XpService>
|
||||||
for (var i = 0; i < users.Count; i++)
|
for (var i = 0; i < users.Count; i++)
|
||||||
{
|
{
|
||||||
var user = users[i];
|
var user = users[i];
|
||||||
embed.AddField($"#{i + 1 + (curPage * 10)} {user}",
|
embed.AddField($"#{i + 1 + (curPage * 9)} {user}",
|
||||||
$"{GetText(strs.level_x(new LevelStats(users[i].TotalXp).Level))} - {users[i].TotalXp}xp");
|
$"{GetText(strs.level_x(new LevelStats(users[i].TotalXp).Level))} - {users[i].TotalXp}xp");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,13 +17,10 @@ public sealed class GreetByeSvc : GrpcGreet.GrpcGreetBase, IGrpcSvc, IEService
|
||||||
public ServerServiceDefinition Bind()
|
public ServerServiceDefinition Bind()
|
||||||
=> GrpcGreet.BindService(this);
|
=> GrpcGreet.BindService(this);
|
||||||
|
|
||||||
private static GrpcGreetSettings ToConf(GreetSettings? conf, GreetType type)
|
private static GrpcGreetSettings ToConf(GreetSettings? conf)
|
||||||
{
|
{
|
||||||
if (conf is null)
|
if (conf is null)
|
||||||
return new GrpcGreetSettings()
|
return new GrpcGreetSettings();
|
||||||
{
|
|
||||||
Type = (GrpcGreetType)type
|
|
||||||
};
|
|
||||||
|
|
||||||
return new GrpcGreetSettings()
|
return new GrpcGreetSettings()
|
||||||
{
|
{
|
||||||
|
@ -38,10 +35,9 @@ public sealed class GreetByeSvc : GrpcGreet.GrpcGreetBase, IGrpcSvc, IEService
|
||||||
{
|
{
|
||||||
var guildId = request.GuildId;
|
var guildId = request.GuildId;
|
||||||
|
|
||||||
var type = (GreetType)request.Type;
|
var conf = await _gs.GetGreetSettingsAsync(guildId, (GreetType)request.Type);
|
||||||
var conf = await _gs.GetGreetSettingsAsync(guildId, type);
|
|
||||||
|
|
||||||
return ToConf(conf, type);
|
return ToConf(conf);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<UpdateGreetReply> UpdateGreet(UpdateGreetRequest request, ServerCallContext context)
|
public override async Task<UpdateGreetReply> UpdateGreet(UpdateGreetRequest request, ServerCallContext context)
|
||||||
|
|
|
@ -69,10 +69,10 @@ public class XpSvc : GrpcXp.GrpcXpBase, IGrpcSvc, IEService
|
||||||
rews = rews.Concat(roleRews.Select(x => new RewItemReply()
|
rews = rews.Concat(roleRews.Select(x => new RewItemReply()
|
||||||
{
|
{
|
||||||
Level = x.Level,
|
Level = x.Level,
|
||||||
Type = x.Remove ? "RemoveRole" : "AddRole",
|
Type = "Role",
|
||||||
Value = guild.GetRole(x.RoleId)?.ToString() ?? x.RoleId.ToString()
|
Value = guild.GetRole(x.RoleId)?.ToString() ?? x.RoleId.ToString()
|
||||||
}))
|
}))
|
||||||
.OrderBy(x => x.Level);
|
.OrderBy(x => x.Level);
|
||||||
|
|
||||||
reply.Rewards.AddRange(rews);
|
reply.Rewards.AddRange(rews);
|
||||||
|
|
||||||
|
@ -159,14 +159,14 @@ public class XpSvc : GrpcXp.GrpcXpBase, IGrpcSvc, IEService
|
||||||
_xp.SetRoleReward(request.GuildId, request.Level, rid, request.Type == "RemoveRole");
|
_xp.SetRoleReward(request.GuildId, request.Level, rid, request.Type == "RemoveRole");
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
// else if (request.Type == "Currency")
|
else if (request.Type == "Currency")
|
||||||
// {
|
{
|
||||||
// if (!int.TryParse(request.Value, out var amount))
|
if (!int.TryParse(request.Value, out var amount))
|
||||||
// throw new RpcException(new Status(StatusCode.InvalidArgument, "Invalid amount"));
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Invalid amount"));
|
||||||
//
|
|
||||||
// _xp.SetCurrencyReward(request.GuildId, request.Level, amount);
|
_xp.SetCurrencyReward(request.GuildId, request.Level, amount);
|
||||||
// success = true;
|
success = true;
|
||||||
// }
|
}
|
||||||
|
|
||||||
return new()
|
return new()
|
||||||
{
|
{
|
||||||
|
@ -207,15 +207,15 @@ public class XpSvc : GrpcXp.GrpcXpBase, IGrpcSvc, IEService
|
||||||
|
|
||||||
public override async Task<GetXpLbReply> GetXpLb(GetXpLbRequest request, ServerCallContext context)
|
public override async Task<GetXpLbReply> GetXpLb(GetXpLbRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
if (request.Page < 1)
|
if (request.Page < 0)
|
||||||
throw new RpcException(new Status(StatusCode.InvalidArgument, "Page must be greater than or equal to 1"));
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Page must be greater than or equal to 0"));
|
||||||
|
|
||||||
var guild = _client.GetGuild(request.GuildId);
|
var guild = _client.GetGuild(request.GuildId);
|
||||||
|
|
||||||
if (guild is null)
|
if (guild is null)
|
||||||
throw new RpcException(new Status(StatusCode.NotFound, "Guild not found"));
|
throw new RpcException(new Status(StatusCode.NotFound, "Guild not found"));
|
||||||
|
|
||||||
var data = await _xp.GetGuildUserXps(request.GuildId, request.Page - 1);
|
var data = await _xp.GetGuildUserXps(request.GuildId, request.Page);
|
||||||
var total = await _xp.GetTotalGuildUsers(request.GuildId);
|
var total = await _xp.GetTotalGuildUsers(request.GuildId);
|
||||||
|
|
||||||
var reply = new GetXpLbReply
|
var reply = new GetXpLbReply
|
||||||
|
@ -223,60 +223,45 @@ public class XpSvc : GrpcXp.GrpcXpBase, IGrpcSvc, IEService
|
||||||
Total = total
|
Total = total
|
||||||
};
|
};
|
||||||
|
|
||||||
var users = await data
|
reply.Users.AddRange(await data
|
||||||
.Select(async x =>
|
.Select(async x =>
|
||||||
{
|
{
|
||||||
var user = guild.GetUser(x.UserId);
|
var user = guild.GetUser(x.UserId);
|
||||||
|
|
||||||
if (user is null)
|
if (user is null)
|
||||||
{
|
{
|
||||||
var du = await _duSvc.GetUserAsync(x.UserId);
|
var du = await _duSvc.GetUserAsync(x.UserId);
|
||||||
if (du is null)
|
if (du is null)
|
||||||
return new XpLbUserReply
|
return new XpLbUserReply
|
||||||
{
|
{
|
||||||
UserId = x.UserId,
|
UserId = x.UserId,
|
||||||
Avatar = string.Empty,
|
Avatar = string.Empty,
|
||||||
Username = string.Empty,
|
Username = string.Empty,
|
||||||
Xp = x.Xp,
|
Xp = x.Xp,
|
||||||
Level = new LevelStats(x.Xp).Level
|
Level = new LevelStats(x.Xp).Level
|
||||||
};
|
};
|
||||||
|
|
||||||
return new XpLbUserReply()
|
return new XpLbUserReply()
|
||||||
{
|
{
|
||||||
UserId = x.UserId,
|
UserId = x.UserId,
|
||||||
Avatar = du.RealAvatarUrl()?.ToString() ?? string.Empty,
|
Avatar = du.RealAvatarUrl()?.ToString() ?? string.Empty,
|
||||||
Username = du.ToString() ?? string.Empty,
|
Username = du.ToString() ?? string.Empty,
|
||||||
Xp = x.Xp,
|
Xp = x.Xp,
|
||||||
Level = new LevelStats(x.Xp).Level
|
Level = new LevelStats(x.Xp).Level
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return new XpLbUserReply
|
return new XpLbUserReply
|
||||||
{
|
{
|
||||||
UserId = x.UserId,
|
UserId = x.UserId,
|
||||||
Avatar = user?.GetAvatarUrl() ?? string.Empty,
|
Avatar = user?.GetAvatarUrl() ?? string.Empty,
|
||||||
Username = user?.ToString() ?? string.Empty,
|
Username = user?.ToString() ?? string.Empty,
|
||||||
Xp = x.Xp,
|
Xp = x.Xp,
|
||||||
Level = new LevelStats(x.Xp).Level
|
Level = new LevelStats(x.Xp).Level
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.WhenAll();
|
.WhenAll());
|
||||||
|
|
||||||
reply.Users.AddRange(users);
|
|
||||||
|
|
||||||
return reply;
|
return reply;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<SetServerExclusionReply> SetServerExclusion(
|
|
||||||
SetServerExclusionRequest request,
|
|
||||||
ServerCallContext context)
|
|
||||||
{
|
|
||||||
await Task.Yield();
|
|
||||||
|
|
||||||
var newValue = _xp.ToggleExcludeServer(request.GuildId);
|
|
||||||
return new()
|
|
||||||
{
|
|
||||||
Success = newValue
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -4,6 +4,6 @@ namespace EllieBot.Services;
|
||||||
|
|
||||||
public interface ITxTracker
|
public interface ITxTracker
|
||||||
{
|
{
|
||||||
Task TrackAdd(ulong userId, long amount, TxData? txData);
|
Task TrackAdd(long amount, TxData? txData);
|
||||||
Task TrackRemove(ulong userId, long amount, TxData? txData);
|
Task TrackRemove(long amount, TxData? txData);
|
||||||
}
|
}
|
|
@ -11,9 +11,9 @@ public class SlotGame
|
||||||
{
|
{
|
||||||
var rolls = new[]
|
var rolls = new[]
|
||||||
{
|
{
|
||||||
(byte)_rng.Next(0, 7),
|
(byte)_rng.Next(0, 6),
|
||||||
(byte)_rng.Next(0, 7),
|
(byte)_rng.Next(0, 6),
|
||||||
(byte)_rng.Next(0, 7)
|
(byte)_rng.Next(0, 6)
|
||||||
};
|
};
|
||||||
|
|
||||||
ref var a = ref rolls[0];
|
ref var a = ref rolls[0];
|
||||||
|
@ -24,24 +24,24 @@ public class SlotGame
|
||||||
var winType = SlotWinType.None;
|
var winType = SlotWinType.None;
|
||||||
if (a == b && b == c)
|
if (a == b && b == c)
|
||||||
{
|
{
|
||||||
if (a == 6)
|
if (a == 5)
|
||||||
{
|
{
|
||||||
winType = SlotWinType.TrippleJoker;
|
winType = SlotWinType.TrippleJoker;
|
||||||
multi = 25;
|
multi = 30;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
winType = SlotWinType.TrippleNormal;
|
winType = SlotWinType.TrippleNormal;
|
||||||
multi = 15;
|
multi = 10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (a == 6 && (b == 6 || c == 6)
|
else if (a == 5 && (b == 5 || c == 5)
|
||||||
|| (b == 6 && c == 6))
|
|| (b == 5 && c == 5))
|
||||||
{
|
{
|
||||||
winType = SlotWinType.DoubleJoker;
|
winType = SlotWinType.DoubleJoker;
|
||||||
multi = 6;
|
multi = 4;
|
||||||
}
|
}
|
||||||
else if (a == 6 || b == 6 || c == 6)
|
else if (a == 5 || b == 5 || c == 5)
|
||||||
{
|
{
|
||||||
winType = SlotWinType.SingleJoker;
|
winType = SlotWinType.SingleJoker;
|
||||||
multi = 1;
|
multi = 1;
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace EllieBot.Common;
|
||||||
public partial class ImageUrls : ICloneable<ImageUrls>
|
public partial class ImageUrls : ICloneable<ImageUrls>
|
||||||
{
|
{
|
||||||
[Comment("DO NOT CHANGE")]
|
[Comment("DO NOT CHANGE")]
|
||||||
public int Version { get; set; } = 6;
|
public int Version { get; set; } = 5;
|
||||||
|
|
||||||
public CoinData Coins { get; set; }
|
public CoinData Coins { get; set; }
|
||||||
public Uri[] Currency { get; set; }
|
public Uri[] Currency { get; set; }
|
||||||
|
|
|
@ -12,11 +12,7 @@ public sealed partial class ReplacementPatternStore
|
||||||
{
|
{
|
||||||
Register("%bot.time%",
|
Register("%bot.time%",
|
||||||
static ()
|
static ()
|
||||||
=> TimestampTag.FromDateTime(DateTime.UtcNow, TimestampTagStyles.ShortTime).ToString());
|
=> DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()));
|
||||||
|
|
||||||
Register("%bot.date%",
|
|
||||||
static ()
|
|
||||||
=> TimestampTag.FromDateTime(DateTime.UtcNow, TimestampTagStyles.ShortTime).ToString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WithClient()
|
private void WithClient()
|
||||||
|
|
|
@ -165,7 +165,7 @@ public class CommandHandler : IEService, IReadyExecutor, ICommandHandler
|
||||||
Log.Information("Succ | g:{GuildId} | c: {ChannelId} | u: {UserId} | msg: {Message}",
|
Log.Information("Succ | g:{GuildId} | c: {ChannelId} | u: {UserId} | msg: {Message}",
|
||||||
channel?.Guild.Id.ToString() ?? "-",
|
channel?.Guild.Id.ToString() ?? "-",
|
||||||
channel?.Id.ToString() ?? "-",
|
channel?.Id.ToString() ?? "-",
|
||||||
usrMsg.Author.Id.ToString(),
|
usrMsg.Author.Id,
|
||||||
usrMsg.Content.TrimTo(10));
|
usrMsg.Content.TrimTo(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,7 +77,7 @@ public sealed class CurrencyService : ICurrencyService, IEService
|
||||||
{
|
{
|
||||||
var wallet = await GetWalletAsync(userId);
|
var wallet = await GetWalletAsync(userId);
|
||||||
await wallet.Add(amount, txData);
|
await wallet.Add(amount, txData);
|
||||||
await _txTracker.TrackAdd(userId, amount, txData);
|
await _txTracker.TrackAdd(amount, txData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddAsync(
|
public async Task AddAsync(
|
||||||
|
@ -97,7 +97,7 @@ public sealed class CurrencyService : ICurrencyService, IEService
|
||||||
var wallet = await GetWalletAsync(userId);
|
var wallet = await GetWalletAsync(userId);
|
||||||
var result = await wallet.Take(amount, txData);
|
var result = await wallet.Take(amount, txData);
|
||||||
if (result)
|
if (result)
|
||||||
await _txTracker.TrackRemove(userId, amount, txData);
|
await _txTracker.TrackRemove(amount, txData);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.Data;
|
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Services.Currency;
|
using EllieBot.Services.Currency;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
using EllieBot.Modules.Gambling;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
|
|
||||||
namespace EllieBot.Services;
|
namespace EllieBot.Services;
|
||||||
|
|
||||||
|
@ -13,11 +10,15 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
||||||
{
|
{
|
||||||
private static readonly IReadOnlySet<string> _gamblingTypes = new HashSet<string>(new[]
|
private static readonly IReadOnlySet<string> _gamblingTypes = new HashSet<string>(new[]
|
||||||
{
|
{
|
||||||
"lula", "betroll", "betflip", "blackjack", "betdraw", "slot",
|
"lula",
|
||||||
|
"betroll",
|
||||||
|
"betflip",
|
||||||
|
"blackjack",
|
||||||
|
"betdraw",
|
||||||
|
"slot",
|
||||||
});
|
});
|
||||||
|
|
||||||
private NonBlocking.ConcurrentDictionary<string, (decimal Bet, decimal PaidOut)> globalStats = new();
|
private ConcurrentDictionary<string, (decimal Bet, decimal PaidOut)> _stats = new();
|
||||||
private ConcurrentBag<UserBetStats> userStats = new();
|
|
||||||
|
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
|
|
||||||
|
@ -27,333 +28,83 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnReadyAsync()
|
public async Task OnReadyAsync()
|
||||||
=> await Task.WhenAll(RunUserStatsCollector(), RunBetStatsCollector());
|
|
||||||
|
|
||||||
public async Task RunBetStatsCollector()
|
|
||||||
{
|
{
|
||||||
using var timer = new PeriodicTimer(TimeSpan.FromHours(1));
|
using var timer = new PeriodicTimer(TimeSpan.FromHours(1));
|
||||||
while (await timer.WaitForNextTickAsync())
|
while (await timer.WaitForNextTickAsync())
|
||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
|
await using var trans = await ctx.Database.BeginTransactionAsync();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// update betstats
|
var keys = _stats.Keys;
|
||||||
var keys = globalStats.Keys;
|
|
||||||
foreach (var key in keys)
|
foreach (var key in keys)
|
||||||
{
|
{
|
||||||
if (globalStats.TryRemove(key, out var stat))
|
if (_stats.TryRemove(key, out var stat))
|
||||||
{
|
{
|
||||||
await ctx.GetTable<GamblingStats>()
|
await ctx.GetTable<GamblingStats>()
|
||||||
.InsertOrUpdateAsync(() => new()
|
.InsertOrUpdateAsync(() => new()
|
||||||
{
|
{
|
||||||
Feature = key,
|
Feature = key,
|
||||||
Bet = stat.Bet,
|
Bet = stat.Bet,
|
||||||
PaidOut = stat.PaidOut,
|
PaidOut = stat.PaidOut,
|
||||||
DateAdded = DateTime.UtcNow
|
DateAdded = DateTime.UtcNow
|
||||||
},
|
}, old => new()
|
||||||
old => new()
|
{
|
||||||
{
|
Bet = old.Bet + stat.Bet,
|
||||||
Bet = old.Bet + stat.Bet,
|
PaidOut = old.PaidOut + stat.PaidOut,
|
||||||
PaidOut = old.PaidOut + stat.PaidOut,
|
}, () => new()
|
||||||
},
|
{
|
||||||
() => new()
|
Feature = key
|
||||||
{
|
});
|
||||||
Feature = key
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Error(ex, "An error occurred in betstats gambling tx tracker");
|
Log.Error(ex, "An error occurred in gambling tx tracker");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await trans.CommitAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RunUserStatsCollector()
|
public Task TrackAdd(long amount, TxData? txData)
|
||||||
{
|
|
||||||
var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));
|
|
||||||
while (await timer.WaitForNextTickAsync())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (userStats.Count == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var users = new List<UserBetStats>(userStats.Count + 5);
|
|
||||||
|
|
||||||
while (userStats.TryTake(out var s))
|
|
||||||
users.Add(s);
|
|
||||||
|
|
||||||
if (users.Count == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// rakeback
|
|
||||||
var rakebacks = new Dictionary<ulong, decimal>();
|
|
||||||
|
|
||||||
// update userstats
|
|
||||||
foreach (var (k, x) in users.GroupBy(x => (x.UserId, x.Game))
|
|
||||||
.ToDictionary(x => x.Key,
|
|
||||||
x => x.Aggregate((a, b) => new()
|
|
||||||
{
|
|
||||||
WinCount = a.WinCount + b.WinCount,
|
|
||||||
LoseCount = a.LoseCount + b.LoseCount,
|
|
||||||
TotalBet = a.TotalBet + b.TotalBet,
|
|
||||||
PaidOut = a.PaidOut + b.PaidOut,
|
|
||||||
MaxBet = Math.Max(a.MaxBet, b.MaxBet),
|
|
||||||
MaxWin = Math.Max(a.MaxWin, b.MaxWin),
|
|
||||||
})))
|
|
||||||
{
|
|
||||||
rakebacks.TryAdd(k.UserId, 0m);
|
|
||||||
rakebacks[k.UserId] += x.TotalBet * GetHouseEdge(k.Game) * BASE_RAKEBACK;
|
|
||||||
|
|
||||||
|
|
||||||
// bulk upsert in the future
|
|
||||||
await using var uow = _db.GetDbContext();
|
|
||||||
await uow.GetTable<UserBetStats>()
|
|
||||||
.InsertOrUpdateAsync(() => new()
|
|
||||||
{
|
|
||||||
UserId = k.UserId,
|
|
||||||
Game = k.Game,
|
|
||||||
WinCount = x.WinCount,
|
|
||||||
LoseCount = Math.Max(0, x.LoseCount),
|
|
||||||
TotalBet = x.TotalBet,
|
|
||||||
PaidOut = x.PaidOut,
|
|
||||||
MaxBet = x.MaxBet,
|
|
||||||
MaxWin = x.MaxWin
|
|
||||||
},
|
|
||||||
o => new()
|
|
||||||
{
|
|
||||||
WinCount = o.WinCount + x.WinCount,
|
|
||||||
LoseCount = Math.Max(0, o.LoseCount + x.LoseCount),
|
|
||||||
TotalBet = o.TotalBet + x.TotalBet,
|
|
||||||
PaidOut = o.PaidOut + x.PaidOut,
|
|
||||||
MaxBet = Math.Max(o.MaxBet, x.MaxBet),
|
|
||||||
MaxWin = Math.Max(o.MaxWin, x.MaxWin),
|
|
||||||
},
|
|
||||||
() => new()
|
|
||||||
{
|
|
||||||
UserId = k.UserId,
|
|
||||||
Game = k.Game
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var (k, v) in rakebacks)
|
|
||||||
{
|
|
||||||
await _db.GetDbContext()
|
|
||||||
.GetTable<Rakeback>()
|
|
||||||
.InsertOrUpdateAsync(() => new()
|
|
||||||
{
|
|
||||||
UserId = k,
|
|
||||||
Amount = v
|
|
||||||
},
|
|
||||||
(old) => new()
|
|
||||||
{
|
|
||||||
Amount = old.Amount + v
|
|
||||||
},
|
|
||||||
() => new()
|
|
||||||
{
|
|
||||||
UserId = k
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "An error occurred in UserBetStats gambling tx tracker");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const decimal BASE_RAKEBACK = 0.05m;
|
|
||||||
|
|
||||||
public Task TrackAdd(ulong userId, long amount, TxData? txData)
|
|
||||||
{
|
{
|
||||||
if (txData is null)
|
if (txData is null)
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
||||||
if (_gamblingTypes.Contains(txData.Type))
|
if (_gamblingTypes.Contains(txData.Type))
|
||||||
{
|
{
|
||||||
globalStats.AddOrUpdate(txData.Type,
|
_stats.AddOrUpdate(txData.Type,
|
||||||
_ => (0, amount),
|
_ => (0, amount),
|
||||||
(_, old) => (old.Bet, old.PaidOut + amount));
|
(_, old) => (old.Bet, old.PaidOut + amount));
|
||||||
}
|
}
|
||||||
|
|
||||||
var mType = GetGameType(txData.Type);
|
|
||||||
|
|
||||||
if (mType is not { } type)
|
|
||||||
return Task.CompletedTask;
|
|
||||||
|
|
||||||
if (txData.Type == "lula")
|
|
||||||
{
|
|
||||||
if (txData.Extra == "lose")
|
|
||||||
{
|
|
||||||
userStats.Add(new()
|
|
||||||
{
|
|
||||||
UserId = userId,
|
|
||||||
Game = type,
|
|
||||||
WinCount = 0,
|
|
||||||
LoseCount = 0,
|
|
||||||
TotalBet = 0,
|
|
||||||
PaidOut = amount,
|
|
||||||
MaxBet = 0,
|
|
||||||
MaxWin = amount,
|
|
||||||
});
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (txData.Type == "animalrace")
|
|
||||||
{
|
|
||||||
if (txData.Extra == "refund")
|
|
||||||
{
|
|
||||||
userStats.Add(new()
|
|
||||||
{
|
|
||||||
UserId = userId,
|
|
||||||
Game = type,
|
|
||||||
WinCount = 0,
|
|
||||||
LoseCount = -1,
|
|
||||||
TotalBet = -amount,
|
|
||||||
PaidOut = 0,
|
|
||||||
MaxBet = 0,
|
|
||||||
MaxWin = 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
userStats.Add(new UserBetStats()
|
|
||||||
{
|
|
||||||
UserId = userId,
|
|
||||||
Game = type,
|
|
||||||
WinCount = 1,
|
|
||||||
LoseCount = -1,
|
|
||||||
TotalBet = 0,
|
|
||||||
PaidOut = amount,
|
|
||||||
MaxBet = 0,
|
|
||||||
MaxWin = amount,
|
|
||||||
});
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task TrackRemove(ulong userId, long amount, TxData? txData)
|
public Task TrackRemove(long amount, TxData? txData)
|
||||||
{
|
{
|
||||||
if (txData is null)
|
if (txData is null)
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
||||||
if (_gamblingTypes.Contains(txData.Type))
|
if (_gamblingTypes.Contains(txData.Type))
|
||||||
{
|
{
|
||||||
globalStats.AddOrUpdate(txData.Type,
|
_stats.AddOrUpdate(txData.Type,
|
||||||
_ => (amount, 0),
|
_ => (amount, 0),
|
||||||
(_, old) => (old.Bet + amount, old.PaidOut));
|
(_, old) => (old.Bet + amount, old.PaidOut));
|
||||||
}
|
}
|
||||||
|
|
||||||
var mType = GetGameType(txData.Type);
|
|
||||||
|
|
||||||
if (mType is not { } type)
|
|
||||||
return Task.CompletedTask;
|
|
||||||
|
|
||||||
userStats.Add(new UserBetStats()
|
|
||||||
{
|
|
||||||
UserId = userId,
|
|
||||||
Game = type,
|
|
||||||
WinCount = 0,
|
|
||||||
LoseCount = 1,
|
|
||||||
TotalBet = amount,
|
|
||||||
PaidOut = 0,
|
|
||||||
MaxBet = amount,
|
|
||||||
MaxWin = 0
|
|
||||||
});
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GamblingGame? GetGameType(string game)
|
|
||||||
=> game switch
|
|
||||||
{
|
|
||||||
"lula" => GamblingGame.Lula,
|
|
||||||
"betroll" => GamblingGame.Betroll,
|
|
||||||
"betflip" => GamblingGame.Betflip,
|
|
||||||
"blackjack" => GamblingGame.Blackjack,
|
|
||||||
"betdraw" => GamblingGame.Betdraw,
|
|
||||||
"slot" => GamblingGame.Slots,
|
|
||||||
"animalrace" => GamblingGame.Race,
|
|
||||||
_ => null
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task<IReadOnlyCollection<GamblingStats>> GetAllAsync()
|
public async Task<IReadOnlyCollection<GamblingStats>> GetAllAsync()
|
||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
return await ctx.Set<GamblingStats>()
|
return await ctx.Set<GamblingStats>()
|
||||||
.ToListAsyncEF();
|
.ToListAsyncEF();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<UserBetStats>> GetUserStatsAsync(ulong userId, GamblingGame? game = null)
|
|
||||||
{
|
|
||||||
await using var ctx = _db.GetDbContext();
|
|
||||||
|
|
||||||
|
|
||||||
if (game is null)
|
|
||||||
return await ctx
|
|
||||||
.GetTable<UserBetStats>()
|
|
||||||
.Where(x => x.UserId == userId)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
return await ctx
|
|
||||||
.GetTable<UserBetStats>()
|
|
||||||
.Where(x => x.UserId == userId && x.Game == game)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public decimal GetHouseEdge(GamblingGame game)
|
|
||||||
=> game switch
|
|
||||||
{
|
|
||||||
GamblingGame.Betflip => 0.025m,
|
|
||||||
GamblingGame.Betroll => 0.04m,
|
|
||||||
GamblingGame.Betdraw => 0.04m,
|
|
||||||
GamblingGame.Slots => 0.034m,
|
|
||||||
GamblingGame.Blackjack => 0.02m,
|
|
||||||
GamblingGame.Lula => 0.025m,
|
|
||||||
GamblingGame.Race => 0.06m,
|
|
||||||
_ => 0
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class UserBetStats
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public ulong UserId { get; set; }
|
|
||||||
public GamblingGame Game { get; set; }
|
|
||||||
public long WinCount { get; set; }
|
|
||||||
public long LoseCount { get; set; }
|
|
||||||
public decimal TotalBet { get; set; }
|
|
||||||
public decimal PaidOut { get; set; }
|
|
||||||
public long MaxWin { get; set; }
|
|
||||||
public long MaxBet { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum GamblingGame
|
|
||||||
{
|
|
||||||
Betflip = 0,
|
|
||||||
Bf = 0,
|
|
||||||
Betroll = 1,
|
|
||||||
Br = 1,
|
|
||||||
Betdraw = 2,
|
|
||||||
Bd = 2,
|
|
||||||
Slots = 3,
|
|
||||||
Slot = 3,
|
|
||||||
Blackjack = 4,
|
|
||||||
Bj = 4,
|
|
||||||
Lula = 5,
|
|
||||||
Race = 6,
|
|
||||||
AnimalRace = 6
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class Rakeback
|
|
||||||
{
|
|
||||||
public ulong UserId { get; set; }
|
|
||||||
public decimal Amount { get; set; }
|
|
||||||
}
|
}
|
|
@ -1,13 +1,13 @@
|
||||||
|
#nullable disable
|
||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.Data;
|
using LinqToDB.Data;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
using System.Collections.Frozen;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Permissions.Services;
|
namespace EllieBot.Modules.Permissions.Services;
|
||||||
|
|
||||||
public sealed class BlacklistService : IExecOnMessage, IReadyExecutor
|
public sealed class BlacklistService : IExecOnMessage
|
||||||
{
|
{
|
||||||
public int Priority
|
public int Priority
|
||||||
=> int.MaxValue;
|
=> int.MaxValue;
|
||||||
|
@ -15,114 +15,69 @@ public sealed class BlacklistService : IExecOnMessage, IReadyExecutor
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
private readonly IPubSub _pubSub;
|
private readonly IPubSub _pubSub;
|
||||||
private readonly IBotCreds _creds;
|
private readonly IBotCreds _creds;
|
||||||
private readonly DiscordSocketClient _client;
|
private IReadOnlyList<BlacklistEntry> blacklist;
|
||||||
|
|
||||||
private FrozenSet<ulong> blacklistedGuilds = new HashSet<ulong>().ToFrozenSet();
|
private readonly TypedKey<BlacklistEntry[]> _blPubKey = new("blacklist.reload");
|
||||||
private FrozenSet<ulong> blacklistedUsers = new HashSet<ulong>().ToFrozenSet();
|
|
||||||
private FrozenSet<ulong> blacklistedChannels = new HashSet<ulong>().ToFrozenSet();
|
|
||||||
|
|
||||||
private readonly TypedKey<bool> _blPubKey = new("blacklist.reload");
|
public BlacklistService(DbService db, IPubSub pubSub, IBotCreds creds)
|
||||||
|
|
||||||
public BlacklistService(
|
|
||||||
DbService db,
|
|
||||||
IPubSub pubSub,
|
|
||||||
IBotCreds creds,
|
|
||||||
DiscordSocketClient client)
|
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_pubSub = pubSub;
|
_pubSub = pubSub;
|
||||||
_creds = creds;
|
_creds = creds;
|
||||||
_client = client;
|
|
||||||
|
|
||||||
_pubSub.Sub(_blPubKey, async _ => await Reload(false));
|
Reload(false);
|
||||||
}
|
_pubSub.Sub(_blPubKey, OnReload);
|
||||||
|
|
||||||
public async Task OnReadyAsync()
|
|
||||||
{
|
|
||||||
_client.JoinedGuild += async (g) =>
|
|
||||||
{
|
|
||||||
if (blacklistedGuilds.Contains(g.Id))
|
|
||||||
{
|
|
||||||
await g.LeaveAsync();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
await Reload(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ValueTask OnReload(BlacklistEntry[] newBlacklist)
|
private ValueTask OnReload(BlacklistEntry[] newBlacklist)
|
||||||
{
|
{
|
||||||
newBlacklist ??= [];
|
blacklist = newBlacklist;
|
||||||
|
|
||||||
blacklistedGuilds =
|
|
||||||
new HashSet<ulong>(newBlacklist.Where(x => x.Type == BlacklistType.Server).Select(x => x.ItemId))
|
|
||||||
.ToFrozenSet();
|
|
||||||
blacklistedChannels =
|
|
||||||
new HashSet<ulong>(newBlacklist.Where(x => x.Type == BlacklistType.Channel).Select(x => x.ItemId))
|
|
||||||
.ToFrozenSet();
|
|
||||||
blacklistedUsers =
|
|
||||||
new HashSet<ulong>(newBlacklist.Where(x => x.Type == BlacklistType.User).Select(x => x.ItemId))
|
|
||||||
.ToFrozenSet();
|
|
||||||
|
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<bool> ExecOnMessageAsync(IGuild? guild, IUserMessage usrMsg)
|
public Task<bool> ExecOnMessageAsync(IGuild guild, IUserMessage usrMsg)
|
||||||
{
|
{
|
||||||
if (guild is not null && blacklistedGuilds.Contains(guild.Id))
|
foreach (var bl in blacklist)
|
||||||
{
|
{
|
||||||
Log.Information("Blocked input from blacklisted guild: {GuildName} [{GuildId}]",
|
if (guild is not null && bl.Type == BlacklistType.Server && bl.ItemId == guild.Id)
|
||||||
guild.Name,
|
{
|
||||||
guild.Id.ToString());
|
Log.Information("Blocked input from blacklisted guild: {GuildName} [{GuildId}]", guild.Name, guild.Id);
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blacklistedChannels.Contains(usrMsg.Channel.Id))
|
return Task.FromResult(true);
|
||||||
{
|
}
|
||||||
Log.Information("Blocked input from blacklisted channel: {ChannelName} [{ChannelId}]",
|
|
||||||
usrMsg.Channel.Name,
|
|
||||||
usrMsg.Channel.Id.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (bl.Type == BlacklistType.Channel && bl.ItemId == usrMsg.Channel.Id)
|
||||||
|
{
|
||||||
|
Log.Information("Blocked input from blacklisted channel: {ChannelName} [{ChannelId}]",
|
||||||
|
usrMsg.Channel.Name,
|
||||||
|
usrMsg.Channel.Id);
|
||||||
|
|
||||||
if (blacklistedUsers.Contains(usrMsg.Author.Id))
|
return Task.FromResult(true);
|
||||||
{
|
}
|
||||||
Log.Information("Blocked input from blacklisted user: {UserName} [{UserId}]",
|
|
||||||
usrMsg.Author.ToString(),
|
if (bl.Type == BlacklistType.User && bl.ItemId == usrMsg.Author.Id)
|
||||||
usrMsg.Author.Id.ToString());
|
{
|
||||||
return Task.FromResult(true);
|
Log.Information("Blocked input from blacklisted user: {UserName} [{UserId}]",
|
||||||
|
usrMsg.Author.ToString(),
|
||||||
|
usrMsg.Author.Id);
|
||||||
|
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult(false);
|
return Task.FromResult(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IReadOnlyList<BlacklistEntry>> GetBlacklist(BlacklistType type)
|
public IReadOnlyList<BlacklistEntry> GetBlacklist()
|
||||||
|
=> blacklist;
|
||||||
|
|
||||||
|
public void Reload(bool publish = true)
|
||||||
{
|
{
|
||||||
await using var uow = _db.GetDbContext();
|
using var uow = _db.GetDbContext();
|
||||||
|
var toPublish = uow.GetTable<BlacklistEntry>().ToArray();
|
||||||
return await uow
|
blacklist = toPublish;
|
||||||
.GetTable<BlacklistEntry>()
|
|
||||||
.Where(x => x.Type == type)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Reload(bool publish = true)
|
|
||||||
{
|
|
||||||
var totalShards = _creds.TotalShards;
|
|
||||||
await using var uow = _db.GetDbContext();
|
|
||||||
var items = uow.GetTable<BlacklistEntry>()
|
|
||||||
.Where(x => x.Type != BlacklistType.Server
|
|
||||||
|| (x.Type == BlacklistType.Server
|
|
||||||
&& Linq2DbExpressions.GuildOnShard(x.ItemId, totalShards, _client.ShardId)))
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
|
|
||||||
if (publish)
|
if (publish)
|
||||||
{
|
_pubSub.Pub(_blPubKey, toPublish);
|
||||||
await _pubSub.Pub(_blPubKey, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
await OnReload(items);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Blacklist(BlacklistType type, ulong id)
|
public async Task Blacklist(BlacklistType type, ulong id)
|
||||||
|
@ -133,34 +88,34 @@ public sealed class BlacklistService : IExecOnMessage, IReadyExecutor
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
|
|
||||||
await uow
|
await uow
|
||||||
.GetTable<BlacklistEntry>()
|
.GetTable<BlacklistEntry>()
|
||||||
.InsertAsync(() => new()
|
.InsertAsync(() => new()
|
||||||
{
|
{
|
||||||
ItemId = id,
|
ItemId = id,
|
||||||
Type = type,
|
Type = type,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (type == BlacklistType.User)
|
if (type == BlacklistType.User)
|
||||||
{
|
{
|
||||||
await uow.GetTable<DiscordUser>()
|
await uow.GetTable<DiscordUser>()
|
||||||
.Where(x => x.UserId == id)
|
.Where(x => x.UserId == id)
|
||||||
.UpdateAsync(_ => new()
|
.UpdateAsync(_ => new()
|
||||||
{
|
{
|
||||||
CurrencyAmount = 0
|
CurrencyAmount = 0
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await Reload();
|
Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UnBlacklist(BlacklistType type, ulong id)
|
public async Task UnBlacklist(BlacklistType type, ulong id)
|
||||||
{
|
{
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
await uow.GetTable<BlacklistEntry>()
|
await uow.GetTable<BlacklistEntry>()
|
||||||
.Where(bi => bi.ItemId == id && bi.Type == type)
|
.Where(bi => bi.ItemId == id && bi.Type == type)
|
||||||
.DeleteAsync();
|
.DeleteAsync();
|
||||||
|
|
||||||
await Reload();
|
Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task BlacklistUsers(IReadOnlyCollection<ulong> toBlacklist)
|
public async Task BlacklistUsers(IReadOnlyCollection<ulong> toBlacklist)
|
||||||
|
@ -175,12 +130,12 @@ public sealed class BlacklistService : IExecOnMessage, IReadyExecutor
|
||||||
|
|
||||||
var blList = toBlacklist.ToList();
|
var blList = toBlacklist.ToList();
|
||||||
await uow.GetTable<DiscordUser>()
|
await uow.GetTable<DiscordUser>()
|
||||||
.Where(x => blList.Contains(x.UserId))
|
.Where(x => blList.Contains(x.UserId))
|
||||||
.UpdateAsync(_ => new()
|
.UpdateAsync(_ => new()
|
||||||
{
|
{
|
||||||
CurrencyAmount = 0
|
CurrencyAmount = 0
|
||||||
});
|
});
|
||||||
|
|
||||||
await Reload();
|
Reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -27,22 +27,5 @@ public sealed class ImagesConfig : ConfigServiceBase<ImageUrls>
|
||||||
c.Version = 5;
|
c.Version = 5;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.Version < 6)
|
|
||||||
{
|
|
||||||
ModifyConfig(c =>
|
|
||||||
{
|
|
||||||
if (c.Slots.Emojis?.Length == 6)
|
|
||||||
{
|
|
||||||
c.Slots.Emojis =
|
|
||||||
[
|
|
||||||
new("https://cdn.nadeko.bot/slots/15.png"),
|
|
||||||
..c.Slots.Emojis
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Version = 6;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Text.RegularExpressions;
|
#nullable disable
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace EllieBot.Common.TypeReaders.Models;
|
namespace EllieBot.Common.TypeReaders.Models;
|
||||||
|
|
||||||
|
@ -8,8 +9,8 @@ public class StoopidTime
|
||||||
@"^(?:(?<months>\d)mo)?(?:(?<weeks>\d{1,2})w)?(?:(?<days>\d{1,2})d)?(?:(?<hours>\d{1,4})h)?(?:(?<minutes>\d{1,5})m)?(?:(?<seconds>\d{1,6})s)?$",
|
@"^(?:(?<months>\d)mo)?(?:(?<weeks>\d{1,2})w)?(?:(?<days>\d{1,2})d)?(?:(?<hours>\d{1,4})h)?(?:(?<minutes>\d{1,5})m)?(?:(?<seconds>\d{1,6})s)?$",
|
||||||
RegexOptions.Compiled | RegexOptions.Multiline);
|
RegexOptions.Compiled | RegexOptions.Multiline);
|
||||||
|
|
||||||
public string Input { get; set; } = string.Empty;
|
public string Input { get; set; }
|
||||||
public TimeSpan Time { get; set; } = default;
|
public TimeSpan Time { get; set; }
|
||||||
|
|
||||||
private StoopidTime() { }
|
private StoopidTime() { }
|
||||||
|
|
||||||
|
@ -52,8 +53,8 @@ public class StoopidTime
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static implicit operator TimeSpan?(StoopidTime? st)
|
public static implicit operator TimeSpan(StoopidTime st)
|
||||||
=> st?.Time;
|
=> st.Time;
|
||||||
|
|
||||||
public static implicit operator StoopidTime(TimeSpan ts)
|
public static implicit operator StoopidTime(TimeSpan ts)
|
||||||
=> new()
|
=> new()
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
# WORK IN PROGRESS
|
|
||||||
|
|
||||||
# Define the folders to search for designer.cs files
|
|
||||||
$folders = @("Migrations/PostgreSql", "Migrations/Sqlite")
|
|
||||||
|
|
||||||
# Loop through each folder
|
|
||||||
foreach ($folder in $folders) {
|
|
||||||
# Get all designer.cs files in the folder and subfolders
|
|
||||||
$files = Get-ChildItem -Path $folder -Filter *.designer.cs -Recurse
|
|
||||||
|
|
||||||
$excludedPattern = "cleanup|mysql-init|squash|rero-cascade"
|
|
||||||
|
|
||||||
$filteredFiles = $files | Where-Object { $_.Name -notmatch $excludedPattern }
|
|
||||||
# Loop through each file
|
|
||||||
foreach ($file in ($files | Where-Object { $_.Name -notmatch $excludedPattern })) {
|
|
||||||
# Read the contents of the file
|
|
||||||
$content = Get-Content -Path $file.FullName | Select-Object -First 30
|
|
||||||
|
|
||||||
# Find the attribute lines
|
|
||||||
$attributes = $content | Where-Object { $_ -match '\[.*\]' } | ForEach-Object { ' ' + $_.Trim() }
|
|
||||||
|
|
||||||
# Find the namespace
|
|
||||||
$namespace = $content | Where-Object { $_ -match 'namespace' } | ForEach-Object { $_.Split(' ')[1] }
|
|
||||||
|
|
||||||
# Find the class name
|
|
||||||
$class_name = $content | Where-Object { $_ -match 'partial class' } | ForEach-Object { $_.Trim().Split(' ')[2] }
|
|
||||||
|
|
||||||
# Replace the contents with the new template
|
|
||||||
$new_content = @"
|
|
||||||
// <auto-generated />
|
|
||||||
using EllieBot.Db;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace $namespace
|
|
||||||
{
|
|
||||||
$($attributes -join "`n")
|
|
||||||
partial class $class_name
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"@
|
|
||||||
|
|
||||||
# Write the new contents to the file
|
|
||||||
Set-Content -Path $file.FullName -Value $new_content
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -848,10 +848,6 @@ eventstart:
|
||||||
- eventstart
|
- eventstart
|
||||||
betstats:
|
betstats:
|
||||||
- betstats
|
- betstats
|
||||||
- bs
|
|
||||||
gamblestats:
|
|
||||||
- gamblestats
|
|
||||||
- gs
|
|
||||||
bettest:
|
bettest:
|
||||||
- bettest
|
- bettest
|
||||||
slot:
|
slot:
|
||||||
|
@ -863,11 +859,6 @@ affinity:
|
||||||
waifuclaim:
|
waifuclaim:
|
||||||
- waifuclaim
|
- waifuclaim
|
||||||
- claim
|
- claim
|
||||||
- wc
|
|
||||||
waifuclaims:
|
|
||||||
- waifuclaims
|
|
||||||
- claims
|
|
||||||
- wcs
|
|
||||||
waifureset:
|
waifureset:
|
||||||
- waifureset
|
- waifureset
|
||||||
waifutransfer:
|
waifutransfer:
|
||||||
|
@ -1458,18 +1449,4 @@ translateflags:
|
||||||
- translateflags
|
- translateflags
|
||||||
- trfl
|
- trfl
|
||||||
- fltr
|
- fltr
|
||||||
- transflags
|
- transflags
|
||||||
rakeback:
|
|
||||||
- rakeback
|
|
||||||
- rb
|
|
||||||
betstatsreset:
|
|
||||||
- betstatsreset
|
|
||||||
- bsr
|
|
||||||
- bsreset
|
|
||||||
gamblestatsreset:
|
|
||||||
- gamblestatsreset
|
|
||||||
- gsr
|
|
||||||
- gsreset
|
|
||||||
snipe:
|
|
||||||
- snipe
|
|
||||||
- sn
|
|
|
@ -2756,6 +2756,19 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Gambling": [
|
"Gambling": [
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".betstats"
|
||||||
|
],
|
||||||
|
"Description": "Shows the total stats of several gambling features.\nUpdates once an hour.",
|
||||||
|
"Usage": [
|
||||||
|
".betstats"
|
||||||
|
],
|
||||||
|
"Submodule": "Gambling",
|
||||||
|
"Module": "Gambling",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".timely"
|
".timely"
|
||||||
|
@ -2999,20 +3012,6 @@
|
||||||
"Bot Owner Only"
|
"Bot Owner Only"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"Aliases": [
|
|
||||||
".rakeback",
|
|
||||||
".rb"
|
|
||||||
],
|
|
||||||
"Description": "Try to claim any rakeback that you have available.\nRakeback is accumulated by betting (not by winning or losing).\nDefault rakeback is 0.05 * house edge\nHouse edge is defined per game",
|
|
||||||
"Usage": [
|
|
||||||
".rakeback"
|
|
||||||
],
|
|
||||||
"Submodule": "Gambling",
|
|
||||||
"Module": "Gambling",
|
|
||||||
"Options": null,
|
|
||||||
"Requirements": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".race"
|
".race"
|
||||||
|
@ -3120,70 +3119,6 @@
|
||||||
"Bot Owner Only"
|
"Bot Owner Only"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"Aliases": [
|
|
||||||
".betstatsreset",
|
|
||||||
".bsr",
|
|
||||||
".bsreset"
|
|
||||||
],
|
|
||||||
"Description": "Reset all of your Bet Stats for a fee.\nYou can alternatively reset Bet Stats for the specified game.",
|
|
||||||
"Usage": [
|
|
||||||
".betstatsreset",
|
|
||||||
".betstatsreset game"
|
|
||||||
],
|
|
||||||
"Submodule": "BetStatsCommands",
|
|
||||||
"Module": "Gambling",
|
|
||||||
"Options": null,
|
|
||||||
"Requirements": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Aliases": [
|
|
||||||
".betstats",
|
|
||||||
".bs"
|
|
||||||
],
|
|
||||||
"Description": "Shows the current bet stats for yourself, or the targetted user.\nYou may optionally specify the game to show stats for.\nSupported games right now are: bf, br, bd, lula, slot, race",
|
|
||||||
"Usage": [
|
|
||||||
".betstats",
|
|
||||||
".betstats @someone",
|
|
||||||
".betstats @someone lula",
|
|
||||||
".betstats bd"
|
|
||||||
],
|
|
||||||
"Submodule": "BetStatsCommands",
|
|
||||||
"Module": "Gambling",
|
|
||||||
"Options": null,
|
|
||||||
"Requirements": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Aliases": [
|
|
||||||
".gamblestats",
|
|
||||||
".gs"
|
|
||||||
],
|
|
||||||
"Description": "Shows the total stats of several gambling features.\nUpdates once an hour.",
|
|
||||||
"Usage": [
|
|
||||||
".gamblestats"
|
|
||||||
],
|
|
||||||
"Submodule": "BetStatsCommands",
|
|
||||||
"Module": "Gambling",
|
|
||||||
"Options": null,
|
|
||||||
"Requirements": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Aliases": [
|
|
||||||
".gamblestatsreset",
|
|
||||||
".gsr",
|
|
||||||
".gsreset"
|
|
||||||
],
|
|
||||||
"Description": "Resets the gamble stats.",
|
|
||||||
"Usage": [
|
|
||||||
".gamblestatsreset"
|
|
||||||
],
|
|
||||||
"Submodule": "BetStatsCommands",
|
|
||||||
"Module": "Gambling",
|
|
||||||
"Options": null,
|
|
||||||
"Requirements": [
|
|
||||||
"Bot Owner Only"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".blackjack",
|
".blackjack",
|
||||||
|
@ -3648,25 +3583,10 @@
|
||||||
"Options": null,
|
"Options": null,
|
||||||
"Requirements": []
|
"Requirements": []
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"Aliases": [
|
|
||||||
".waifuclaims",
|
|
||||||
".claims",
|
|
||||||
".wcs"
|
|
||||||
],
|
|
||||||
"Description": "Shows all of your currently claimed waifus.",
|
|
||||||
"Usage": [
|
|
||||||
".waifuclaims"
|
|
||||||
],
|
|
||||||
"Submodule": "WaifuClaimCommands",
|
|
||||||
"Module": "Gambling",
|
|
||||||
"Options": null,
|
|
||||||
"Requirements": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".waifuclaim",
|
".waifuclaim",
|
||||||
".claim - wc"
|
".claim"
|
||||||
],
|
],
|
||||||
"Description": "Claim a waifu for yourself by spending currency. You must spend at least 10% more than her current value unless she set `.affinity` towards you.",
|
"Description": "Claim a waifu for yourself by spending currency. You must spend at least 10% more than her current value unless she set `.affinity` towards you.",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
|
@ -6240,24 +6160,6 @@
|
||||||
"Options": null,
|
"Options": null,
|
||||||
"Requirements": []
|
"Requirements": []
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"Aliases": [
|
|
||||||
".translateflags",
|
|
||||||
".trfl",
|
|
||||||
".fltr",
|
|
||||||
".transflags"
|
|
||||||
],
|
|
||||||
"Description": "Toggles translate flags on the current channel.\nReacting with a country flag will translate the message to that country's language.",
|
|
||||||
"Usage": [
|
|
||||||
".translateflags"
|
|
||||||
],
|
|
||||||
"Submodule": "TranslateCommands",
|
|
||||||
"Module": "Searches",
|
|
||||||
"Options": null,
|
|
||||||
"Requirements": [
|
|
||||||
"ManageChannels Channel Permission"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".xkcd"
|
".xkcd"
|
||||||
|
@ -6606,20 +6508,6 @@
|
||||||
"No Public Bot"
|
"No Public Bot"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"Aliases": [
|
|
||||||
".snipe",
|
|
||||||
".sn"
|
|
||||||
],
|
|
||||||
"Description": "Snipe the message you replied to with this command.\nOtherwise, if you don't reply to a message, it will snipe the last message sent in the channel (out of the last few messages) which has text or an image.",
|
|
||||||
"Usage": [
|
|
||||||
".snipe"
|
|
||||||
],
|
|
||||||
"Submodule": "Utility",
|
|
||||||
"Module": "Utility",
|
|
||||||
"Options": null,
|
|
||||||
"Requirements": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".prompt"
|
".prompt"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# DO NOT CHANGE
|
# DO NOT CHANGE
|
||||||
version: 12
|
version: 9
|
||||||
# Currency settings
|
# Currency settings
|
||||||
currency:
|
currency:
|
||||||
# What is the emoji/character which represents the currency
|
# What is the emoji/character which represents the currency
|
||||||
|
@ -28,7 +28,7 @@ betRoll:
|
||||||
multiplyBy: 10
|
multiplyBy: 10
|
||||||
- whenAbove: 90
|
- whenAbove: 90
|
||||||
multiplyBy: 4
|
multiplyBy: 4
|
||||||
- whenAbove: 65
|
- whenAbove: 66
|
||||||
multiplyBy: 2
|
multiplyBy: 2
|
||||||
# Automatic currency generation settings.
|
# Automatic currency generation settings.
|
||||||
generation:
|
generation:
|
||||||
|
@ -56,9 +56,8 @@ timely:
|
||||||
# How often (in hours) can users claim currency with .timely command
|
# How often (in hours) can users claim currency with .timely command
|
||||||
# setting to 0 or less will disable this feature
|
# setting to 0 or less will disable this feature
|
||||||
cooldown: 12
|
cooldown: 12
|
||||||
# How will timely be protected?
|
# Whether the users are required to type a password when they do timely.
|
||||||
# None, Button (users have to click the button) or Captcha (users have to type the captcha from an image)
|
requirePassword: true
|
||||||
protType: Button
|
|
||||||
# How much will each user's owned currency decay over time.
|
# How much will each user's owned currency decay over time.
|
||||||
decay:
|
decay:
|
||||||
# Percentage of user's current currency which will be deducted every 24h.
|
# Percentage of user's current currency which will be deducted every 24h.
|
||||||
|
@ -85,7 +84,7 @@ luckyLadder:
|
||||||
- 2.4
|
- 2.4
|
||||||
- 1.7
|
- 1.7
|
||||||
- 1.5
|
- 1.5
|
||||||
- 1.1
|
- 1.2
|
||||||
- 0.5
|
- 0.5
|
||||||
- 0.3
|
- 0.3
|
||||||
- 0.2
|
- 0.2
|
||||||
|
@ -274,9 +273,3 @@ voteReward: 100
|
||||||
slots:
|
slots:
|
||||||
# Hex value of the color which the numbers on the slot image will have.
|
# Hex value of the color which the numbers on the slot image will have.
|
||||||
currencyFontColor: ff0000
|
currencyFontColor: ff0000
|
||||||
# Bonus config for server boosts
|
|
||||||
boostBonus:
|
|
||||||
# Users will receive a bonus if they boost any of these servers
|
|
||||||
guildIds: []
|
|
||||||
# This bonus will be added before any other multiplier is applied to the .timely command
|
|
||||||
baseTimelyBonus: 50
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# DO NOT CHANGE
|
# DO NOT CHANGE
|
||||||
version: 6
|
version: 5
|
||||||
coins:
|
coins:
|
||||||
heads:
|
heads:
|
||||||
- https://cdn.nadeko.bot/coins/heads3.png
|
- https://cdn.nadeko.bot/coins/heads3.png
|
||||||
|
@ -22,13 +22,15 @@ dice:
|
||||||
- https://cdn.nadeko.bot/other/dice/9.png
|
- https://cdn.nadeko.bot/other/dice/9.png
|
||||||
xp:
|
xp:
|
||||||
bg: https://cdn.nadeko.bot/other/xp/bg_k.png
|
bg: https://cdn.nadeko.bot/other/xp/bg_k.png
|
||||||
|
rip:
|
||||||
|
bg: https://cdn.nadeko.bot/other/rip/rip.png
|
||||||
|
overlay: https://cdn.nadeko.bot/other/rip/overlay.png
|
||||||
slots:
|
slots:
|
||||||
emojis:
|
emojis:
|
||||||
- https://cdn.nadeko.bot/slots/10.png
|
- https://cdn.nadeko.bot/slots/0.png
|
||||||
- https://cdn.nadeko.bot/slots/11.png
|
- https://cdn.nadeko.bot/slots/1.png
|
||||||
- https://cdn.nadeko.bot/slots/12.png
|
- https://cdn.nadeko.bot/slots/2.png
|
||||||
- https://cdn.nadeko.bot/slots/13.png
|
- https://cdn.nadeko.bot/slots/3.png
|
||||||
- https://cdn.nadeko.bot/slots/14.png
|
- https://cdn.nadeko.bot/slots/4.png
|
||||||
- https://cdn.nadeko.bot/slots/15.png
|
- https://cdn.nadeko.bot/slots/5.png
|
||||||
- https://cdn.nadeko.bot/slots/16.png
|
|
||||||
bg: https://cdn.nadeko.bot/slots/slots_bg.png
|
bg: https://cdn.nadeko.bot/slots/slots_bg.png
|
||||||
|
|
|
@ -1517,7 +1517,7 @@ take:
|
||||||
betroll:
|
betroll:
|
||||||
desc: |-
|
desc: |-
|
||||||
Bets the specified amount of currency and rolls a dice.
|
Bets the specified amount of currency and rolls a dice.
|
||||||
Rolling over 65 yields x2 of your currency, over 90 - x4 and 100 x10.
|
Rolling over 66 yields x2 of your currency, over 90 - x4 and 100 x10.
|
||||||
You can specify 'all', 'half' or 'X%' instead of the amount to bet that part of your current balance.
|
You can specify 'all', 'half' or 'X%' instead of the amount to bet that part of your current balance.
|
||||||
ex:
|
ex:
|
||||||
- 5
|
- 5
|
||||||
|
@ -2703,7 +2703,7 @@ eventstart:
|
||||||
desc: "The type of event being started."
|
desc: "The type of event being started."
|
||||||
options:
|
options:
|
||||||
desc: "The optional option flags for the event."
|
desc: "The optional option flags for the event."
|
||||||
gamblestats:
|
betstats:
|
||||||
desc: |-
|
desc: |-
|
||||||
Shows the total stats of several gambling features.
|
Shows the total stats of several gambling features.
|
||||||
Updates once an hour.
|
Updates once an hour.
|
||||||
|
@ -2711,13 +2711,6 @@ gamblestats:
|
||||||
- ''
|
- ''
|
||||||
params:
|
params:
|
||||||
- { }
|
- { }
|
||||||
gamblestatsreset:
|
|
||||||
desc: |-
|
|
||||||
Resets the gamble stats.
|
|
||||||
ex:
|
|
||||||
- ''
|
|
||||||
params:
|
|
||||||
- { }
|
|
||||||
slot:
|
slot:
|
||||||
desc: |-
|
desc: |-
|
||||||
Play Ellie slots by placing your bet.
|
Play Ellie slots by placing your bet.
|
||||||
|
@ -2745,12 +2738,6 @@ waifuclaim:
|
||||||
desc: "The cost of claiming the waifu."
|
desc: "The cost of claiming the waifu."
|
||||||
target:
|
target:
|
||||||
desc: "The user to whom the claim is being made, allowing the waifu to be claimed from their collection."
|
desc: "The user to whom the claim is being made, allowing the waifu to be claimed from their collection."
|
||||||
waifuclaims:
|
|
||||||
desc: Shows all of your currently claimed waifus.
|
|
||||||
ex:
|
|
||||||
- ''
|
|
||||||
params:
|
|
||||||
- { }
|
|
||||||
waifureset:
|
waifureset:
|
||||||
desc: Resets your waifu stats, except current waifus.
|
desc: Resets your waifu stats, except current waifus.
|
||||||
ex:
|
ex:
|
||||||
|
@ -4653,53 +4640,5 @@ translateflags:
|
||||||
Reacting with a country flag will translate the message to that country's language.
|
Reacting with a country flag will translate the message to that country's language.
|
||||||
ex:
|
ex:
|
||||||
- ''
|
- ''
|
||||||
params:
|
|
||||||
- { }
|
|
||||||
betstatsreset:
|
|
||||||
desc: |-
|
|
||||||
Reset all of your Bet Stats for a fee.
|
|
||||||
You can alternatively reset Bet Stats for the specified game.
|
|
||||||
ex:
|
|
||||||
- ''
|
|
||||||
- 'game'
|
|
||||||
params:
|
|
||||||
- game:
|
|
||||||
desc: 'The game to reset betstats for. Omit to reset all games'
|
|
||||||
betstats:
|
|
||||||
desc: |-
|
|
||||||
Shows the current bet stats for yourself, or the targetted user.
|
|
||||||
You may optionally specify the game to show stats for.
|
|
||||||
Supported games right now are: bf, br, bd, lula, slot, race
|
|
||||||
ex:
|
|
||||||
- ''
|
|
||||||
- '@someone'
|
|
||||||
- '@someone lula'
|
|
||||||
- 'bd'
|
|
||||||
params:
|
|
||||||
- {}
|
|
||||||
- user:
|
|
||||||
desc: 'The user for who to show the betstats for.'
|
|
||||||
- user:
|
|
||||||
desc: 'The user for who to show the betstats for.'
|
|
||||||
game:
|
|
||||||
desc: 'The game to show betstats for. Omit to show betstats for all games combined'
|
|
||||||
- game:
|
|
||||||
desc: 'The game to show betstats for. Omit to show betstats for all games combined'
|
|
||||||
rakeback:
|
|
||||||
desc: |-
|
|
||||||
Try to claim any rakeback that you have available.
|
|
||||||
Rakeback is accumulated by betting (not by winning or losing).
|
|
||||||
Default rakeback is 0.05 * house edge
|
|
||||||
House edge is defined per game
|
|
||||||
ex:
|
|
||||||
- ''
|
|
||||||
params:
|
|
||||||
- {}
|
|
||||||
snipe:
|
|
||||||
desc: |-
|
|
||||||
Snipe the message you replied to with this command.
|
|
||||||
Otherwise, if you don't reply to a message, it will snipe the last message sent in the channel (out of the last few messages) which has text or an image.
|
|
||||||
ex:
|
|
||||||
- ''
|
|
||||||
params:
|
params:
|
||||||
- { }
|
- { }
|
|
@ -1114,9 +1114,5 @@
|
||||||
"invalid_img_size": "Image must to be {0}x{1} pixels.",
|
"invalid_img_size": "Image must to be {0}x{1} pixels.",
|
||||||
"no_attach_found": "No attachment found. Please send the image along with this command.",
|
"no_attach_found": "No attachment found. Please send the image along with this command.",
|
||||||
"trfl_enabled": "Flag translation enabled on this channel. Reacting to a message with a flag will translate it to that language.",
|
"trfl_enabled": "Flag translation enabled on this channel. Reacting to a message with a flag will translate it to that language.",
|
||||||
"trfl_disabled": "Flag translation disabled.",
|
"trfl_disabled": "Flag translation disabled."
|
||||||
"rakeback_claimed": "You've claimed {0} as rakeback!",
|
|
||||||
"rakeback_none": "You don't have any rakeback to claim yet.",
|
|
||||||
"rakeback_available": "You have {0} rakeback available. Click the button to claim.",
|
|
||||||
"sniped_by": "Sniped by {0}"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
if ($args.Length -eq 0) {
|
|
||||||
Write-Host "Please provide a migration name." -ForegroundColor Red
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$migrationName = $args[0]
|
|
||||||
dotnet ef migrations add $migrationName -c SqliteContext -p EllieBot.csproj
|
|
||||||
dotnet ef migrations add $migrationName -c PostgreSqlContext -p EllieBot.csproj
|
|
||||||
}
|
|
Loading…
Reference in a new issue