mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-11 09:48:26 -04:00
Compare commits
40 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
7f5e065c4d | ||
|
86b214163a | ||
|
9dbb08d85f | ||
|
89ca56c77c | ||
|
9e96679099 | ||
|
1280d2b397 | ||
|
c731127607 | ||
|
c15930306a | ||
|
701501d678 | ||
|
fa12fcea58 | ||
|
274219c40b | ||
|
96c9b47da2 | ||
|
b5d1469df1 | ||
|
d7747bd25a | ||
|
7d162d1f04 | ||
|
704d061d46 | ||
|
c39c9061fd | ||
|
619ddba4f8 | ||
|
3acef04b32 | ||
|
83a1d959b1 | ||
|
a1632722bc | ||
|
ee0a28afab | ||
|
2b301c0aab | ||
|
b6b6b4e19e | ||
|
32fc8b6e03 | ||
|
297e2fde0e | ||
|
729f26caab | ||
|
4b12e4e923 | ||
|
12f4ce7f2a | ||
|
00944e08c3 | ||
|
569abd7194 | ||
|
474a1db41d | ||
|
0f6255947e | ||
|
f68f219a25 | ||
|
8f16b11d02 | ||
|
df5eced904 | ||
|
1dcd158f43 | ||
|
757c9b564d | ||
|
07cef3eb5e | ||
|
85c525e19b |
727
CHANGELOG.md
727
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -1,3 +0,0 @@
|
|||||||
dotnet ef migrations remove -c SqliteContext -f -p src/NadekoBot/NadekoBot.csproj
|
|
||||||
dotnet ef migrations remove -c PostgreSqlContext -f -p src/NadekoBot/NadekoBot.csproj
|
|
||||||
|
|
47
src/NadekoBot.GrpcApiBase/protos/canvas.proto
Normal file
47
src/NadekoBot.GrpcApiBase/protos/canvas.proto
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option csharp_namespace = "NadekoBot.GrpcApi";
|
||||||
|
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
|
||||||
|
package ncanvas;
|
||||||
|
|
||||||
|
service GrpcNCanvas {
|
||||||
|
rpc GetCanvas(google.protobuf.Empty) returns (CanvasReply);
|
||||||
|
rpc GetPixel(GetPixelRequest) returns (GetPixelReply);
|
||||||
|
rpc SetPixel(SetPixelRequest) returns (SetPixelReply);
|
||||||
|
}
|
||||||
|
|
||||||
|
message CanvasReply {
|
||||||
|
repeated uint32 pixels = 1;
|
||||||
|
int32 width = 2;
|
||||||
|
int32 height = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetPixelRequest {
|
||||||
|
int32 x = 1;
|
||||||
|
int32 y = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetPixelReply {
|
||||||
|
string color = 1;
|
||||||
|
uint32 packedColor = 2;
|
||||||
|
int32 positionX = 3;
|
||||||
|
int32 positionY = 4;
|
||||||
|
int64 price = 5;
|
||||||
|
string text = 6;
|
||||||
|
string position = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetPixelRequest {
|
||||||
|
string position = 1;
|
||||||
|
string color = 2;
|
||||||
|
string text = 3;
|
||||||
|
int64 price = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetPixelReply {
|
||||||
|
string error = 1;
|
||||||
|
bool success = 2;
|
||||||
|
optional GetPixelReply pixel = 3;
|
||||||
|
}
|
@@ -1,26 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
option csharp_namespace = "NadekoBot.GrpcApi";
|
|
||||||
|
|
||||||
package econ;
|
|
||||||
|
|
||||||
service GrpcEcon {
|
|
||||||
rpc GetEconomy(EconomyRequest) returns (EconomyReply);
|
|
||||||
}
|
|
||||||
|
|
||||||
message EconomyRequest {
|
|
||||||
string guildId = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message EconomyReply {
|
|
||||||
uint64 totalOwned = 1;
|
|
||||||
uint64 byTopOnePercent = 2;
|
|
||||||
uint64 plantedAmount = 3;
|
|
||||||
uint64 ownedByTheBot = 4;
|
|
||||||
uint64 inTheBank = 5;
|
|
||||||
uint64 totalEconomy = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message CurrencyLbRequest {
|
|
||||||
int32 page = 1;
|
|
||||||
}
|
|
60
src/NadekoBot.GrpcApiBase/protos/fin.proto
Normal file
60
src/NadekoBot.GrpcApiBase/protos/fin.proto
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option csharp_namespace = "NadekoBot.GrpcApi";
|
||||||
|
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
|
package fin;
|
||||||
|
|
||||||
|
service GrpcFin {
|
||||||
|
rpc GetTransactions(GetTransactionsRequest) returns (GetTransactionsReply);
|
||||||
|
rpc GetHoldings(GetHoldingsRequest) returns (GetHoldingsReply);
|
||||||
|
rpc Withdraw(WithdrawRequest) returns (WithdrawReply);
|
||||||
|
rpc Deposit(DepositRequest) returns (DepositReply);
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetTransactionsRequest {
|
||||||
|
int32 page = 1;
|
||||||
|
uint64 userId = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetTransactionsReply {
|
||||||
|
repeated TransactionReply transactions = 1;
|
||||||
|
int32 total = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TransactionReply {
|
||||||
|
int64 amount = 1;
|
||||||
|
string note = 2;
|
||||||
|
string type = 3;
|
||||||
|
string extra = 4;
|
||||||
|
google.protobuf.Timestamp timestamp = 5;
|
||||||
|
string id = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetHoldingsRequest {
|
||||||
|
uint64 userId = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetHoldingsReply {
|
||||||
|
int64 cash = 1;
|
||||||
|
int64 bank = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WithdrawRequest {
|
||||||
|
uint64 userId = 1;
|
||||||
|
int64 amount = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WithdrawReply {
|
||||||
|
bool success = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DepositRequest {
|
||||||
|
uint64 userId = 1;
|
||||||
|
int64 amount = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DepositReply {
|
||||||
|
bool success = 1;
|
||||||
|
}
|
@@ -3,13 +3,11 @@ syntax = "proto3";
|
|||||||
option csharp_namespace = "NadekoBot.GrpcApi";
|
option csharp_namespace = "NadekoBot.GrpcApi";
|
||||||
|
|
||||||
import "google/protobuf/empty.proto";
|
import "google/protobuf/empty.proto";
|
||||||
import "google/protobuf/timestamp.proto";
|
|
||||||
|
|
||||||
package other;
|
package other;
|
||||||
|
|
||||||
service GrpcOther {
|
service GrpcOther {
|
||||||
rpc BotOnGuild(BotOnGuildRequest) returns (BotOnGuildReply);
|
rpc BotOnGuild(BotOnGuildRequest) returns (BotOnGuildReply);
|
||||||
rpc GetGuilds(google.protobuf.Empty) returns (GetGuildsReply);
|
|
||||||
rpc GetTextChannels(GetTextChannelsRequest) returns (GetTextChannelsReply);
|
rpc GetTextChannels(GetTextChannelsRequest) returns (GetTextChannelsReply);
|
||||||
rpc GetRoles(GetRolesRequest) returns (GetRolesReply);
|
rpc GetRoles(GetRolesRequest) returns (GetRolesReply);
|
||||||
|
|
||||||
@@ -17,10 +15,15 @@ service GrpcOther {
|
|||||||
rpc GetXpLb(GetLbRequest) returns (XpLbReply);
|
rpc GetXpLb(GetLbRequest) returns (XpLbReply);
|
||||||
rpc GetWaifuLb(GetLbRequest) returns (WaifuLbReply);
|
rpc GetWaifuLb(GetLbRequest) returns (WaifuLbReply);
|
||||||
|
|
||||||
rpc GetShardStatuses(google.protobuf.Empty) returns (GetShardStatusesReply);
|
rpc GetShardStats(google.protobuf.Empty) returns (stream ShardStatsReply);
|
||||||
|
rpc GetCommandFeed(google.protobuf.Empty) returns (stream CommandFeedEntry);
|
||||||
rpc GetServerInfo(ServerInfoRequest) returns (GetServerInfoReply);
|
rpc GetServerInfo(ServerInfoRequest) returns (GetServerInfoReply);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message CommandFeedEntry {
|
||||||
|
string command = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message GetRolesRequest {
|
message GetRolesRequest {
|
||||||
uint64 guildId = 1;
|
uint64 guildId = 1;
|
||||||
}
|
}
|
||||||
@@ -37,26 +40,13 @@ message BotOnGuildReply {
|
|||||||
bool success = 1;
|
bool success = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetGuildsReply {
|
message ShardStatsReply {
|
||||||
repeated GuildReply guilds = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GuildReply {
|
|
||||||
uint64 id = 1;
|
|
||||||
string name = 2;
|
|
||||||
string iconUrl = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetShardStatusesReply {
|
|
||||||
repeated ShardStatusReply shards = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ShardStatusReply {
|
|
||||||
int32 id = 1;
|
int32 id = 1;
|
||||||
string status = 2;
|
string status = 2;
|
||||||
|
|
||||||
int32 guildCount = 3;
|
int32 guildCount = 3;
|
||||||
google.protobuf.Timestamp lastUpdate = 4;
|
string uptime = 4;
|
||||||
|
int64 commands = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetTextChannelsRequest{
|
message GetTextChannelsRequest{
|
||||||
|
120
src/NadekoBot.GrpcApiBase/protos/xp.proto
Normal file
120
src/NadekoBot.GrpcApiBase/protos/xp.proto
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option csharp_namespace = "NadekoBot.GrpcApi";
|
||||||
|
|
||||||
|
package xp;
|
||||||
|
|
||||||
|
service GrpcXp {
|
||||||
|
rpc GetXpLb(GetXpLbRequest) returns (GetXpLbReply);
|
||||||
|
rpc ResetUserXp(ResetUserXpRequest) returns (ResetUserXpReply);
|
||||||
|
|
||||||
|
rpc GetXpSettings(GetXpSettingsRequest) returns (GetXpSettingsReply);
|
||||||
|
|
||||||
|
rpc AddExclusion(AddExclusionRequest) returns (AddExclusionReply);
|
||||||
|
rpc DeleteExclusion(DeleteExclusionRequest) returns (DeleteExclusionReply);
|
||||||
|
|
||||||
|
rpc AddReward(AddRewardRequest) returns (AddRewardReply);
|
||||||
|
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 {
|
||||||
|
uint64 guildId = 1;
|
||||||
|
int32 page = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetXpLbReply {
|
||||||
|
repeated XpLbUserReply users = 1;
|
||||||
|
int32 total = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message XpLbUserReply {
|
||||||
|
uint64 userId = 1;
|
||||||
|
string username = 2;
|
||||||
|
int64 xp = 3;
|
||||||
|
int64 level = 4;
|
||||||
|
int64 levelPercent = 5;
|
||||||
|
string avatar = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ResetUserXpRequest {
|
||||||
|
uint64 guildId = 1;
|
||||||
|
uint64 userId = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ResetUserXpReply {
|
||||||
|
bool success = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetXpSettingsReply {
|
||||||
|
repeated ExclItemReply exclusions = 1;
|
||||||
|
repeated RewItemReply rewards = 2;
|
||||||
|
bool serverExcluded = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetXpSettingsRequest {
|
||||||
|
uint64 guildId = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ExclItemReply {
|
||||||
|
string type = 1;
|
||||||
|
uint64 id = 2;
|
||||||
|
string name = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RewItemReply {
|
||||||
|
int32 level = 1;
|
||||||
|
string type = 2;
|
||||||
|
string value = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddExclusionRequest {
|
||||||
|
uint64 guildId = 1;
|
||||||
|
string type = 2;
|
||||||
|
uint64 id = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddExclusionReply {
|
||||||
|
bool success = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteExclusionRequest {
|
||||||
|
uint64 guildId = 1;
|
||||||
|
string type = 2;
|
||||||
|
uint64 id = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteExclusionReply {
|
||||||
|
bool success = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddRewardRequest {
|
||||||
|
uint64 guildId = 1;
|
||||||
|
int32 level = 2;
|
||||||
|
string type = 3;
|
||||||
|
string value = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddRewardReply {
|
||||||
|
bool success = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteRewardRequest {
|
||||||
|
uint64 guildId = 1;
|
||||||
|
int32 level = 2;
|
||||||
|
string type = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteRewardReply {
|
||||||
|
bool success = 1;
|
||||||
|
}
|
@@ -25,7 +25,6 @@ public static class DiscordUserExtensions
|
|||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
Username = username,
|
Username = username,
|
||||||
Discriminator = discrim,
|
|
||||||
AvatarId = avatarId,
|
AvatarId = avatarId,
|
||||||
TotalXp = 0,
|
TotalXp = 0,
|
||||||
CurrencyAmount = 0
|
CurrencyAmount = 0
|
||||||
@@ -33,7 +32,6 @@ public static class DiscordUserExtensions
|
|||||||
old => new()
|
old => new()
|
||||||
{
|
{
|
||||||
Username = username,
|
Username = username,
|
||||||
Discriminator = discrim,
|
|
||||||
AvatarId = avatarId
|
AvatarId = avatarId
|
||||||
},
|
},
|
||||||
() => new()
|
() => new()
|
||||||
@@ -49,8 +47,7 @@ public static class DiscordUserExtensions
|
|||||||
() => new()
|
() => new()
|
||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
Username = "Unknown",
|
Username = "??Unknown",
|
||||||
Discriminator = "????",
|
|
||||||
AvatarId = string.Empty,
|
AvatarId = string.Empty,
|
||||||
TotalXp = 0,
|
TotalXp = 0,
|
||||||
CurrencyAmount = 0
|
CurrencyAmount = 0
|
||||||
|
@@ -44,9 +44,6 @@ public static class UserXpExtensions
|
|||||||
.CountAsyncLinqToDB()
|
.CountAsyncLinqToDB()
|
||||||
+ 1;
|
+ 1;
|
||||||
|
|
||||||
public static void ResetGuildUserXp(this DbSet<UserXpStats> xps, ulong userId, ulong guildId)
|
|
||||||
=> xps.Delete(x => x.UserId == userId && x.GuildId == guildId);
|
|
||||||
|
|
||||||
public static void ResetGuildXp(this DbSet<UserXpStats> xps, ulong guildId)
|
public static void ResetGuildXp(this DbSet<UserXpStats> xps, ulong guildId)
|
||||||
=> xps.Delete(x => x.GuildId == guildId);
|
=> xps.Delete(x => x.GuildId == guildId);
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@ public class DiscordUser : DbEntity
|
|||||||
{
|
{
|
||||||
public ulong UserId { get; set; }
|
public ulong UserId { get; set; }
|
||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
public string Discriminator { get; set; }
|
// public string Discriminator { get; set; }
|
||||||
public string AvatarId { get; set; }
|
public string AvatarId { get; set; }
|
||||||
|
|
||||||
public int? ClubId { get; set; }
|
public int? ClubId { get; set; }
|
||||||
@@ -27,9 +27,6 @@ public class DiscordUser : DbEntity
|
|||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(Discriminator) || Discriminator == "0000")
|
return Username;
|
||||||
return Username;
|
|
||||||
|
|
||||||
return Username + "#" + Discriminator;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
8
src/NadekoBot/Db/Models/FlagTranslateChannel.cs
Normal file
8
src/NadekoBot/Db/Models/FlagTranslateChannel.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#nullable disable
|
||||||
|
namespace NadekoBot.Db.Models;
|
||||||
|
|
||||||
|
public class FlagTranslateChannel : DbEntity
|
||||||
|
{
|
||||||
|
public ulong GuildId { get; set; }
|
||||||
|
public ulong ChannelId { get; set; }
|
||||||
|
}
|
19
src/NadekoBot/Db/Models/NCanvas/NCanvas.cs
Normal file
19
src/NadekoBot/Db/Models/NCanvas/NCanvas.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace NadekoBot.Db.Models;
|
||||||
|
|
||||||
|
public class NCPixel
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public required int Position { get; init; }
|
||||||
|
|
||||||
|
public required long Price { get; init; }
|
||||||
|
|
||||||
|
public required ulong OwnerId { get; init; }
|
||||||
|
public required uint Color { get; init; }
|
||||||
|
|
||||||
|
[MaxLength(256)]
|
||||||
|
public required string Text { get; init; }
|
||||||
|
}
|
@@ -1,8 +1,12 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace NadekoBot.Db.Models;
|
namespace NadekoBot.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; }
|
||||||
|
@@ -61,6 +61,7 @@ public abstract class NadekoContext : DbContext
|
|||||||
public DbSet<TodoModel> Todos { get; set; }
|
public DbSet<TodoModel> Todos { get; set; }
|
||||||
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; }
|
||||||
|
|
||||||
@@ -73,6 +74,40 @@ public abstract class NadekoContext : DbContext
|
|||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
|
#region Rakeback
|
||||||
|
|
||||||
|
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>()
|
||||||
|
.HasIndex(x => new { x.GuildId, x.ChannelId })
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region NCanvas
|
||||||
|
|
||||||
|
modelBuilder.Entity<NCPixel>()
|
||||||
|
.HasAlternateKey(x => x.Position);
|
||||||
|
|
||||||
|
modelBuilder.Entity<NCPixel>()
|
||||||
|
.HasIndex(x => x.OwnerId);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region QUOTES
|
#region QUOTES
|
||||||
|
|
||||||
var quoteEntity = modelBuilder.Entity<Quote>();
|
var quoteEntity = modelBuilder.Entity<Quote>();
|
||||||
|
@@ -5,6 +5,11 @@ namespace NadekoBot.Migrations;
|
|||||||
|
|
||||||
public static class MigrationQueries
|
public static class MigrationQueries
|
||||||
{
|
{
|
||||||
|
public static void UpdateUsernames(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql("UPDATE DiscordUser SET Username = '??' + Username WHERE Discriminator = '????';");
|
||||||
|
}
|
||||||
|
|
||||||
public static void MigrateRero(MigrationBuilder migrationBuilder)
|
public static void MigrateRero(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
if (migrationBuilder.IsSqlite())
|
if (migrationBuilder.IsSqlite())
|
||||||
|
3824
src/NadekoBot/Migrations/PostgreSql/20241028033704_ncanvas.Designer.cs
generated
Normal file
3824
src/NadekoBot/Migrations/PostgreSql/20241028033704_ncanvas.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,54 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace NadekoBot.Migrations.PostgreSql
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class ncanvas : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ncpixel",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
position = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
price = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
ownerid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||||
|
color = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
text = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_ncpixel", x => x.id);
|
||||||
|
table.UniqueConstraint("ak_ncpixel_position", x => x.position);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_discorduser_username",
|
||||||
|
table: "discorduser",
|
||||||
|
column: "username");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_ncpixel_ownerid",
|
||||||
|
table: "ncpixel",
|
||||||
|
column: "ownerid");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "ncpixel");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "ix_discorduser_username",
|
||||||
|
table: "discorduser");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3851
src/NadekoBot/Migrations/PostgreSql/20241102022956_no-discrim-and-flag-translate.Designer.cs
generated
Normal file
3851
src/NadekoBot/Migrations/PostgreSql/20241102022956_no-discrim-and-flag-translate.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,56 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace NadekoBot.Migrations.PostgreSql
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class nodiscrimandflagtranslate : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
MigrationQueries.UpdateUsernames(migrationBuilder);
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "discriminator",
|
||||||
|
table: "discorduser");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "flagtranslatechannel",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||||
|
channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||||
|
dateadded = table.Column<DateTime>(type: "timestamp without time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_flagtranslatechannel", x => x.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_flagtranslatechannel_guildid_channelid",
|
||||||
|
table: "flagtranslatechannel",
|
||||||
|
columns: new[] { "guildid", "channelid" },
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "flagtranslatechannel");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "discriminator",
|
||||||
|
table: "discorduser",
|
||||||
|
type: "text",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3902
src/NadekoBot/Migrations/PostgreSql/20241104094232_betstats.Designer.cs
generated
Normal file
3902
src/NadekoBot/Migrations/PostgreSql/20241104094232_betstats.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,48 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace NadekoBot.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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3919
src/NadekoBot/Migrations/PostgreSql/20241105073004_rakeback.Designer.cs
generated
Normal file
3919
src/NadekoBot/Migrations/PostgreSql/20241105073004_rakeback.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace NadekoBot.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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -751,10 +751,6 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
.HasColumnType("timestamp without time zone")
|
.HasColumnType("timestamp without time zone")
|
||||||
.HasColumnName("dateadded");
|
.HasColumnName("dateadded");
|
||||||
|
|
||||||
b.Property<string>("Discriminator")
|
|
||||||
.HasColumnType("text")
|
|
||||||
.HasColumnName("discriminator");
|
|
||||||
|
|
||||||
b.Property<bool>("IsClubAdmin")
|
b.Property<bool>("IsClubAdmin")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("boolean")
|
.HasColumnType("boolean")
|
||||||
@@ -799,6 +795,9 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
b.HasIndex("UserId")
|
b.HasIndex("UserId")
|
||||||
.HasDatabaseName("ix_discorduser_userid");
|
.HasDatabaseName("ix_discorduser_userid");
|
||||||
|
|
||||||
|
b.HasIndex("Username")
|
||||||
|
.HasDatabaseName("ix_discorduser_username");
|
||||||
|
|
||||||
b.ToTable("discorduser", (string)null);
|
b.ToTable("discorduser", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -995,6 +994,37 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
b.ToTable("filteredword", (string)null);
|
b.ToTable("filteredword", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Db.Models.FlagTranslateChannel", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("ChannelId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("channelid");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DateAdded")
|
||||||
|
.HasColumnType("timestamp without time zone")
|
||||||
|
.HasColumnName("dateadded");
|
||||||
|
|
||||||
|
b.Property<decimal>("GuildId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("guildid");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_flagtranslatechannel");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId", "ChannelId")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("ix_flagtranslatechannel_guildid_channelid");
|
||||||
|
|
||||||
|
b.ToTable("flagtranslatechannel", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Db.Models.FollowedStream", b =>
|
modelBuilder.Entity("NadekoBot.Db.Models.FollowedStream", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -1627,6 +1657,49 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
b.ToTable("muteduserid", (string)null);
|
b.ToTable("muteduserid", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Db.Models.NCPixel", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("Color")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("color");
|
||||||
|
|
||||||
|
b.Property<decimal>("OwnerId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("ownerid");
|
||||||
|
|
||||||
|
b.Property<int>("Position")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("position");
|
||||||
|
|
||||||
|
b.Property<long>("Price")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("price");
|
||||||
|
|
||||||
|
b.Property<string>("Text")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)")
|
||||||
|
.HasColumnName("text");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_ncpixel");
|
||||||
|
|
||||||
|
b.HasAlternateKey("Position")
|
||||||
|
.HasName("ak_ncpixel_position");
|
||||||
|
|
||||||
|
b.HasIndex("OwnerId")
|
||||||
|
.HasDatabaseName("ix_ncpixel_ownerid");
|
||||||
|
|
||||||
|
b.ToTable("ncpixel", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Db.Models.NadekoExpression", b =>
|
modelBuilder.Entity("NadekoBot.Db.Models.NadekoExpression", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -3154,6 +3227,74 @@ namespace NadekoBot.Migrations.PostgreSql
|
|||||||
b.ToTable("greetsettings", (string)null);
|
b.ToTable("greetsettings", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Services.Rakeback", b =>
|
||||||
|
{
|
||||||
|
b.Property<decimal>("UserId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("userid");
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("numeric")
|
||||||
|
.HasColumnName("amount");
|
||||||
|
|
||||||
|
b.HasKey("UserId")
|
||||||
|
.HasName("pk_rakeback");
|
||||||
|
|
||||||
|
b.ToTable("rakeback", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Services.UserBetStats", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("Game")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("game");
|
||||||
|
|
||||||
|
b.Property<long>("LoseCount")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("losecount");
|
||||||
|
|
||||||
|
b.Property<long>("MaxBet")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("maxbet");
|
||||||
|
|
||||||
|
b.Property<long>("MaxWin")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("maxwin");
|
||||||
|
|
||||||
|
b.Property<decimal>("PaidOut")
|
||||||
|
.HasColumnType("numeric")
|
||||||
|
.HasColumnName("paidout");
|
||||||
|
|
||||||
|
b.Property<decimal>("TotalBet")
|
||||||
|
.HasColumnType("numeric")
|
||||||
|
.HasColumnName("totalbet");
|
||||||
|
|
||||||
|
b.Property<decimal>("UserId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("userid");
|
||||||
|
|
||||||
|
b.Property<long>("WinCount")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("wincount");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_userbetstats");
|
||||||
|
|
||||||
|
b.HasIndex("UserId", "Game")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("ix_userbetstats_userid_game");
|
||||||
|
|
||||||
|
b.ToTable("userbetstats", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Db.Models.AntiAltSetting", b =>
|
modelBuilder.Entity("NadekoBot.Db.Models.AntiAltSetting", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NadekoBot.Db.Models.GuildConfig", null)
|
b.HasOne("NadekoBot.Db.Models.GuildConfig", null)
|
||||||
|
2953
src/NadekoBot/Migrations/Sqlite/20241028033656_ncanvas.Designer.cs
generated
Normal file
2953
src/NadekoBot/Migrations/Sqlite/20241028033656_ncanvas.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
53
src/NadekoBot/Migrations/Sqlite/20241028033656_ncanvas.cs
Normal file
53
src/NadekoBot/Migrations/Sqlite/20241028033656_ncanvas.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace NadekoBot.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class ncanvas : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "NCPixel",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
Position = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
Price = table.Column<long>(type: "INTEGER", nullable: false),
|
||||||
|
OwnerId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||||
|
Color = table.Column<uint>(type: "INTEGER", nullable: false),
|
||||||
|
Text = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_NCPixel", x => x.Id);
|
||||||
|
table.UniqueConstraint("AK_NCPixel_Position", x => x.Position);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_DiscordUser_Username",
|
||||||
|
table: "DiscordUser",
|
||||||
|
column: "Username");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_NCPixel_OwnerId",
|
||||||
|
table: "NCPixel",
|
||||||
|
column: "OwnerId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "NCPixel");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_DiscordUser_Username",
|
||||||
|
table: "DiscordUser");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2973
src/NadekoBot/Migrations/Sqlite/20241102022949_no-discrim-and-flag-translate.Designer.cs
generated
Normal file
2973
src/NadekoBot/Migrations/Sqlite/20241102022949_no-discrim-and-flag-translate.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,55 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace NadekoBot.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class nodiscrimandflagtranslate : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
MigrationQueries.UpdateUsernames(migrationBuilder);
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Discriminator",
|
||||||
|
table: "DiscordUser");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "FlagTranslateChannel",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||||
|
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||||
|
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_FlagTranslateChannel", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_FlagTranslateChannel_GuildId_ChannelId",
|
||||||
|
table: "FlagTranslateChannel",
|
||||||
|
columns: new[] { "GuildId", "ChannelId" },
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "FlagTranslateChannel");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Discriminator",
|
||||||
|
table: "DiscordUser",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3011
src/NadekoBot/Migrations/Sqlite/20241104094222_betstats.Designer.cs
generated
Normal file
3011
src/NadekoBot/Migrations/Sqlite/20241104094222_betstats.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
47
src/NadekoBot/Migrations/Sqlite/20241104094222_betstats.cs
Normal file
47
src/NadekoBot/Migrations/Sqlite/20241104094222_betstats.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace NadekoBot.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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3025
src/NadekoBot/Migrations/Sqlite/20241105072953_rakeback.Designer.cs
generated
Normal file
3025
src/NadekoBot/Migrations/Sqlite/20241105072953_rakeback.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
34
src/NadekoBot/Migrations/Sqlite/20241105072953_rakeback.cs
Normal file
34
src/NadekoBot/Migrations/Sqlite/20241105072953_rakeback.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace NadekoBot.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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -560,9 +560,6 @@ namespace NadekoBot.Migrations
|
|||||||
b.Property<DateTime?>("DateAdded")
|
b.Property<DateTime?>("DateAdded")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<string>("Discriminator")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<bool>("IsClubAdmin")
|
b.Property<bool>("IsClubAdmin")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER")
|
.HasColumnType("INTEGER")
|
||||||
@@ -596,6 +593,8 @@ namespace NadekoBot.Migrations
|
|||||||
|
|
||||||
b.HasIndex("UserId");
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.HasIndex("Username");
|
||||||
|
|
||||||
b.ToTable("DiscordUser");
|
b.ToTable("DiscordUser");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -741,6 +740,29 @@ namespace NadekoBot.Migrations
|
|||||||
b.ToTable("FilteredWord");
|
b.ToTable("FilteredWord");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Db.Models.FlagTranslateChannel", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ulong>("ChannelId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DateAdded")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<ulong>("GuildId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId", "ChannelId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("FlagTranslateChannel");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Db.Models.FollowedStream", b =>
|
modelBuilder.Entity("NadekoBot.Db.Models.FollowedStream", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -1213,6 +1235,38 @@ namespace NadekoBot.Migrations
|
|||||||
b.ToTable("MutedUserId");
|
b.ToTable("MutedUserId");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Db.Models.NCPixel", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<uint>("Color")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ulong>("OwnerId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Position")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("Price")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Text")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasAlternateKey("Position");
|
||||||
|
|
||||||
|
b.HasIndex("OwnerId");
|
||||||
|
|
||||||
|
b.ToTable("NCPixel");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Db.Models.NadekoExpression", b =>
|
modelBuilder.Entity("NadekoBot.Db.Models.NadekoExpression", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -2345,6 +2399,58 @@ namespace NadekoBot.Migrations
|
|||||||
b.ToTable("GreetSettings");
|
b.ToTable("GreetSettings");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Services.Rakeback", b =>
|
||||||
|
{
|
||||||
|
b.Property<ulong>("UserId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("UserId");
|
||||||
|
|
||||||
|
b.ToTable("Rakeback");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Services.UserBetStats", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Game")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("LoseCount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("MaxBet")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("MaxWin")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<decimal>("PaidOut")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("TotalBet")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<ulong>("UserId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("WinCount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId", "Game")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("UserBetStats");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Db.Models.AntiAltSetting", b =>
|
modelBuilder.Entity("NadekoBot.Db.Models.AntiAltSetting", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NadekoBot.Db.Models.GuildConfig", null)
|
b.HasOne("NadekoBot.Db.Models.GuildConfig", null)
|
||||||
|
@@ -24,6 +24,13 @@ public partial class Administration
|
|||||||
await Response().Error(strs.hierarchy).SendAsync();
|
await Response().Error(strs.hierarchy).SendAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the user can't aar the role which is greater or equal to the bot's highest role
|
||||||
|
if (role.Position >= ((SocketGuild)ctx.Guild).CurrentUser.GetRoles().Max(x => x.Position))
|
||||||
|
{
|
||||||
|
await Response().Error(strs.hierarchy).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var roles = await _service.ToggleAarAsync(ctx.Guild.Id, role.Id);
|
var roles = await _service.ToggleAarAsync(ctx.Guild.Id, role.Id);
|
||||||
if (roles.Count == 0)
|
if (roles.Count == 0)
|
||||||
|
@@ -339,7 +339,7 @@ public class GreetService : INService, IReadyExecutor
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Error(ex, "Error sending greet dm");
|
Log.Warning(ex, "Unable to send Greet DM. Probably the user has closed DMs");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -71,7 +71,6 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
|
|||||||
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
|
||||||
{
|
{
|
||||||
@@ -453,7 +452,6 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
|
|||||||
{
|
{
|
||||||
x.UserId,
|
x.UserId,
|
||||||
x.Username,
|
x.Username,
|
||||||
x.Discriminator
|
|
||||||
})
|
})
|
||||||
.Where(x => users.Select(y => y.Id).Contains(x.UserId))
|
.Where(x => users.Select(y => y.Id).Contains(x.UserId))
|
||||||
.ToArrayAsyncEF();
|
.ToArrayAsyncEF();
|
||||||
@@ -465,12 +463,11 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
|
|||||||
UserId = x.Id,
|
UserId = x.Id,
|
||||||
AvatarId = x.AvatarId,
|
AvatarId = x.AvatarId,
|
||||||
Username = x.Username,
|
Username = x.Username,
|
||||||
Discriminator = x.Discriminator
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var added = (await ctx.BulkCopyAsync(usersToAdd)).RowsCopied;
|
var added = (await ctx.BulkCopyAsync(usersToAdd)).RowsCopied;
|
||||||
var toUpdateUserIds = presentDbUsers
|
var toUpdateUserIds = presentDbUsers
|
||||||
.Where(x => x.Username == "Unknown" && x.Discriminator == "????")
|
.Where(x => x.Username.StartsWith("??"))
|
||||||
.Select(x => x.UserId)
|
.Select(x => x.UserId)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
@@ -481,7 +478,6 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
|
|||||||
.UpdateAsync(x => new DiscordUser()
|
.UpdateAsync(x => new DiscordUser()
|
||||||
{
|
{
|
||||||
Username = user.Username,
|
Username = user.Username,
|
||||||
Discriminator = user.Discriminator,
|
|
||||||
|
|
||||||
// .award tends to set AvatarId and DateAdded to NULL, so account for that.
|
// .award tends to set AvatarId and DateAdded to NULL, so account for that.
|
||||||
AvatarId = user.AvatarId,
|
AvatarId = user.AvatarId,
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using NadekoBot.Common.ModuleBehaviors;
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
using NadekoBot.Common.TypeReaders.Models;
|
|
||||||
using NadekoBot.Modules.Permissions.Services;
|
using NadekoBot.Modules.Permissions.Services;
|
||||||
using NadekoBot.Db.Models;
|
using NadekoBot.Db.Models;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
@@ -6,6 +6,10 @@ namespace NadekoBot.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,
|
||||||
@@ -100,7 +104,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, user.Bet, new("animalrace", "refund"));
|
await _currency.AddAsync(user.UserId, (long)(user.Bet * BASE_MULTIPLIER), new("animalrace", "refund"));
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = OnStartingFailed?.Invoke(this);
|
_ = OnStartingFailed?.Invoke(this);
|
||||||
@@ -116,7 +120,7 @@ public sealed class AnimalRace : IDisposable
|
|||||||
{
|
{
|
||||||
foreach (var user in _users)
|
foreach (var user in _users)
|
||||||
{
|
{
|
||||||
user.Progress += rng.Next(1, 11);
|
user.Progress += rng.Next(1, 10);
|
||||||
if (user.Progress >= 60)
|
if (user.Progress >= 60)
|
||||||
user.Progress = 60;
|
user.Progress = 60;
|
||||||
}
|
}
|
||||||
@@ -126,13 +130,15 @@ public sealed class AnimalRace : IDisposable
|
|||||||
FinishedUsers.AddRange(finished);
|
FinishedUsers.AddRange(finished);
|
||||||
|
|
||||||
_ = OnStateUpdate?.Invoke(this);
|
_ = OnStateUpdate?.Invoke(this);
|
||||||
await Task.Delay(2500);
|
await Task.Delay(1750);
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
FinishedUsers[0].Bet * (_users.Count - 1),
|
(long)(FinishedUsers[0].Bet * Multi),
|
||||||
new("animalrace", "win"));
|
new("animalrace", "win"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,6 +146,8 @@ 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 NadekoBot.Modules.Gambling;
|
|||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class AnimalRacingCommands : GamblingSubmodule<AnimalRaceService>
|
public partial class AnimalRacingCommands : GamblingModule<AnimalRaceService>
|
||||||
{
|
{
|
||||||
private readonly ICurrencyService _cs;
|
private readonly ICurrencyService _cs;
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
@@ -74,10 +74,14 @@ public partial class Gambling
|
|||||||
if (race.FinishedUsers[0].Bet > 0)
|
if (race.FinishedUsers[0].Bet > 0)
|
||||||
{
|
{
|
||||||
return Response()
|
return Response()
|
||||||
.Confirm(GetText(strs.animal_race),
|
.Embed(_sender.CreateEmbed()
|
||||||
GetText(strs.animal_race_won_money(Format.Bold(winner.Username),
|
.WithOkColor()
|
||||||
winner.Animal.Icon,
|
.WithTitle(GetText(strs.animal_race))
|
||||||
(race.FinishedUsers[0].Bet * (race.Users.Count - 1)) + CurrencySign)))
|
.WithDescription(GetText(strs.animal_race_won_money(
|
||||||
|
Format.Bold(winner.Username),
|
||||||
|
winner.Animal.Icon,
|
||||||
|
N(race.FinishedUsers[0].Bet * race.Multi))))
|
||||||
|
.WithFooter($"x{race.Multi:F2}"))
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,14 +117,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;
|
||||||
|
|
||||||
@@ -129,10 +133,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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
175
src/NadekoBot/Modules/Gambling/BetStatsCommands.cs
Normal file
175
src/NadekoBot/Modules/Gambling/BetStatsCommands.cs
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
#nullable disable
|
||||||
|
using NadekoBot.Modules.Gambling.Common;
|
||||||
|
using NadekoBot.Modules.Gambling.Services;
|
||||||
|
|
||||||
|
namespace NadekoBot.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 NadekoBot.Modules.Gambling;
|
|||||||
|
|
||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
public partial class BlackJackCommands : GamblingSubmodule<BlackJackService>
|
public partial class BlackJackCommands : GamblingModule<BlackJackService>
|
||||||
{
|
{
|
||||||
public enum BjAction
|
public enum BjAction
|
||||||
{
|
{
|
||||||
|
@@ -9,7 +9,7 @@ namespace NadekoBot.Modules.Gambling;
|
|||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class Connect4Commands : GamblingSubmodule<GamblingService>
|
public partial class Connect4Commands : GamblingModule<GamblingService>
|
||||||
{
|
{
|
||||||
private static readonly string[] _numbers =
|
private static readonly string[] _numbers =
|
||||||
[
|
[
|
||||||
|
@@ -12,7 +12,7 @@ namespace NadekoBot.Modules.Gambling;
|
|||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class DrawCommands : GamblingSubmodule<IGamblingService>
|
public partial class DrawCommands : GamblingModule<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 NadekoBot.Modules.Gambling;
|
|||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class CurrencyEventsCommands : GamblingSubmodule<CurrencyEventsService>
|
public partial class CurrencyEventsCommands : GamblingModule<CurrencyEventsService>
|
||||||
{
|
{
|
||||||
public CurrencyEventsCommands(GamblingConfigService gamblingConf)
|
public CurrencyEventsCommands(GamblingConfigService gamblingConf)
|
||||||
: base(gamblingConf)
|
: base(gamblingConf)
|
||||||
|
@@ -11,7 +11,7 @@ namespace NadekoBot.Modules.Gambling;
|
|||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class FlipCoinCommands : GamblingSubmodule<IGamblingService>
|
public partial class FlipCoinCommands : GamblingModule<IGamblingService>
|
||||||
{
|
{
|
||||||
public enum BetFlipGuess : byte
|
public enum BetFlipGuess : byte
|
||||||
{
|
{
|
||||||
|
@@ -14,6 +14,13 @@ using System.Text;
|
|||||||
using NadekoBot.Modules.Gambling.Rps;
|
using NadekoBot.Modules.Gambling.Rps;
|
||||||
using NadekoBot.Common.TypeReaders;
|
using NadekoBot.Common.TypeReaders;
|
||||||
using NadekoBot.Modules.Patronage;
|
using NadekoBot.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 NadekoBot.Modules.Gambling;
|
namespace NadekoBot.Modules.Gambling;
|
||||||
|
|
||||||
@@ -26,10 +33,12 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
private readonly NumberFormatInfo _enUsCulture;
|
private readonly NumberFormatInfo _enUsCulture;
|
||||||
private readonly DownloadTracker _tracker;
|
private readonly DownloadTracker _tracker;
|
||||||
private readonly GamblingConfigService _configService;
|
private readonly GamblingConfigService _configService;
|
||||||
|
private readonly FontProvider _fonts;
|
||||||
private readonly IBankService _bank;
|
private readonly IBankService _bank;
|
||||||
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,
|
||||||
@@ -38,10 +47,12 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
DownloadTracker tracker,
|
DownloadTracker tracker,
|
||||||
GamblingConfigService configService,
|
GamblingConfigService configService,
|
||||||
|
FontProvider fonts,
|
||||||
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;
|
||||||
@@ -51,13 +62,16 @@ 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 NadekoRandom();
|
||||||
|
|
||||||
_enUsCulture = new CultureInfo("en-US", false).NumberFormat;
|
_enUsCulture = new CultureInfo("en-US", false).NumberFormat;
|
||||||
_enUsCulture.NumberDecimalDigits = 0;
|
_enUsCulture.NumberDecimalDigits = 0;
|
||||||
_enUsCulture.NumberGroupSeparator = " ";
|
_enUsCulture.NumberGroupSeparator = " ";
|
||||||
_tracker = tracker;
|
_tracker = tracker;
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
|
_fonts = fonts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetBalanceStringAsync(ulong userId)
|
public async Task<string> GetBalanceStringAsync(ulong userId)
|
||||||
@@ -66,42 +80,6 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -140,7 +118,21 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
(smc) => RemindTimelyAction(smc, DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(ms)))
|
(smc) => RemindTimelyAction(smc, DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(ms)))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private NadekoInteractionBase CreateTimelyInteraction()
|
||||||
|
=> _inter
|
||||||
|
.Create(ctx.User.Id,
|
||||||
|
new ButtonBuilder(
|
||||||
|
label: "Timely",
|
||||||
|
emote: Emoji.Parse("💰"),
|
||||||
|
customId: "timely:" + _rng.Next(123456, 999999)),
|
||||||
|
async (smc) =>
|
||||||
|
{
|
||||||
|
await smc.DeferAsync();
|
||||||
|
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;
|
||||||
@@ -151,6 +143,71 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Config.Timely.ProtType == TimelyProt.Button)
|
||||||
|
{
|
||||||
|
var interaction = CreateTimelyInteraction();
|
||||||
|
var msg = await Response().Pending(strs.timely_button).Interaction(interaction).SendAsync();
|
||||||
|
await msg.DeleteAsync();
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ClaimTimely()
|
||||||
|
{
|
||||||
|
var period = Config.Timely.Cooldown;
|
||||||
if (await _service.ClaimTimelyAsync(ctx.User.Id, period) is { } remainder)
|
if (await _service.ClaimTimelyAsync(ctx.User.Id, period) is { } remainder)
|
||||||
{
|
{
|
||||||
// Get correct time form remainder
|
// Get correct time form remainder
|
||||||
@@ -169,6 +226,30 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
@@ -179,7 +260,21 @@ 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"));
|
||||||
|
|
||||||
await Response().Confirm(strs.timely(N(val), period)).Interaction(inter).SendAsync();
|
var msg = GetText(strs.timely(N(val), period));
|
||||||
|
if (booster || percentBonus > float.Epsilon)
|
||||||
|
{
|
||||||
|
msg += "\n\n";
|
||||||
|
if (booster)
|
||||||
|
msg += $"*+{N(Config.BoostBonus.BaseTimelyBonus)} bonus for boosting {userInfo.guild}!*";
|
||||||
|
|
||||||
|
if (percentBonus > float.Epsilon)
|
||||||
|
msg +=
|
||||||
|
$"*+{percentBonus:P0} bonus for the [Patreon](https://patreon.com/nadekobot) pledge! <:hart:746995901758832712>*";
|
||||||
|
|
||||||
|
await Response().Confirm(msg).Interaction(inter).SendAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
await Response().Confirm(strs.timely(N(val), period)).Interaction(inter).SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
@@ -290,8 +385,9 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
}
|
}
|
||||||
|
|
||||||
var embed = _sender.CreateEmbed()
|
var embed = _sender.CreateEmbed()
|
||||||
.WithTitle(GetText(strs.transactions(((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString()
|
.WithTitle(GetText(strs.transactions(
|
||||||
?? $"{userId}")))
|
((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString()
|
||||||
|
?? $"{userId}")))
|
||||||
.WithOkColor();
|
.WithOkColor();
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
@@ -547,7 +643,9 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await Response().Error(strs.take_fail(N(amount), Format.Bold(user.ToString()), CurrencySign)).SendAsync();
|
await Response()
|
||||||
|
.Error(strs.take_fail(N(amount), Format.Bold(user.ToString()), CurrencySign))
|
||||||
|
.SendAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -568,7 +666,9 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await Response().Error(strs.take_fail(N(amount), Format.Code(usrId.ToString()), CurrencySign)).SendAsync();
|
await Response()
|
||||||
|
.Error(strs.take_fail(N(amount), Format.Code(usrId.ToString()), CurrencySign))
|
||||||
|
.SendAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -762,6 +862,8 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
private static readonly ImmutableArray<string> _emojis =
|
private static readonly ImmutableArray<string> _emojis =
|
||||||
new[] { "⬆", "↖", "⬅", "↙", "⬇", "↘", "➡", "↗" }.ToImmutableArray();
|
new[] { "⬆", "↖", "⬅", "↙", "⬇", "↘", "➡", "↗" }.ToImmutableArray();
|
||||||
|
|
||||||
|
private readonly NadekoRandom _rng;
|
||||||
|
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
public async Task LuckyLadder([OverrideTypeReader(typeof(BalanceTypeReader))] long amount)
|
public async Task LuckyLadder([OverrideTypeReader(typeof(BalanceTypeReader))] long amount)
|
||||||
@@ -900,4 +1002,45 @@ 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 NadekoInteractionBase 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 NadekoBot.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; } = 8;
|
public int Version { get; set; } = 12;
|
||||||
|
|
||||||
[Comment("""Currency settings""")]
|
[Comment("""Currency settings""")]
|
||||||
public CurrencyConfig Currency { get; set; }
|
public CurrencyConfig Currency { get; set; }
|
||||||
@@ -67,6 +67,11 @@ 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();
|
||||||
@@ -79,6 +84,7 @@ public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
|
|||||||
Slots = new();
|
Slots = new();
|
||||||
LuckyLadder = new();
|
LuckyLadder = new();
|
||||||
BotCuts = new();
|
BotCuts = new();
|
||||||
|
BoostBonus = new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,13 +110,26 @@ 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 int Amount { get; set; } = 0;
|
public long 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
|
||||||
setting to 0 or less will disable this feature
|
setting to 0 or less will disable this feature
|
||||||
""")]
|
""")]
|
||||||
public int Cooldown { get; set; } = 24;
|
public int Cooldown { get; set; } = 24;
|
||||||
|
|
||||||
|
[Comment("""
|
||||||
|
How will timely be protected?
|
||||||
|
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 enum TimelyProt
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Button,
|
||||||
|
Captcha
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cloneable]
|
[Cloneable]
|
||||||
@@ -145,7 +164,7 @@ public partial class BetRollConfig
|
|||||||
},
|
},
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
WhenAbove = 66,
|
WhenAbove = 65,
|
||||||
MultiplyBy = 2
|
MultiplyBy = 2
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@@ -207,7 +226,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.2M, 0.5M, 0.3M, 0.2M, 0.1M];
|
=> Multipliers = [2.4M, 1.7M, 1.5M, 1.1M, 0.5M, 0.3M, 0.2M, 0.1M];
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cloneable]
|
[Cloneable]
|
||||||
@@ -408,4 +427,15 @@ 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,6 +144,11 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
|
|||||||
ConfigPrinters.ToString,
|
ConfigPrinters.ToString,
|
||||||
val => val >= 0);
|
val => val >= 0);
|
||||||
|
|
||||||
|
AddParsedProp("timely.prot",
|
||||||
|
gs => gs.Timely.ProtType,
|
||||||
|
ConfigParsers.InsensitiveEnum,
|
||||||
|
ConfigPrinters.ToString);
|
||||||
|
|
||||||
Migrate();
|
Migrate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,22 +172,6 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.Version < 5)
|
|
||||||
{
|
|
||||||
ModifyConfig(c =>
|
|
||||||
{
|
|
||||||
c.Version = 5;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.Version < 6)
|
|
||||||
{
|
|
||||||
ModifyConfig(c =>
|
|
||||||
{
|
|
||||||
c.Version = 6;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.Version < 7)
|
if (data.Version < 7)
|
||||||
{
|
{
|
||||||
ModifyConfig(c =>
|
ModifyConfig(c =>
|
||||||
@@ -199,5 +188,18 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
|
|||||||
c.Waifu.Decay.UnclaimedDecayPercent = 0;
|
c.Waifu.Decay.UnclaimedDecayPercent = 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.Version < 12)
|
||||||
|
{
|
||||||
|
ModifyConfig(c =>
|
||||||
|
{
|
||||||
|
c.Version = 12;
|
||||||
|
|
||||||
|
if (c.BetRoll.Pairs.Length == 3 && c.BetRoll.Pairs[2].WhenAbove == 66)
|
||||||
|
{
|
||||||
|
c.BetRoll.Pairs[2].WhenAbove = 65;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -16,6 +16,7 @@ public class GamblingService : INService, IReadyExecutor
|
|||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
private readonly IBotCache _cache;
|
private readonly IBotCache _cache;
|
||||||
private readonly GamblingConfigService _gss;
|
private readonly GamblingConfigService _gss;
|
||||||
|
private readonly NadekoRandom _rng;
|
||||||
|
|
||||||
private static readonly TypedKey<long> _curDecayKey = new("currency:last_decay");
|
private static readonly TypedKey<long> _curDecayKey = new("currency:last_decay");
|
||||||
|
|
||||||
@@ -29,11 +30,19 @@ public class GamblingService : INService, IReadyExecutor
|
|||||||
_client = client;
|
_client = client;
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
_gss = gss;
|
_gss = gss;
|
||||||
|
_rng = new NadekoRandom();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task OnReadyAsync()
|
public Task OnReadyAsync()
|
||||||
=> Task.WhenAll(CurrencyDecayLoopAsync(), TransactionClearLoopAsync());
|
=> Task.WhenAll(CurrencyDecayLoopAsync(), TransactionClearLoopAsync());
|
||||||
|
|
||||||
|
|
||||||
|
public string GeneratePassword()
|
||||||
|
{
|
||||||
|
var num = _rng.Next((int)Math.Pow(31, 2), (int)Math.Pow(32, 3));
|
||||||
|
return new kwum(num).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task TransactionClearLoopAsync()
|
private async Task TransactionClearLoopAsync()
|
||||||
{
|
{
|
||||||
if (_client.ShardId != 0)
|
if (_client.ShardId != 0)
|
||||||
@@ -52,7 +61,7 @@ public class GamblingService : INService, IReadyExecutor
|
|||||||
var days = TimeSpan.FromDays(lifetime);
|
var days = TimeSpan.FromDays(lifetime);
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
await uow.Set<CurrencyTransaction>()
|
await uow.Set<CurrencyTransaction>()
|
||||||
.DeleteAsync(ct => ct.DateAdded == null || now - ct.DateAdded < days);
|
.DeleteAsync(ct => ct.DateAdded == null || now - ct.DateAdded < days);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -90,11 +99,11 @@ public class GamblingService : INService, IReadyExecutor
|
|||||||
}
|
}
|
||||||
|
|
||||||
Log.Information("""
|
Log.Information("""
|
||||||
--- Decaying users' currency ---
|
--- Decaying users' currency ---
|
||||||
| decay: {ConfigDecayPercent}%
|
| decay: {ConfigDecayPercent}%
|
||||||
| max: {MaxDecay}
|
| max: {MaxDecay}
|
||||||
| threshold: {DecayMinTreshold}
|
| threshold: {DecayMinTreshold}
|
||||||
""",
|
""",
|
||||||
config.Decay.Percent * 100,
|
config.Decay.Percent * 100,
|
||||||
maxDecay,
|
maxDecay,
|
||||||
config.Decay.MinThreshold);
|
config.Decay.MinThreshold);
|
||||||
@@ -104,14 +113,14 @@ public class GamblingService : INService, IReadyExecutor
|
|||||||
|
|
||||||
var decay = (double)config.Decay.Percent;
|
var decay = (double)config.Decay.Percent;
|
||||||
await uow.Set<DiscordUser>()
|
await uow.Set<DiscordUser>()
|
||||||
.Where(x => x.CurrencyAmount > config.Decay.MinThreshold && x.UserId != _client.CurrentUser.Id)
|
.Where(x => x.CurrencyAmount > config.Decay.MinThreshold && x.UserId != _client.CurrentUser.Id)
|
||||||
.UpdateAsync(old => new()
|
.UpdateAsync(old => new()
|
||||||
{
|
{
|
||||||
CurrencyAmount =
|
CurrencyAmount =
|
||||||
maxDecay > Sql.Round((old.CurrencyAmount * decay) - 0.5)
|
maxDecay > Sql.Round((old.CurrencyAmount * decay) - 0.5)
|
||||||
? (long)(old.CurrencyAmount - Sql.Round((old.CurrencyAmount * decay) - 0.5))
|
? (long)(old.CurrencyAmount - Sql.Round((old.CurrencyAmount * decay) - 0.5))
|
||||||
: old.CurrencyAmount - maxDecay
|
: old.CurrencyAmount - maxDecay
|
||||||
});
|
});
|
||||||
|
|
||||||
await uow.SaveChangesAsync();
|
await uow.SaveChangesAsync();
|
||||||
|
|
||||||
@@ -133,6 +142,7 @@ public class GamblingService : INService, IReadyExecutor
|
|||||||
private static TypedKey<Dictionary<ulong, long>> _timelyKey
|
private static TypedKey<Dictionary<ulong, long>> _timelyKey
|
||||||
= new("timely:claims");
|
= new("timely:claims");
|
||||||
|
|
||||||
|
|
||||||
public async Task<TimeSpan?> ClaimTimelyAsync(ulong userId, int period)
|
public async Task<TimeSpan?> ClaimTimelyAsync(ulong userId, int period)
|
||||||
{
|
{
|
||||||
if (period == 0)
|
if (period == 0)
|
||||||
@@ -178,9 +188,10 @@ public class GamblingService : INService, IReadyExecutor
|
|||||||
public bool UserHasTimelyReminder(ulong userId)
|
public bool UserHasTimelyReminder(ulong userId)
|
||||||
{
|
{
|
||||||
var db = _db.GetDbContext();
|
var db = _db.GetDbContext();
|
||||||
return db.GetTable<Reminder>().Any(x => x.UserId == userId
|
return db.GetTable<Reminder>()
|
||||||
&& x.Type == ReminderType.Timely);
|
.Any(x => x.UserId == userId
|
||||||
}
|
&& x.Type == ReminderType.Timely);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task RemoveAllTimelyClaimsAsync()
|
public async Task RemoveAllTimelyClaimsAsync()
|
||||||
=> await _cache.RemoveAsync(_timelyKey);
|
=> await _cache.RemoveAsync(_timelyKey);
|
||||||
|
@@ -57,12 +57,4 @@ public abstract class GamblingModule<TService> : NadekoModule<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 NadekoBot.Modules.Gambling;
|
|||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class PlantPickCommands : GamblingSubmodule<PlantPickService>
|
public partial class PlantPickCommands : GamblingModule<PlantPickService>
|
||||||
{
|
{
|
||||||
private readonly ILogCommandService _logService;
|
private readonly ILogCommandService _logService;
|
||||||
|
|
||||||
|
@@ -1,8 +1,11 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NadekoBot.Common.ModuleBehaviors;
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
using NadekoBot.Db.Models;
|
using NadekoBot.Db.Models;
|
||||||
using SixLabors.Fonts;
|
using SixLabors.Fonts;
|
||||||
|
using SixLabors.Fonts.Unicode;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.Drawing.Processing;
|
using SixLabors.ImageSharp.Drawing.Processing;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
@@ -25,6 +28,7 @@ public class PlantPickService : INService, IExecNoCommand
|
|||||||
private readonly NadekoRandom _rng;
|
private readonly NadekoRandom _rng;
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
private readonly GamblingConfigService _gss;
|
private readonly GamblingConfigService _gss;
|
||||||
|
private readonly GamblingService _gs;
|
||||||
|
|
||||||
private readonly ConcurrentHashSet<ulong> _generationChannels;
|
private readonly ConcurrentHashSet<ulong> _generationChannels;
|
||||||
private readonly SemaphoreSlim _pickLock = new(1, 1);
|
private readonly SemaphoreSlim _pickLock = new(1, 1);
|
||||||
@@ -37,7 +41,8 @@ public class PlantPickService : INService, IExecNoCommand
|
|||||||
ICurrencyService cs,
|
ICurrencyService cs,
|
||||||
CommandHandler cmdHandler,
|
CommandHandler cmdHandler,
|
||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
GamblingConfigService gss)
|
GamblingConfigService gss,
|
||||||
|
GamblingService gs)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_strings = strings;
|
_strings = strings;
|
||||||
@@ -48,6 +53,7 @@ public class PlantPickService : INService, IExecNoCommand
|
|||||||
_rng = new();
|
_rng = new();
|
||||||
_client = client;
|
_client = client;
|
||||||
_gss = gss;
|
_gss = gss;
|
||||||
|
_gs = gs;
|
||||||
|
|
||||||
using var uow = db.GetDbContext();
|
using var uow = db.GetDbContext();
|
||||||
var guildIds = client.Guilds.Select(x => x.Id).ToList();
|
var guildIds = client.Guilds.Select(x => x.Id).ToList();
|
||||||
@@ -87,6 +93,7 @@ public class PlantPickService : INService, IExecNoCommand
|
|||||||
var toDelete = guildConfig.GenerateCurrencyChannelIds.FirstOrDefault(x => x.Equals(toAdd));
|
var toDelete = guildConfig.GenerateCurrencyChannelIds.FirstOrDefault(x => x.Equals(toAdd));
|
||||||
if (toDelete is not null)
|
if (toDelete is not null)
|
||||||
uow.Remove(toDelete);
|
uow.Remove(toDelete);
|
||||||
|
|
||||||
_generationChannels.TryRemove(cid);
|
_generationChannels.TryRemove(cid);
|
||||||
enabled = false;
|
enabled = false;
|
||||||
}
|
}
|
||||||
@@ -140,7 +147,7 @@ public class PlantPickService : INService, 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 / 12.0f, FontStyle.Bold);
|
var font = _fonts.NotoSans.CreateFont(img.Height / 11.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
|
||||||
@@ -152,13 +159,31 @@ public class PlantPickService : INService, 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(0, 0),
|
new PointF(1, 1),
|
||||||
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));
|
||||||
|
|
||||||
|
var strikeoutRun = new RichTextRun
|
||||||
|
{
|
||||||
|
Start = 0,
|
||||||
|
End = pass.GetGraphemeCount(),
|
||||||
|
Font = font,
|
||||||
|
StrikeoutPen = new SolidPen(Color.White, 2),
|
||||||
|
TextDecorations = TextDecorations.Strikeout
|
||||||
|
};
|
||||||
|
|
||||||
// draw the password over the background
|
// draw the password over the background
|
||||||
x.DrawText(pass, font, Color.White, new(0, 0));
|
x.DrawText(new RichTextOptions(font)
|
||||||
|
{
|
||||||
|
Origin = new(0, 0),
|
||||||
|
TextRuns =
|
||||||
|
[
|
||||||
|
strikeoutRun
|
||||||
|
]
|
||||||
|
},
|
||||||
|
pass,
|
||||||
|
new SolidBrush(Color.White));
|
||||||
});
|
});
|
||||||
// return image as a stream for easy sending
|
// return image as a stream for easy sending
|
||||||
var format = img.Metadata.DecodedImageFormat;
|
var format = img.Metadata.DecodedImageFormat;
|
||||||
@@ -208,7 +233,7 @@ public class PlantPickService : INService, IExecNoCommand
|
|||||||
+ " "
|
+ " "
|
||||||
+ GetText(channel.GuildId, strs.pick_pl(prefix));
|
+ GetText(channel.GuildId, strs.pick_pl(prefix));
|
||||||
|
|
||||||
var pw = config.Generation.HasPassword ? GenerateCurrencyPassword().ToUpperInvariant() : null;
|
var pw = config.Generation.HasPassword ? _gs.GeneratePassword().ToUpperInvariant() : null;
|
||||||
|
|
||||||
IUserMessage sent;
|
IUserMessage sent;
|
||||||
var (stream, ext) = await GetRandomCurrencyImageAsync(pw);
|
var (stream, ext) = await GetRandomCurrencyImageAsync(pw);
|
||||||
@@ -232,67 +257,44 @@ public class PlantPickService : INService, IExecNoCommand
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generate a hexadecimal string from 1000 to ffff.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A hexadecimal string from 1000 to ffff</returns>
|
|
||||||
private string GenerateCurrencyPassword()
|
|
||||||
{
|
|
||||||
// generate a number from 1000 to ffff
|
|
||||||
var num = _rng.Next(4096, 65536);
|
|
||||||
// convert it to hexadecimal
|
|
||||||
return num.ToString("x4");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<long> PickAsync(
|
public async Task<long> PickAsync(
|
||||||
ulong gid,
|
ulong gid,
|
||||||
ITextChannel ch,
|
ITextChannel ch,
|
||||||
ulong uid,
|
ulong uid,
|
||||||
string pass)
|
string pass)
|
||||||
{
|
{
|
||||||
await _pickLock.WaitAsync();
|
long amount;
|
||||||
|
ulong[] ids;
|
||||||
|
await using (var uow = _db.GetDbContext())
|
||||||
|
{
|
||||||
|
// this method will sum all plants with that password,
|
||||||
|
// remove them, and get messageids of the removed plants
|
||||||
|
|
||||||
|
pass = pass?.Trim().TrimTo(10, true)?.ToUpperInvariant();
|
||||||
|
// gets all plants in this channel with the same password
|
||||||
|
var entries = await uow.GetTable<PlantedCurrency>()
|
||||||
|
.Where(x => x.ChannelId == ch.Id && pass == x.Password)
|
||||||
|
.DeleteWithOutputAsync();
|
||||||
|
|
||||||
|
if (!entries.Any())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
amount = entries.Sum(x => x.Amount);
|
||||||
|
ids = entries.Select(x => x.MessageId).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount > 0)
|
||||||
|
await _cs.AddAsync(uid, amount, new("currency", "collect"));
|
||||||
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
long amount;
|
_ = ch.DeleteMessagesAsync(ids);
|
||||||
ulong[] ids;
|
|
||||||
await using (var uow = _db.GetDbContext())
|
|
||||||
{
|
|
||||||
// this method will sum all plants with that password,
|
|
||||||
// remove them, and get messageids of the removed plants
|
|
||||||
|
|
||||||
pass = pass?.Trim().TrimTo(10, true).ToUpperInvariant();
|
|
||||||
// gets all plants in this channel with the same password
|
|
||||||
var entries = uow.Set<PlantedCurrency>()
|
|
||||||
.AsQueryable()
|
|
||||||
.Where(x => x.ChannelId == ch.Id && pass == x.Password)
|
|
||||||
.ToList();
|
|
||||||
// sum how much currency that is, and get all of the message ids (so that i can delete them)
|
|
||||||
amount = entries.Sum(x => x.Amount);
|
|
||||||
ids = entries.Select(x => x.MessageId).ToArray();
|
|
||||||
// remove them from the database
|
|
||||||
uow.RemoveRange(entries);
|
|
||||||
|
|
||||||
|
|
||||||
if (amount > 0)
|
|
||||||
// give the picked currency to the user
|
|
||||||
await _cs.AddAsync(uid, amount, new("currency", "collect"));
|
|
||||||
await uow.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// delete all of the plant messages which have just been picked
|
|
||||||
_ = ch.DeleteMessagesAsync(ids);
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
|
|
||||||
// return the amount of currency the user picked
|
|
||||||
return amount;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_pickLock.Release();
|
|
||||||
}
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
// return the amount of currency the user picked
|
||||||
|
return amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ulong?> SendPlantMessageAsync(
|
public async Task<ulong?> SendPlantMessageAsync(
|
||||||
|
@@ -10,7 +10,7 @@ namespace NadekoBot.Modules.Gambling;
|
|||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class ShopCommands : GamblingSubmodule<IShopService>
|
public partial class ShopCommands : GamblingModule<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 : GamblingSubmodule<IGamblingService>
|
public partial class SlotCommands : GamblingModule<IGamblingService>
|
||||||
{
|
{
|
||||||
private readonly IImageCache _images;
|
private readonly IImageCache _images;
|
||||||
private readonly FontProvider _fonts;
|
private readonly FontProvider _fonts;
|
||||||
|
55
src/NadekoBot/Modules/Gambling/UserBetStatsService.cs
Normal file
55
src/NadekoBot/Modules/Gambling/UserBetStatsService.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#nullable disable
|
||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
|
using NadekoBot.Db.Models;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Gambling.Services;
|
||||||
|
|
||||||
|
public sealed class UserBetStatsService : INService
|
||||||
|
{
|
||||||
|
private const long RESET_MIN_PRICE = 1000;
|
||||||
|
private const decimal RESET_TOTAL_MULTIPLIER = 0.02m;
|
||||||
|
|
||||||
|
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 NadekoBot.Modules.Gambling;
|
|||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class WaifuClaimCommands : GamblingSubmodule<WaifuService>
|
public partial class WaifuClaimCommands : GamblingModule<WaifuService>
|
||||||
{
|
{
|
||||||
public WaifuClaimCommands(GamblingConfigService gamblingConfService)
|
public WaifuClaimCommands(GamblingConfigService gamblingConfService)
|
||||||
: base(gamblingConfService)
|
: base(gamblingConfService)
|
||||||
@@ -37,6 +37,45 @@ 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)
|
||||||
@@ -74,7 +113,7 @@ public partial class Gambling
|
|||||||
Format.Bold(ctx.User.ToString()),
|
Format.Bold(ctx.User.ToString()),
|
||||||
Format.Bold(target.ToString()),
|
Format.Bold(target.ToString()),
|
||||||
N(amount)));
|
N(amount)));
|
||||||
|
|
||||||
if (w.Affinity?.UserId == ctx.User.Id)
|
if (w.Affinity?.UserId == ctx.User.Id)
|
||||||
msg += "\n" + GetText(strs.waifu_fulfilled(target, N(w.Price)));
|
msg += "\n" + GetText(strs.waifu_fulfilled(target, N(w.Price)));
|
||||||
else
|
else
|
||||||
@@ -144,7 +183,7 @@ public partial class Gambling
|
|||||||
if (targetId == ctx.User.Id)
|
if (targetId == ctx.User.Id)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var (w, result, amount, remaining) = await _service.DivorceWaifuAsync(ctx.User, targetId);
|
var (w, result, amount) = await _service.DivorceWaifuAsync(ctx.User, targetId);
|
||||||
|
|
||||||
if (result == DivorceResult.SucessWithPenalty)
|
if (result == DivorceResult.SucessWithPenalty)
|
||||||
{
|
{
|
||||||
@@ -157,14 +196,6 @@ 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,25 +318,20 @@ public class WaifuService : INService, 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, TimeSpan?)> DivorceWaifuAsync(IUser user, ulong targetId)
|
public async Task<(WaifuInfo, DivorceResult, long)> 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;
|
||||||
|
|
||||||
@@ -369,7 +364,7 @@ public class WaifuService : INService, IReadyExecutor
|
|||||||
await uow.SaveChangesAsync();
|
await uow.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (w, result, amount, remaining);
|
return (w, result, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> GiftWaifuAsync(
|
public async Task<bool> GiftWaifuAsync(
|
||||||
@@ -603,7 +598,7 @@ public class WaifuService : INService, IReadyExecutor
|
|||||||
.Where(wi => wi.ClaimerId == waifuId)
|
.Where(wi => wi.ClaimerId == waifuId)
|
||||||
.Select(wi => wi.WaifuId)
|
.Select(wi => wi.WaifuId)
|
||||||
.Contains(x.Id))
|
.Contains(x.Id))
|
||||||
.Select(x => $"{x.Username}#{x.Discriminator}")
|
.Select(x => x.Username)
|
||||||
.ToListAsyncEF();
|
.ToListAsyncEF();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -615,7 +610,7 @@ public class WaifuService : INService, IReadyExecutor
|
|||||||
.Where(wi => wi.AffinityId == waifuId)
|
.Where(wi => wi.AffinityId == waifuId)
|
||||||
.Select(wi => wi.WaifuId)
|
.Select(wi => wi.WaifuId)
|
||||||
.Contains(x.Id))
|
.Contains(x.Id))
|
||||||
.Select(x => $"{x.Username}#{x.Discriminator}")
|
.Select(x => x.Username)
|
||||||
.ToListAsyncEF();
|
.ToListAsyncEF();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -630,4 +625,38 @@ public class WaifuService : INService, 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;
|
||||||
}
|
}
|
@@ -42,15 +42,12 @@ public static class WaifuExtensions
|
|||||||
{
|
{
|
||||||
Affinity = x.Affinity == null
|
Affinity = x.Affinity == null
|
||||||
? null
|
? null
|
||||||
: x.Affinity.Username
|
: x.Affinity.Username,
|
||||||
+ (x.Affinity.Discriminator != "0000" ? "#" + x.Affinity.Discriminator : ""),
|
|
||||||
ClaimerName =
|
ClaimerName =
|
||||||
x.Claimer == null
|
x.Claimer == null
|
||||||
? null
|
? null
|
||||||
: x.Claimer.Username
|
: x.Claimer.Username,
|
||||||
+ (x.Claimer.Discriminator != "0000" ? "#" + x.Claimer.Discriminator : ""),
|
WaifuName = x.Waifu.Username,
|
||||||
WaifuName = x.Waifu.Username
|
|
||||||
+ (x.Waifu.Discriminator != "0000" ? "#" + x.Waifu.Discriminator : ""),
|
|
||||||
Price = x.Price
|
Price = x.Price
|
||||||
})
|
})
|
||||||
.ToListAsyncEF();
|
.ToListAsyncEF();
|
||||||
@@ -62,7 +59,7 @@ public static class WaifuExtensions
|
|||||||
public static ulong GetWaifuUserId(this DbSet<WaifuInfo> waifus, ulong ownerId, string name)
|
public static ulong GetWaifuUserId(this DbSet<WaifuInfo> waifus, ulong ownerId, string name)
|
||||||
=> waifus.AsQueryable()
|
=> waifus.AsQueryable()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(x => x.Claimer.UserId == ownerId && x.Waifu.Username + "#" + x.Waifu.Discriminator == name)
|
.Where(x => x.Claimer.UserId == ownerId && x.Waifu.Username == name)
|
||||||
.Select(x => x.Waifu.UserId)
|
.Select(x => x.Waifu.UserId)
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
@@ -100,7 +97,7 @@ public static class WaifuExtensions
|
|||||||
ctx.Set<DiscordUser>()
|
ctx.Set<DiscordUser>()
|
||||||
.AsQueryable()
|
.AsQueryable()
|
||||||
.Where(u => u.UserId == userId)
|
.Where(u => u.UserId == userId)
|
||||||
.Select(u => u.Username + "#" + u.Discriminator)
|
.Select(u => u.Username)
|
||||||
.FirstOrDefault(),
|
.FirstOrDefault(),
|
||||||
AffinityCount =
|
AffinityCount =
|
||||||
ctx.Set<WaifuUpdate>()
|
ctx.Set<WaifuUpdate>()
|
||||||
@@ -112,14 +109,14 @@ public static class WaifuExtensions
|
|||||||
ctx.Set<DiscordUser>()
|
ctx.Set<DiscordUser>()
|
||||||
.AsQueryable()
|
.AsQueryable()
|
||||||
.Where(u => u.Id == w.AffinityId)
|
.Where(u => u.Id == w.AffinityId)
|
||||||
.Select(u => u.Username + "#" + u.Discriminator)
|
.Select(u => u.Username)
|
||||||
.FirstOrDefault(),
|
.FirstOrDefault(),
|
||||||
ClaimCount = ctx.Set<WaifuInfo>().AsQueryable().Count(x => x.ClaimerId == w.WaifuId),
|
ClaimCount = ctx.Set<WaifuInfo>().AsQueryable().Count(x => x.ClaimerId == w.WaifuId),
|
||||||
ClaimerName =
|
ClaimerName =
|
||||||
ctx.Set<DiscordUser>()
|
ctx.Set<DiscordUser>()
|
||||||
.AsQueryable()
|
.AsQueryable()
|
||||||
.Where(u => u.Id == w.ClaimerId)
|
.Where(u => u.Id == w.ClaimerId)
|
||||||
.Select(u => u.Username + "#" + u.Discriminator)
|
.Select(u => u.Username)
|
||||||
.FirstOrDefault(),
|
.FirstOrDefault(),
|
||||||
DivorceCount =
|
DivorceCount =
|
||||||
ctx.Set<WaifuUpdate>()
|
ctx.Set<WaifuUpdate>()
|
||||||
|
@@ -13,5 +13,10 @@ 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,4 +1,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using NadekoBot.Modules.Gambling.Betdraw;
|
using NadekoBot.Modules.Gambling.Betdraw;
|
||||||
using NadekoBot.Modules.Gambling.Rps;
|
using NadekoBot.Modules.Gambling.Rps;
|
||||||
using NadekoBot.Modules.Gambling.Services;
|
using NadekoBot.Modules.Gambling.Services;
|
||||||
@@ -8,15 +10,15 @@ namespace NadekoBot.Modules.Gambling;
|
|||||||
|
|
||||||
public sealed class NewGamblingService : IGamblingService, INService
|
public sealed class NewGamblingService : IGamblingService, INService
|
||||||
{
|
{
|
||||||
private readonly GamblingConfigService _bcs;
|
private readonly GamblingConfigService _gcs;
|
||||||
private readonly ICurrencyService _cs;
|
private readonly ICurrencyService _cs;
|
||||||
|
|
||||||
public NewGamblingService(GamblingConfigService bcs, ICurrencyService cs)
|
public NewGamblingService(GamblingConfigService gcs, ICurrencyService cs)
|
||||||
{
|
{
|
||||||
_bcs = bcs;
|
_gcs = gcs;
|
||||||
_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);
|
||||||
@@ -31,13 +33,13 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var game = new LulaGame(_bcs.Data.LuckyLadder.Multipliers);
|
var game = new LulaGame(_gcs.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", "win"));
|
await _cs.AddAsync(userId, won, new("lula", result.Multiplier >= 1 ? "win" : "lose"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -57,9 +59,9 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var game = new BetrollGame(_bcs.Data.BetRoll.Pairs
|
var game = new BetrollGame(_gcs.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);
|
||||||
|
|
||||||
@@ -88,19 +90,23 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var game = new BetflipGame(_bcs.Data.BetFlip.Multiplier);
|
var game = new BetflipGame(_gcs.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(ulong userId, long amount, byte? maybeGuessValue, byte? maybeGuessColor)
|
public async Task<OneOf<BetdrawResult, GamblingError>> BetDrawAsync(
|
||||||
|
ulong userId,
|
||||||
|
long amount,
|
||||||
|
byte? maybeGuessValue,
|
||||||
|
byte? maybeGuessColor)
|
||||||
{
|
{
|
||||||
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
||||||
|
|
||||||
@@ -109,7 +115,7 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
|
|
||||||
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));
|
||||||
|
|
||||||
@@ -125,13 +131,13 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +161,7 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
var won = (long)result.Won;
|
var won = (long)result.Won;
|
||||||
if (won > 0)
|
if (won > 0)
|
||||||
{
|
{
|
||||||
await _cs.AddAsync(userId, won, new("slot", "won"));
|
await _cs.AddAsync(userId, won, new("slot", "win"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -178,7 +184,7 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
|
|
||||||
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>();
|
||||||
@@ -236,7 +242,7 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
{
|
{
|
||||||
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"));
|
||||||
@@ -249,7 +255,7 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
@@ -265,4 +271,46 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class RakebackService: INService
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
24
src/NadekoBot/Modules/Games/NCanvas/INCanvasService.cs
Normal file
24
src/NadekoBot/Modules/Games/NCanvas/INCanvasService.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using NadekoBot.Db.Models;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Games;
|
||||||
|
|
||||||
|
public interface INCanvasService
|
||||||
|
{
|
||||||
|
Task<uint[]> GetCanvas();
|
||||||
|
Task<NCPixel[]> GetPixelGroup(int position);
|
||||||
|
|
||||||
|
Task<SetPixelResult> SetPixel(
|
||||||
|
int position,
|
||||||
|
uint color,
|
||||||
|
string text,
|
||||||
|
ulong userId,
|
||||||
|
long price);
|
||||||
|
|
||||||
|
Task<bool> SetImage(uint[] img);
|
||||||
|
|
||||||
|
Task<NCPixel?> GetPixel(int x, int y);
|
||||||
|
Task<NCPixel?> GetPixel(int position);
|
||||||
|
int GetHeight();
|
||||||
|
int GetWidth();
|
||||||
|
Task ResetAsync();
|
||||||
|
}
|
305
src/NadekoBot/Modules/Games/NCanvas/NCanvasCommands.cs
Normal file
305
src/NadekoBot/Modules/Games/NCanvas/NCanvasCommands.cs
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
using NadekoBot.Modules.Gambling.Services;
|
||||||
|
using SixLabors.Fonts;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.Advanced;
|
||||||
|
using SixLabors.ImageSharp.Drawing.Processing;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
using Image = SixLabors.ImageSharp.Image;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Games;
|
||||||
|
|
||||||
|
public partial class Games
|
||||||
|
{
|
||||||
|
public sealed class NCanvasCommands : NadekoModule
|
||||||
|
{
|
||||||
|
private readonly INCanvasService _service;
|
||||||
|
private readonly IHttpClientFactory _http;
|
||||||
|
private readonly FontProvider _fonts;
|
||||||
|
private readonly GamblingConfigService _gcs;
|
||||||
|
|
||||||
|
public NCanvasCommands(
|
||||||
|
INCanvasService service,
|
||||||
|
IHttpClientFactory http,
|
||||||
|
FontProvider fonts,
|
||||||
|
GamblingConfigService gcs)
|
||||||
|
{
|
||||||
|
_service = service;
|
||||||
|
_http = http;
|
||||||
|
_fonts = fonts;
|
||||||
|
_gcs = gcs;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task NCanvas()
|
||||||
|
{
|
||||||
|
var pixels = await _service.GetCanvas();
|
||||||
|
var image = new Image<Rgba32>(_service.GetWidth(), _service.GetHeight());
|
||||||
|
|
||||||
|
Parallel.For(0,
|
||||||
|
image.Height,
|
||||||
|
y =>
|
||||||
|
{
|
||||||
|
var pixelAccessor = image.DangerousGetPixelRowMemory(y);
|
||||||
|
var row = pixelAccessor.Span;
|
||||||
|
for (int x = 0; x < image.Width; x++)
|
||||||
|
{
|
||||||
|
row[x] = new Rgba32(pixels[(y * image.Width) + x]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await using var stream = await image.ToStreamAsync();
|
||||||
|
|
||||||
|
var hint = GetText(strs.nc_hint(prefix, _service.GetWidth(), _service.GetHeight()));
|
||||||
|
await Response()
|
||||||
|
.File(stream, "ncanvas.png")
|
||||||
|
.Embed(_sender.CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
#if GLOBAL_NADEKO
|
||||||
|
.WithDescription("https://dashy.nadeko.bot/ncanvas")
|
||||||
|
#endif
|
||||||
|
.WithFooter(hint)
|
||||||
|
.WithImageUrl("attachment://ncanvas.png"))
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public Task NCzoom(int row, int col)
|
||||||
|
=> NCzoom((col * _service.GetWidth()) + row);
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task NCzoom(kwum position)
|
||||||
|
{
|
||||||
|
var w = _service.GetWidth();
|
||||||
|
var h = _service.GetHeight();
|
||||||
|
|
||||||
|
if (position < 0 || position >= w * h)
|
||||||
|
{
|
||||||
|
await Response().Error(strs.invalid_input).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var img = await GetZoomImage(position);
|
||||||
|
await using var stream = await img.ToStreamAsync();
|
||||||
|
await ctx.Channel.SendFileAsync(stream, $"zoom_{position}.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Image<Rgba32>> GetZoomImage(kwum position)
|
||||||
|
{
|
||||||
|
var w = _service.GetWidth();
|
||||||
|
var pixels = await _service.GetPixelGroup(position);
|
||||||
|
|
||||||
|
var origX = ((position % w) - 2) * 100;
|
||||||
|
var origY = ((position / w) - 2) * 100;
|
||||||
|
|
||||||
|
var image = new Image<Rgba32>(500, 500);
|
||||||
|
|
||||||
|
const float fontSize = 30;
|
||||||
|
|
||||||
|
var posFont = _fonts.NotoSans.CreateFont(fontSize, FontStyle.Bold);
|
||||||
|
var size = TextMeasurer.MeasureSize("wwww", new TextOptions(posFont));
|
||||||
|
var scale = 100f / size.Width;
|
||||||
|
if (scale < 1)
|
||||||
|
posFont = _fonts.NotoSans.CreateFont(fontSize * scale, FontStyle.Bold);
|
||||||
|
var outlinePen = new SolidPen(SixLabors.ImageSharp.Color.Black, 1f);
|
||||||
|
|
||||||
|
Parallel.For(0,
|
||||||
|
pixels.Length,
|
||||||
|
i =>
|
||||||
|
{
|
||||||
|
var pix = pixels[i];
|
||||||
|
var startX = pix.Position % w * 100 - origX;
|
||||||
|
var startY = pix.Position / w * 100 - origY;
|
||||||
|
|
||||||
|
var color = new Rgba32(pix.Color);
|
||||||
|
image.Mutate(x => FillRectangleExtensions.Fill(x,
|
||||||
|
new SolidBrush(color),
|
||||||
|
new RectangleF(startX, startY, 100, 100)));
|
||||||
|
|
||||||
|
image.Mutate(x =>
|
||||||
|
{
|
||||||
|
x.DrawText(new RichTextOptions(posFont)
|
||||||
|
{
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
Origin = new(startX + 50, startY + 50)
|
||||||
|
},
|
||||||
|
((kwum)pix.Position).ToString().PadLeft(2, '2'),
|
||||||
|
Brushes.Solid(SixLabors.ImageSharp.Color.White),
|
||||||
|
outlinePen);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// write the position on each section of the image
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task NcSetPixel(kwum position, string colorHex, [Leftover] string text = "")
|
||||||
|
{
|
||||||
|
if (position < 0 || position >= _service.GetWidth() * _service.GetHeight())
|
||||||
|
{
|
||||||
|
await Response().Error(strs.invalid_input).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (colorHex.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
|
||||||
|
colorHex = colorHex[2..];
|
||||||
|
|
||||||
|
if (!Rgba32.TryParseHex(colorHex, out var clr))
|
||||||
|
{
|
||||||
|
await Response().Error(strs.invalid_color).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pixel = await _service.GetPixel(position);
|
||||||
|
if (pixel is null)
|
||||||
|
{
|
||||||
|
await Response().Error(strs.nc_pixel_not_found).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var prompt = GetText(strs.nc_pixel_set_confirm(Format.Code(position.ToString()),
|
||||||
|
Format.Bold(CurrencyHelper.N(pixel.Price,
|
||||||
|
Culture,
|
||||||
|
_gcs.Data.Currency.Sign))));
|
||||||
|
|
||||||
|
if (!await PromptUserConfirmAsync(_sender.CreateEmbed()
|
||||||
|
.WithPendingColor()
|
||||||
|
.WithDescription(prompt)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await _service.SetPixel(position, clr.PackedValue, text, ctx.User.Id, pixel.Price);
|
||||||
|
|
||||||
|
if (result == SetPixelResult.NotEnoughMoney)
|
||||||
|
{
|
||||||
|
await Response().Error(strs.not_enough(_gcs.Data.Currency.Sign)).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (result == SetPixelResult.InsufficientPayment)
|
||||||
|
{
|
||||||
|
await Response().Error(strs.nc_insuff_payment).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (result == SetPixelResult.InvalidInput)
|
||||||
|
{
|
||||||
|
await Response().Error(strs.invalid_input).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var img = await GetZoomImage(position);
|
||||||
|
await using var stream = await img.ToStreamAsync();
|
||||||
|
|
||||||
|
await Response()
|
||||||
|
.Embed(_sender.CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
.WithDescription(GetText(strs.nc_pixel_set(Format.Code(position.ToString()))))
|
||||||
|
.WithImageUrl($"attachment://zoom_{position}.png"))
|
||||||
|
.File(stream, $"zoom_{position}.png")
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task NcPixel(int x, int y)
|
||||||
|
=> await NcPixel((y * _service.GetWidth()) + x);
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task NcPixel(kwum position)
|
||||||
|
{
|
||||||
|
if (position < 0 || position >= _service.GetWidth() * _service.GetHeight())
|
||||||
|
{
|
||||||
|
await Response().Error(strs.invalid_input).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pixel = await _service.GetPixel(position);
|
||||||
|
if (pixel is null)
|
||||||
|
{
|
||||||
|
await Response().Error(strs.nc_pixel_not_found).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var image = new Image<Rgba32>(100, 100);
|
||||||
|
image.Mutate(x
|
||||||
|
=> x.Fill(new SolidBrush(new Rgba32(pixel.Color)),
|
||||||
|
new RectangleF(0, 0, 100, 100)));
|
||||||
|
|
||||||
|
await using var stream = await image.ToStreamAsync();
|
||||||
|
|
||||||
|
var pos = new kwum(pixel.Position);
|
||||||
|
await Response()
|
||||||
|
.File(stream, $"{pixel.Position}.png")
|
||||||
|
.Embed(_sender.CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
.WithDescription(string.IsNullOrWhiteSpace(pixel.Text) ? string.Empty : pixel.Text)
|
||||||
|
.WithTitle(GetText(strs.nc_pixel(pos)))
|
||||||
|
.AddField(GetText(strs.nc_position),
|
||||||
|
$"{pixel.Position % _service.GetWidth()} {pixel.Position / _service.GetWidth()}",
|
||||||
|
true)
|
||||||
|
.AddField(GetText(strs.price), pixel.Price.ToString(), true)
|
||||||
|
.AddField(GetText(strs.color), "#" + new Rgba32(pixel.Color).ToHex())
|
||||||
|
.WithImageUrl($"attachment://{pixel.Position}.png"))
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[OwnerOnly]
|
||||||
|
public async Task NcSetImg()
|
||||||
|
{
|
||||||
|
var attach = ctx.Message.Attachments.FirstOrDefault();
|
||||||
|
if (attach is null)
|
||||||
|
{
|
||||||
|
await Response().Error(strs.no_attach_found).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var w = _service.GetWidth();
|
||||||
|
var h = _service.GetHeight();
|
||||||
|
if (attach.Width != w || attach.Height != h)
|
||||||
|
{
|
||||||
|
await Response().Error(strs.invalid_img_size(w, h)).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await PromptUserConfirmAsync(_sender.CreateEmbed()
|
||||||
|
.WithDescription(
|
||||||
|
"This will reset the canvas to the specified image. All prices, text and colors will be reset.\n\n"
|
||||||
|
+ "Are you sure you want to continue?")))
|
||||||
|
return;
|
||||||
|
|
||||||
|
using var http = _http.CreateClient();
|
||||||
|
await using var stream = await http.GetStreamAsync(attach.Url);
|
||||||
|
using var img = await Image.LoadAsync<Rgba32>(stream);
|
||||||
|
|
||||||
|
var pixels = new uint[_service.GetWidth() * _service.GetHeight()];
|
||||||
|
|
||||||
|
Parallel.For(0,
|
||||||
|
_service.GetWidth() * _service.GetHeight(),
|
||||||
|
i => pixels[i] = img[i % _service.GetWidth(), i / _service.GetWidth()].PackedValue);
|
||||||
|
|
||||||
|
// for (var y = 0; y < _service.GetHeight(); y++)
|
||||||
|
// for (var x = 0; x < _service.GetWidth(); x++)
|
||||||
|
// pixels[(y * _service.GetWidth()) + x] = img[x, y].PackedValue;
|
||||||
|
|
||||||
|
await _service.SetImage(pixels);
|
||||||
|
await ctx.OkAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[OwnerOnly]
|
||||||
|
public async Task NcReset()
|
||||||
|
{
|
||||||
|
await _service.ResetAsync();
|
||||||
|
|
||||||
|
if (!await PromptUserConfirmAsync(_sender.CreateEmbed()
|
||||||
|
.WithDescription(
|
||||||
|
"This will delete all pixels and reset the canvas.\n\n"
|
||||||
|
+ "Are you sure you want to continue?")))
|
||||||
|
return;
|
||||||
|
|
||||||
|
await ctx.OkAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
206
src/NadekoBot/Modules/Games/NCanvas/NCanvasService.cs
Normal file
206
src/NadekoBot/Modules/Games/NCanvas/NCanvasService.cs
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.Data;
|
||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
|
using NadekoBot.Db.Models;
|
||||||
|
using SixLabors.ImageSharp.ColorSpaces;
|
||||||
|
using SixLabors.ImageSharp.ColorSpaces.Conversion;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Games;
|
||||||
|
|
||||||
|
public sealed class NCanvasService : INCanvasService, IReadyExecutor, INService
|
||||||
|
{
|
||||||
|
private readonly TypedKey<uint[]> _canvasKey = new("ncanvas");
|
||||||
|
|
||||||
|
private readonly DbService _db;
|
||||||
|
private readonly IBotCache _cache;
|
||||||
|
private readonly DiscordSocketClient _client;
|
||||||
|
private readonly ICurrencyService _cs;
|
||||||
|
|
||||||
|
public const int CANVAS_WIDTH = 500;
|
||||||
|
public const int CANVAS_HEIGHT = 350;
|
||||||
|
public const int INITIAL_PRICE = 3;
|
||||||
|
|
||||||
|
public NCanvasService(
|
||||||
|
DbService db,
|
||||||
|
IBotCache cache,
|
||||||
|
DiscordSocketClient client,
|
||||||
|
ICurrencyService cs)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
_cache = cache;
|
||||||
|
_client = client;
|
||||||
|
_cs = cs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnReadyAsync()
|
||||||
|
{
|
||||||
|
if (_client.ShardId != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
|
||||||
|
if (await uow.GetTable<NCPixel>().CountAsyncLinqToDB() > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await ResetAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ResetAsync()
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
await uow.GetTable<NCPixel>().DeleteAsync();
|
||||||
|
|
||||||
|
var toAdd = new List<int>();
|
||||||
|
for (var i = 0; i < CANVAS_WIDTH * CANVAS_HEIGHT; i++)
|
||||||
|
{
|
||||||
|
toAdd.Add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
await uow.GetTable<NCPixel>()
|
||||||
|
.BulkCopyAsync(toAdd.Select(x =>
|
||||||
|
{
|
||||||
|
var clr = ColorSpaceConverter.ToRgb(new Hsv(((float)Random.Shared.NextDouble() * 360),
|
||||||
|
(float)(0.5 + (Random.Shared.NextDouble() * 0.49)),
|
||||||
|
(float)(0.4 + (Random.Shared.NextDouble() / 5 + (x % 100 * 0.2)))))
|
||||||
|
.ToVector3();
|
||||||
|
|
||||||
|
var packed = new Rgba32(clr).PackedValue;
|
||||||
|
return new NCPixel()
|
||||||
|
{
|
||||||
|
Color = packed,
|
||||||
|
Price = 1,
|
||||||
|
Position = x,
|
||||||
|
Text = "",
|
||||||
|
OwnerId = 0
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async Task<uint[]> InternalGetCanvas()
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
var colors = await uow.GetTable<NCPixel>()
|
||||||
|
.OrderBy(x => x.Position)
|
||||||
|
.Select(x => x.Color)
|
||||||
|
.ToArrayAsyncLinqToDB();
|
||||||
|
|
||||||
|
return colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<uint[]> GetCanvas()
|
||||||
|
{
|
||||||
|
return await _cache.GetOrAddAsync(_canvasKey,
|
||||||
|
async () => await InternalGetCanvas(),
|
||||||
|
TimeSpan.FromSeconds(15))
|
||||||
|
?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SetPixelResult> SetPixel(
|
||||||
|
int position,
|
||||||
|
uint color,
|
||||||
|
string text,
|
||||||
|
ulong userId,
|
||||||
|
long price)
|
||||||
|
{
|
||||||
|
if (position < 0 || position >= CANVAS_WIDTH * CANVAS_HEIGHT)
|
||||||
|
return SetPixelResult.InvalidInput;
|
||||||
|
|
||||||
|
var wallet = await _cs.GetWalletAsync(userId);
|
||||||
|
|
||||||
|
var paid = await wallet.Take(price, new("canvas", "pixel-buy", $"Bought pixel {new kwum(position)}"));
|
||||||
|
if (!paid)
|
||||||
|
{
|
||||||
|
return SetPixelResult.NotEnoughMoney;
|
||||||
|
}
|
||||||
|
|
||||||
|
var success = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
var updates = await uow.GetTable<NCPixel>()
|
||||||
|
.Where(x => x.Position == position && x.Price <= price)
|
||||||
|
.UpdateAsync(old => new NCPixel()
|
||||||
|
{
|
||||||
|
Position = position,
|
||||||
|
Color = color,
|
||||||
|
Text = text,
|
||||||
|
OwnerId = userId,
|
||||||
|
Price = price + 1
|
||||||
|
});
|
||||||
|
success = updates > 0;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
await wallet.Add(price, new("canvas", "pixel-refund", $"Refund pixel {new kwum(position)} purchase"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return success ? SetPixelResult.Success : SetPixelResult.InsufficientPayment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SetImage(uint[] colors)
|
||||||
|
{
|
||||||
|
if (colors.Length != CANVAS_WIDTH * CANVAS_HEIGHT)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
await uow.GetTable<NCPixel>().DeleteAsync();
|
||||||
|
await uow.GetTable<NCPixel>()
|
||||||
|
.BulkCopyAsync(colors.Select((x, i) => new NCPixel()
|
||||||
|
{
|
||||||
|
Color = x,
|
||||||
|
Price = INITIAL_PRICE,
|
||||||
|
Position = i,
|
||||||
|
Text = "",
|
||||||
|
OwnerId = 0
|
||||||
|
}));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<NCPixel?> GetPixel(int x, int y)
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(x);
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(y);
|
||||||
|
|
||||||
|
if (x >= CANVAS_WIDTH || y >= CANVAS_HEIGHT)
|
||||||
|
return Task.FromResult<NCPixel?>(null);
|
||||||
|
|
||||||
|
return GetPixel(x + (y * CANVAS_WIDTH));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<NCPixel?> GetPixel(int position)
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(position);
|
||||||
|
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
return await uow.GetTable<NCPixel>().FirstOrDefaultAsync(x => x.Position == position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<NCPixel[]> GetPixelGroup(int position)
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(position);
|
||||||
|
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(position, CANVAS_WIDTH * CANVAS_HEIGHT);
|
||||||
|
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
return await uow.GetTable<NCPixel>()
|
||||||
|
.Where(x => x.Position % CANVAS_WIDTH >= (position % CANVAS_WIDTH) - 2
|
||||||
|
&& x.Position % CANVAS_WIDTH <= (position % CANVAS_WIDTH) + 2
|
||||||
|
&& x.Position / CANVAS_WIDTH >= (position / CANVAS_WIDTH) - 2
|
||||||
|
&& x.Position / CANVAS_WIDTH <= (position / CANVAS_WIDTH) + 2)
|
||||||
|
.OrderBy(x => x.Position)
|
||||||
|
.ToArrayAsyncLinqToDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHeight()
|
||||||
|
=> CANVAS_HEIGHT;
|
||||||
|
|
||||||
|
public int GetWidth()
|
||||||
|
=> CANVAS_WIDTH;
|
||||||
|
}
|
9
src/NadekoBot/Modules/Games/NCanvas/SetPixelResult.cs
Normal file
9
src/NadekoBot/Modules/Games/NCanvas/SetPixelResult.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace NadekoBot.Modules.Games;
|
||||||
|
|
||||||
|
public enum SetPixelResult
|
||||||
|
{
|
||||||
|
Success,
|
||||||
|
InsufficientPayment,
|
||||||
|
NotEnoughMoney,
|
||||||
|
InvalidInput
|
||||||
|
}
|
@@ -29,7 +29,7 @@ public partial class Games
|
|||||||
if (!await nunchi.Join(ctx.User.Id, ctx.User.ToString()))
|
if (!await nunchi.Join(ctx.User.Id, ctx.User.ToString()))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await Response().Error(strs.nunchi_joined(nunchi.ParticipantCount)).SendAsync();
|
await Response().Confirm(strs.nunchi_joined(nunchi.ParticipantCount)).SendAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -122,9 +122,9 @@ public sealed class CurrencyRewardService : INService, IReadyExecutor
|
|||||||
var dollarValue = pledgeCents / 100;
|
var dollarValue = pledgeCents / 100;
|
||||||
percentBonus = dollarValue switch
|
percentBonus = dollarValue switch
|
||||||
{
|
{
|
||||||
>= 100 => 100,
|
>= 100 => 25,
|
||||||
>= 50 => 50,
|
>= 50 => 20,
|
||||||
>= 20 => 20,
|
>= 20 => 15,
|
||||||
>= 10 => 10,
|
>= 10 => 10,
|
||||||
>= 5 => 5,
|
>= 5 => 5,
|
||||||
_ => 0
|
_ => 0
|
||||||
|
@@ -404,9 +404,9 @@ public sealed class PatronageService
|
|||||||
{
|
{
|
||||||
>= 10_000 => 100,
|
>= 10_000 => 100,
|
||||||
>= 5000 => 50,
|
>= 5000 => 50,
|
||||||
>= 2000 => 20,
|
>= 2000 => 30,
|
||||||
>= 1000 => 10,
|
>= 1000 => 20,
|
||||||
>= 500 => 5,
|
>= 500 => 10,
|
||||||
_ => 0
|
_ => 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -18,39 +18,39 @@ public partial class Permissions
|
|||||||
{
|
{
|
||||||
ArgumentOutOfRangeException.ThrowIfNegative(page);
|
ArgumentOutOfRangeException.ThrowIfNegative(page);
|
||||||
|
|
||||||
var list = _service.GetBlacklist();
|
var list = await _service.GetBlacklist(type);
|
||||||
var allItems = await list.Where(x => x.Type == type)
|
var allItems = await list
|
||||||
.Select(i =>
|
.Select(i =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return Task.FromResult(i.Type switch
|
return Task.FromResult(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.GetGuild(i.ItemId)?.ToString() ?? ""),
|
+ (_client.GetChannel(i.ItemId)?.ToString()
|
||||||
_ => Format.Code(i.ItemId.ToString())
|
?? ""),
|
||||||
});
|
BlacklistType.User => Format.Code(i.ItemId.ToString())
|
||||||
}
|
+ " "
|
||||||
catch
|
+ ((_client.GetUser(i.ItemId))
|
||||||
{
|
?.ToString()
|
||||||
Log.Warning("Can't get {BlacklistType} [{BlacklistItemId}]",
|
?? ""),
|
||||||
i.Type,
|
BlacklistType.Server => Format.Code(i.ItemId.ToString())
|
||||||
i.ItemId);
|
+ " "
|
||||||
|
+ (_client.GetGuild(i.ItemId)?.ToString() ?? ""),
|
||||||
return Task.FromResult(Format.Code(i.ItemId.ToString()));
|
_ => Format.Code(i.ItemId.ToString())
|
||||||
}
|
});
|
||||||
})
|
}
|
||||||
.WhenAll();
|
catch
|
||||||
|
{
|
||||||
|
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(allItems.Join('\n'))
|
.WithDescription(pageItems.Join('\n'))
|
||||||
.WithOkColor();
|
.WithOkColor();
|
||||||
})
|
})
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,9 @@ public sealed class YoutubeDataApiSearchService : IYoutubeSearchService, INServi
|
|||||||
if(results.Count == 0)
|
if(results.Count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return results.Map(r => new VideoInfo(r));
|
return results.Map(r => new VideoInfo()
|
||||||
|
{
|
||||||
|
Url = r
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
191
src/NadekoBot/Modules/Searches/Translate/FlagTranslateService.cs
Normal file
191
src/NadekoBot/Modules/Searches/Translate/FlagTranslateService.cs
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
#nullable disable
|
||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
|
using NadekoBot.Db.Models;
|
||||||
|
using System.Collections.Frozen;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Searches;
|
||||||
|
|
||||||
|
public sealed partial class FlagTranslateService : IReadyExecutor, INService
|
||||||
|
{
|
||||||
|
private readonly IBotCreds _creds;
|
||||||
|
private readonly DiscordSocketClient _client;
|
||||||
|
private readonly TranslateService _ts;
|
||||||
|
private readonly IMessageSenderService _sender;
|
||||||
|
private IReadOnlyDictionary<string, string> _supportedFlags;
|
||||||
|
private readonly DbService _db;
|
||||||
|
private ConcurrentHashSet<ulong> _enabledChannels;
|
||||||
|
private readonly IBotCache _cache;
|
||||||
|
|
||||||
|
// disallow same message being translated multiple times to the same language
|
||||||
|
private readonly ConcurrentHashSet<(ulong, string)> _msgLangs = new();
|
||||||
|
|
||||||
|
public FlagTranslateService(
|
||||||
|
IBotCreds creds,
|
||||||
|
DiscordSocketClient client,
|
||||||
|
TranslateService ts,
|
||||||
|
IMessageSenderService sender,
|
||||||
|
DbService db,
|
||||||
|
IBotCache cache)
|
||||||
|
{
|
||||||
|
_creds = creds;
|
||||||
|
_client = client;
|
||||||
|
_ts = ts;
|
||||||
|
_sender = sender;
|
||||||
|
_db = db;
|
||||||
|
_cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnReadyAsync()
|
||||||
|
{
|
||||||
|
_supportedFlags = COUNTRIES
|
||||||
|
.Split('\n')
|
||||||
|
.Select(x => x.Split(' '))
|
||||||
|
.ToDictionary(x => x[0], x => x[1].TrimEnd())
|
||||||
|
.ToFrozenDictionary();
|
||||||
|
|
||||||
|
await using (var uow = _db.GetDbContext())
|
||||||
|
{
|
||||||
|
_enabledChannels = (await uow.GetTable<FlagTranslateChannel>()
|
||||||
|
.Where(x => Linq2DbExpressions.GuildOnShard(x.GuildId,
|
||||||
|
_creds.TotalShards,
|
||||||
|
_client.ShardId))
|
||||||
|
.Select(x => new
|
||||||
|
{
|
||||||
|
x.ChannelId,
|
||||||
|
x.GuildId
|
||||||
|
})
|
||||||
|
.ToListAsyncLinqToDB())
|
||||||
|
.Select(x => x.ChannelId)
|
||||||
|
.ToHashSet()
|
||||||
|
.ToConcurrentSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
_client.ReactionAdded += OnReactionAdded;
|
||||||
|
|
||||||
|
var periodicCleanup = new PeriodicTimer(TimeSpan.FromHours(24));
|
||||||
|
|
||||||
|
while (await periodicCleanup.WaitForNextTickAsync())
|
||||||
|
{
|
||||||
|
_msgLangs.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const int FLAG_START = 127462;
|
||||||
|
|
||||||
|
private static TypedKey<bool> CdKey(ulong userId)
|
||||||
|
=> new($"flagtranslate:{userId}");
|
||||||
|
|
||||||
|
private Task OnReactionAdded(
|
||||||
|
Cacheable<IUserMessage, ulong> arg1,
|
||||||
|
Cacheable<IMessageChannel, ulong> arg2,
|
||||||
|
SocketReaction reaction)
|
||||||
|
{
|
||||||
|
if (!_enabledChannels.Contains(reaction.Channel.Id))
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
var runes = reaction.Emote.Name.EnumerateRunes();
|
||||||
|
if (!runes.MoveNext()
|
||||||
|
|| runes.Current is not { Value: >= 127462 and <= 127487 } l1
|
||||||
|
|| !runes.MoveNext()
|
||||||
|
|| runes.Current is not { Value: >= 127462 and <= 127487 } l2)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
if (reaction.Channel is not SocketTextChannel tc)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var user = await ((IGuild)tc.Guild).GetUserAsync(reaction.UserId);
|
||||||
|
|
||||||
|
if (user is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!user.GetPermissions(tc).SendMessages)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!tc.Guild.CurrentUser.GetPermissions(tc).SendMessages
|
||||||
|
|| !tc.Guild.CurrentUser.GetPermissions(tc).EmbedLinks)
|
||||||
|
{
|
||||||
|
await Disable(tc.Guild.Id, tc.Id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var c1 = (char)(l1.Value - FLAG_START + 65);
|
||||||
|
var c2 = (char)(l2.Value - FLAG_START + 65);
|
||||||
|
|
||||||
|
var code = $"{c1}{c2}".ToUpper();
|
||||||
|
|
||||||
|
if (!_supportedFlags.TryGetValue(code, out var lang))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_msgLangs.Add((reaction.MessageId, lang)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var result = await _cache.GetAsync(CdKey(reaction.UserId));
|
||||||
|
if (result.TryPickT0(out _, out _))
|
||||||
|
return;
|
||||||
|
|
||||||
|
await _cache.AddAsync(CdKey(reaction.UserId), true, TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
|
var msg = await arg1.GetOrDownloadAsync();
|
||||||
|
|
||||||
|
var response = await _ts.Translate("", lang, msg.Content).ConfigureAwait(false);
|
||||||
|
|
||||||
|
await msg.ReplyAsync(embed: _sender.CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
.WithFooter(user.ToString() ?? reaction.UserId.ToString(),
|
||||||
|
user.RealAvatarUrl().ToString())
|
||||||
|
.WithDescription(response)
|
||||||
|
.WithAuthor(reaction.Emote.ToString())
|
||||||
|
.Build(),
|
||||||
|
allowedMentions: AllowedMentions.None
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Disable(ulong guildId, ulong tcId)
|
||||||
|
{
|
||||||
|
if (!_enabledChannels.TryRemove(tcId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
await uow.GetTable<FlagTranslateChannel>()
|
||||||
|
.Where(x => x.GuildId == guildId
|
||||||
|
&& x.ChannelId == tcId)
|
||||||
|
.DeleteAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Toggle(ulong guildId, ulong tcId)
|
||||||
|
{
|
||||||
|
if (_enabledChannels.Contains(tcId))
|
||||||
|
{
|
||||||
|
await Disable(guildId, tcId);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Enable(guildId, tcId);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Enable(ulong guildId, ulong tcId)
|
||||||
|
{
|
||||||
|
if (!_enabledChannels.Add(tcId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
await uow.GetTable<FlagTranslateChannel>()
|
||||||
|
.InsertAsync(() => new FlagTranslateChannel
|
||||||
|
{
|
||||||
|
GuildId = guildId,
|
||||||
|
ChannelId = tcId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,81 @@
|
|||||||
|
namespace NadekoBot.Modules.Searches;
|
||||||
|
|
||||||
|
public partial class FlagTranslateService
|
||||||
|
{
|
||||||
|
private const string COUNTRIES = """
|
||||||
|
CN zh
|
||||||
|
IN hi
|
||||||
|
US en
|
||||||
|
ID id
|
||||||
|
PK ur
|
||||||
|
BR pt
|
||||||
|
NG ha
|
||||||
|
BD bn
|
||||||
|
RU ru
|
||||||
|
JP ja
|
||||||
|
MX es
|
||||||
|
PH tl
|
||||||
|
VN vi
|
||||||
|
EG ar
|
||||||
|
ET am
|
||||||
|
DE de
|
||||||
|
IR fa
|
||||||
|
TR tr
|
||||||
|
TH th
|
||||||
|
FR fr
|
||||||
|
CD fr
|
||||||
|
MM my
|
||||||
|
UG en
|
||||||
|
MZ pt
|
||||||
|
ZA zu
|
||||||
|
CO es
|
||||||
|
BG bg
|
||||||
|
HR hr
|
||||||
|
MY ms
|
||||||
|
NL nl
|
||||||
|
RO ro
|
||||||
|
CZ cs
|
||||||
|
GR el
|
||||||
|
SK sk
|
||||||
|
PT pt
|
||||||
|
KR ko
|
||||||
|
IT it
|
||||||
|
ES es
|
||||||
|
RS sr
|
||||||
|
TN ar
|
||||||
|
PL pl
|
||||||
|
SD ar
|
||||||
|
CM fr
|
||||||
|
SN fr
|
||||||
|
ML fr
|
||||||
|
NE ha
|
||||||
|
BI fr
|
||||||
|
AO pt
|
||||||
|
AF ps
|
||||||
|
MA ar
|
||||||
|
DZ ar
|
||||||
|
GB en
|
||||||
|
AR es
|
||||||
|
ZW ny
|
||||||
|
KE sw
|
||||||
|
GH en
|
||||||
|
SA ar
|
||||||
|
IL he
|
||||||
|
IQ ar
|
||||||
|
UA ua
|
||||||
|
LY ar
|
||||||
|
KW ar
|
||||||
|
OM ar
|
||||||
|
YE ar
|
||||||
|
AL sq
|
||||||
|
AE ar
|
||||||
|
AU en
|
||||||
|
NZ en
|
||||||
|
KZ kz
|
||||||
|
NO no
|
||||||
|
SE sv
|
||||||
|
DK da
|
||||||
|
FI fi
|
||||||
|
HU hu
|
||||||
|
""";
|
||||||
|
}
|
@@ -44,12 +44,10 @@ public sealed class TranslateService : ITranslateService, IExecNoCommand, IReady
|
|||||||
foreach (var c in cs)
|
foreach (var c in cs)
|
||||||
{
|
{
|
||||||
_atcs[c.ChannelId] = c.AutoDelete;
|
_atcs[c.ChannelId] = c.AutoDelete;
|
||||||
_users[c.ChannelId] =
|
_users[c.ChannelId] = new(c.Users.ToDictionary(x => x.UserId, x => (x.Source.ToLower(), x.Target.ToLower())));
|
||||||
new(c.Users.ToDictionary(x => x.UserId, x => (x.Source.ToLower(), x.Target.ToLower())));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg)
|
public async Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(msg.Content))
|
if (string.IsNullOrWhiteSpace(msg.Content))
|
||||||
@@ -95,7 +93,7 @@ public sealed class TranslateService : ITranslateService, IExecNoCommand, IReady
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> Translate(string source, string target, string text = null)
|
public async Task<string> Translate(string source, string target, string text)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(text))
|
if (string.IsNullOrWhiteSpace(text))
|
||||||
throw new ArgumentException("Text is empty or null", nameof(text));
|
throw new ArgumentException("Text is empty or null", nameof(text));
|
||||||
|
@@ -6,6 +6,14 @@ public partial class Searches
|
|||||||
[Group]
|
[Group]
|
||||||
public partial class TranslateCommands : NadekoModule<ITranslateService>
|
public partial class TranslateCommands : NadekoModule<ITranslateService>
|
||||||
{
|
{
|
||||||
|
private readonly FlagTranslateService _flagSvc;
|
||||||
|
|
||||||
|
public TranslateCommands(FlagTranslateService flagSvc)
|
||||||
|
{
|
||||||
|
_flagSvc = flagSvc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public enum AutoDeleteAutoTranslate
|
public enum AutoDeleteAutoTranslate
|
||||||
{
|
{
|
||||||
Del,
|
Del,
|
||||||
@@ -91,5 +99,18 @@ public partial class Searches
|
|||||||
|
|
||||||
await Response().Embed(eb).SendAsync();
|
await Response().Embed(eb).SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[UserPerm(ChannelPermission.ManageChannels)]
|
||||||
|
[BotPerm(ChannelPermission.SendMessages | ChannelPermission.EmbedLinks)]
|
||||||
|
public async Task TranslateFlags()
|
||||||
|
{
|
||||||
|
var enabled = await _flagSvc.Toggle(ctx.Guild.Id, ctx.Channel.Id);
|
||||||
|
if (enabled)
|
||||||
|
await Response().Confirm(strs.trfl_enabled).SendAsync();
|
||||||
|
else
|
||||||
|
await Response().Confirm(strs.trfl_disabled).SendAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -783,4 +783,28 @@ public partial class Utility : NadekoModule
|
|||||||
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 : NadekoModule<XpService>
|
|||||||
else if (userXpData.AwardedXp < 0)
|
else if (userXpData.AwardedXp < 0)
|
||||||
awardStr = $"({userXpData.AwardedXp})";
|
awardStr = $"({userXpData.AwardedXp})";
|
||||||
|
|
||||||
embed.AddField($"#{i + 1 + (curPage * 9)} {user?.ToString() ?? users[i].UserId.ToString()}",
|
embed.AddField($"#{i + 1 + (curPage * 10)} {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 : NadekoModule<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 * 9)} {user}",
|
embed.AddField($"#{i + 1 + (curPage * 10)} {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");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,7 +357,7 @@ public partial class Xp : NadekoModule<XpService>
|
|||||||
if (!await PromptUserConfirmAsync(embed))
|
if (!await PromptUserConfirmAsync(embed))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_service.XpReset(ctx.Guild.Id, userId);
|
await _service.XpReset(ctx.Guild.Id, userId);
|
||||||
|
|
||||||
await Response().Confirm(strs.reset_user(userId)).SendAsync();
|
await Response().Confirm(strs.reset_user(userId)).SendAsync();
|
||||||
}
|
}
|
||||||
|
@@ -20,6 +20,31 @@ using Image = SixLabors.ImageSharp.Image;
|
|||||||
|
|
||||||
namespace NadekoBot.Modules.Xp.Services;
|
namespace NadekoBot.Modules.Xp.Services;
|
||||||
|
|
||||||
|
public interface IUserService
|
||||||
|
{
|
||||||
|
Task<DiscordUser?> GetUserAsync(ulong userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class UserService : IUserService, INService
|
||||||
|
{
|
||||||
|
private readonly DbService _db;
|
||||||
|
|
||||||
|
public UserService(DbService db)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DiscordUser> GetUserAsync(ulong userId)
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
var user = await uow
|
||||||
|
.GetTable<DiscordUser>()
|
||||||
|
.FirstOrDefaultAsyncLinqToDB(u => u.UserId == userId);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class XpService : INService, IReadyExecutor, IExecNoCommand
|
public class XpService : INService, IReadyExecutor, IExecNoCommand
|
||||||
{
|
{
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
@@ -1437,11 +1462,11 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void XpReset(ulong guildId, ulong userId)
|
public async Task XpReset(ulong guildId, ulong userId)
|
||||||
{
|
{
|
||||||
using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
uow.Set<UserXpStats>().ResetGuildUserXp(userId, guildId);
|
await uow.GetTable<UserXpStats>()
|
||||||
uow.SaveChanges();
|
.DeleteAsync(x => x.UserId == userId && x.GuildId == guildId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void XpReset(ulong guildId)
|
public void XpReset(ulong guildId)
|
||||||
@@ -1637,6 +1662,15 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
|
|
||||||
public bool IsShopEnabled()
|
public bool IsShopEnabled()
|
||||||
=> _xpConfig.Data.Shop.IsEnabled;
|
=> _xpConfig.Data.Shop.IsEnabled;
|
||||||
|
|
||||||
|
public async Task<int> GetTotalGuildUsers(ulong requestGuildId, List<ulong>? guildUsers = null)
|
||||||
|
{
|
||||||
|
await using var ctx = _db.GetDbContext();
|
||||||
|
return await ctx.GetTable<UserXpStats>()
|
||||||
|
.Where(x => x.GuildId == requestGuildId
|
||||||
|
&& (guildUsers == null || guildUsers.Contains(x.UserId)))
|
||||||
|
.CountAsyncLinqToDB();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum BuyResult
|
public enum BuyResult
|
||||||
|
@@ -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.15</Version>
|
<Version>5.1.20</Version>
|
||||||
|
|
||||||
<!-- Output/build -->
|
<!-- Output/build -->
|
||||||
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
||||||
|
@@ -18,13 +18,10 @@ public class ExprsSvc : GrpcExprs.GrpcExprsBase, IGrpcSvc, INService
|
|||||||
_qs = qs;
|
_qs = qs;
|
||||||
_client = client;
|
_client = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerServiceDefinition Bind()
|
public ServerServiceDefinition Bind()
|
||||||
=> GrpcExprs.BindService(this);
|
=> GrpcExprs.BindService(this);
|
||||||
|
|
||||||
private ulong GetUserId(Metadata meta)
|
|
||||||
=> ulong.Parse(meta.FirstOrDefault(x => x.Key == "userid")!.Value);
|
|
||||||
|
|
||||||
public override async Task<AddExprReply> AddExpr(AddExprRequest request, ServerCallContext context)
|
public override async Task<AddExprReply> AddExpr(AddExprRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(request.Expr.Trigger) || string.IsNullOrWhiteSpace(request.Expr.Response))
|
if (string.IsNullOrWhiteSpace(request.Expr.Trigger) || string.IsNullOrWhiteSpace(request.Expr.Response))
|
||||||
@@ -109,7 +106,7 @@ public class ExprsSvc : GrpcExprs.GrpcExprsBase, IGrpcSvc, INService
|
|||||||
|
|
||||||
public override async Task<AddQuoteReply> AddQuote(AddQuoteRequest request, ServerCallContext context)
|
public override async Task<AddQuoteReply> AddQuote(AddQuoteRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
var userId = GetUserId(context.RequestHeaders);
|
var userId = context.RequestHeaders.GetUserId();
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(request.Quote.Trigger) || string.IsNullOrWhiteSpace(request.Quote.Response))
|
if (string.IsNullOrWhiteSpace(request.Quote.Trigger) || string.IsNullOrWhiteSpace(request.Quote.Response))
|
||||||
throw new RpcException(new Status(StatusCode.InvalidArgument, "Trigger and response are required"));
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Trigger and response are required"));
|
||||||
@@ -146,7 +143,7 @@ public class ExprsSvc : GrpcExprs.GrpcExprsBase, IGrpcSvc, INService
|
|||||||
|
|
||||||
public override async Task<Empty> DeleteQuote(DeleteQuoteRequest request, ServerCallContext context)
|
public override async Task<Empty> DeleteQuote(DeleteQuoteRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
await _qs.DeleteQuoteAsync(request.GuildId, GetUserId(context.RequestHeaders), true, new kwum(request.Id));
|
await _qs.DeleteQuoteAsync(request.GuildId, context.RequestHeaders.GetUserId(), true, new kwum(request.Id));
|
||||||
return new Empty();
|
return new Empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
89
src/NadekoBot/Services/GrpcApi/FinSvc.cs
Normal file
89
src/NadekoBot/Services/GrpcApi/FinSvc.cs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
using Google.Protobuf.WellKnownTypes;
|
||||||
|
using Grpc.Core;
|
||||||
|
using NadekoBot.Db.Models;
|
||||||
|
using NadekoBot.Modules.Gambling.Bank;
|
||||||
|
using NadekoBot.Modules.NadekoExpressions;
|
||||||
|
using NadekoBot.Modules.Utility;
|
||||||
|
|
||||||
|
namespace NadekoBot.GrpcApi;
|
||||||
|
|
||||||
|
public class FinSvc : GrpcFin.GrpcFinBase, IGrpcSvc, INService
|
||||||
|
{
|
||||||
|
private readonly ICurrencyService _cs;
|
||||||
|
private readonly IBankService _bank;
|
||||||
|
|
||||||
|
public FinSvc(ICurrencyService cs, IBankService bank)
|
||||||
|
{
|
||||||
|
_cs = cs;
|
||||||
|
_bank = bank;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerServiceDefinition Bind()
|
||||||
|
=> GrpcFin.BindService(this);
|
||||||
|
|
||||||
|
[GrpcNoAuthRequired]
|
||||||
|
public override async Task<DepositReply> Deposit(DepositRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
if (request.Amount <= 0)
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Amount must be greater than 0"));
|
||||||
|
|
||||||
|
var succ = await _bank.DepositAsync(request.UserId, request.Amount);
|
||||||
|
|
||||||
|
return new DepositReply
|
||||||
|
{
|
||||||
|
Success = succ
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[GrpcNoAuthRequired]
|
||||||
|
public override async Task<WithdrawReply> Withdraw(WithdrawRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
if (request.Amount <= 0)
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Amount must be greater than 0"));
|
||||||
|
|
||||||
|
var succ = await _bank.WithdrawAsync(request.UserId, request.Amount);
|
||||||
|
|
||||||
|
return new WithdrawReply
|
||||||
|
{
|
||||||
|
Success = succ
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[GrpcNoAuthRequired]
|
||||||
|
public override async Task<GetHoldingsReply> GetHoldings(GetHoldingsRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
return new GetHoldingsReply
|
||||||
|
{
|
||||||
|
Bank = await _bank.GetBalanceAsync(request.UserId),
|
||||||
|
Cash = await _cs.GetBalanceAsync(request.UserId)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[GrpcNoAuthRequired]
|
||||||
|
public override async Task<GetTransactionsReply> GetTransactions(
|
||||||
|
GetTransactionsRequest request,
|
||||||
|
ServerCallContext context)
|
||||||
|
{
|
||||||
|
if (request.Page < 1)
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Page must be greater than 0"));
|
||||||
|
|
||||||
|
var trs = await _cs.GetTransactionsAsync(request.UserId, request.Page - 1);
|
||||||
|
|
||||||
|
var reply = new GetTransactionsReply
|
||||||
|
{
|
||||||
|
Total = await _cs.GetTransactionsCountAsync(request.UserId)
|
||||||
|
};
|
||||||
|
|
||||||
|
reply.Transactions.AddRange(trs.Select(x => new TransactionReply()
|
||||||
|
{
|
||||||
|
Id = new kwum(x.Id).ToString(),
|
||||||
|
Timestamp = Timestamp.FromDateTime(DateTime.UtcNow),
|
||||||
|
Amount = x.Amount,
|
||||||
|
Extra = x.Extra ?? string.Empty,
|
||||||
|
Note = x.Note ?? string.Empty,
|
||||||
|
Type = x.Type ?? string.Empty,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
}
|
@@ -17,10 +17,13 @@ public sealed class GreetByeSvc : GrpcGreet.GrpcGreetBase, IGrpcSvc, INService
|
|||||||
public ServerServiceDefinition Bind()
|
public ServerServiceDefinition Bind()
|
||||||
=> GrpcGreet.BindService(this);
|
=> GrpcGreet.BindService(this);
|
||||||
|
|
||||||
private static GrpcGreetSettings ToConf(GreetSettings? conf)
|
private static GrpcGreetSettings ToConf(GreetSettings? conf, GreetType type)
|
||||||
{
|
{
|
||||||
if (conf is null)
|
if (conf is null)
|
||||||
return new GrpcGreetSettings();
|
return new GrpcGreetSettings()
|
||||||
|
{
|
||||||
|
Type = (GrpcGreetType)type
|
||||||
|
};
|
||||||
|
|
||||||
return new GrpcGreetSettings()
|
return new GrpcGreetSettings()
|
||||||
{
|
{
|
||||||
@@ -35,9 +38,10 @@ public sealed class GreetByeSvc : GrpcGreet.GrpcGreetBase, IGrpcSvc, INService
|
|||||||
{
|
{
|
||||||
var guildId = request.GuildId;
|
var guildId = request.GuildId;
|
||||||
|
|
||||||
var conf = await _gs.GetGreetSettingsAsync(guildId, (GreetType)request.Type);
|
var type = (GreetType)request.Type;
|
||||||
|
var conf = await _gs.GetGreetSettingsAsync(guildId, type);
|
||||||
|
|
||||||
return ToConf(conf);
|
return ToConf(conf, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<UpdateGreetReply> UpdateGreet(UpdateGreetRequest request, ServerCallContext context)
|
public override async Task<UpdateGreetReply> UpdateGreet(UpdateGreetRequest request, ServerCallContext context)
|
||||||
|
95
src/NadekoBot/Services/GrpcApi/NCanvasSvc.cs
Normal file
95
src/NadekoBot/Services/GrpcApi/NCanvasSvc.cs
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
using Google.Protobuf.WellKnownTypes;
|
||||||
|
using Grpc.Core;
|
||||||
|
using NadekoBot.Db.Models;
|
||||||
|
using NadekoBot.Modules.Games;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
|
||||||
|
namespace NadekoBot.GrpcApi;
|
||||||
|
|
||||||
|
public class NCanvasSvc : GrpcNCanvas.GrpcNCanvasBase, IGrpcSvc, INService
|
||||||
|
{
|
||||||
|
private readonly INCanvasService _nCanvas;
|
||||||
|
private readonly DiscordSocketClient _client;
|
||||||
|
|
||||||
|
public NCanvasSvc(INCanvasService nCanvas, DiscordSocketClient client)
|
||||||
|
{
|
||||||
|
_nCanvas = nCanvas;
|
||||||
|
_client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerServiceDefinition Bind()
|
||||||
|
=> GrpcNCanvas.BindService(this);
|
||||||
|
|
||||||
|
[GrpcNoAuthRequired]
|
||||||
|
public override async Task<CanvasReply> GetCanvas(Empty request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
var pixels = await _nCanvas.GetCanvas();
|
||||||
|
var reply = new CanvasReply()
|
||||||
|
{
|
||||||
|
Width = _nCanvas.GetWidth(),
|
||||||
|
Height = _nCanvas.GetHeight()
|
||||||
|
};
|
||||||
|
reply.Pixels.AddRange(pixels);
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
[GrpcNoAuthRequired]
|
||||||
|
public override async Task<GetPixelReply> GetPixel(GetPixelRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
var pixel = await _nCanvas.GetPixel(request.X, request.Y);
|
||||||
|
if (pixel is null)
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, "Pixel not found"));
|
||||||
|
|
||||||
|
var reply = MapPixelToGrpcPixel(pixel);
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GetPixelReply MapPixelToGrpcPixel(NCPixel pixel)
|
||||||
|
{
|
||||||
|
var reply = new GetPixelReply
|
||||||
|
{
|
||||||
|
Color = "#" + new Rgba32(pixel.Color).ToHex(),
|
||||||
|
PackedColor = pixel.Color,
|
||||||
|
Position = new kwum(pixel.Position).ToString(),
|
||||||
|
PositionX = pixel.Position % _nCanvas.GetWidth(),
|
||||||
|
PositionY = pixel.Position / _nCanvas.GetWidth(),
|
||||||
|
// Owner = await ((IDiscordClient)_client).GetUserAsync(pixel.OwnerId)?.ToString() ?? string.Empty,
|
||||||
|
// OwnerId = pixel.OwnerId.ToString(),
|
||||||
|
Price = pixel.Price,
|
||||||
|
Text = pixel.Text
|
||||||
|
};
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
[GrpcNoAuthRequired]
|
||||||
|
public override async Task<SetPixelReply> SetPixel(SetPixelRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
if (!kwum.TryParse(request.Position, out var pos))
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Position is invalid"));
|
||||||
|
|
||||||
|
if (!Rgba32.TryParseHex(request.Color, out var clr))
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Color is invalid"));
|
||||||
|
|
||||||
|
var userId = context.RequestHeaders.GetUserId();
|
||||||
|
var result = await _nCanvas.SetPixel(pos, clr.PackedValue, request.Text, userId, request.Price);
|
||||||
|
var reply = new SetPixelReply()
|
||||||
|
{
|
||||||
|
Success = result == SetPixelResult.Success,
|
||||||
|
Error = result switch
|
||||||
|
{
|
||||||
|
SetPixelResult.Success => string.Empty,
|
||||||
|
SetPixelResult.InsufficientPayment => "You have to pay equal or more than the price.",
|
||||||
|
SetPixelResult.NotEnoughMoney => "You don't have enough currency. ",
|
||||||
|
SetPixelResult.InvalidInput =>
|
||||||
|
$"Invalid input. Position has to be >= 0 and < {_nCanvas.GetWidth()}x{_nCanvas.GetHeight()}",
|
||||||
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var pixel = await _nCanvas.GetPixel(pos);
|
||||||
|
if (pixel is not null)
|
||||||
|
reply.Pixel = MapPixelToGrpcPixel(pixel);
|
||||||
|
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
}
|
@@ -13,69 +13,67 @@ public static class GrpcApiExtensions
|
|||||||
|
|
||||||
public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, INService
|
public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, INService
|
||||||
{
|
{
|
||||||
private readonly IDiscordClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
private readonly XpService _xp;
|
private readonly XpService _xp;
|
||||||
private readonly ICurrencyService _cur;
|
private readonly ICurrencyService _cur;
|
||||||
private readonly WaifuService _waifus;
|
private readonly WaifuService _waifus;
|
||||||
private readonly ICoordinator _coord;
|
|
||||||
private readonly IStatsService _stats;
|
private readonly IStatsService _stats;
|
||||||
private readonly IBotCache _cache;
|
private readonly CommandHandler _cmdHandler;
|
||||||
|
|
||||||
public OtherSvc(
|
public OtherSvc(
|
||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
XpService xp,
|
XpService xp,
|
||||||
ICurrencyService cur,
|
ICurrencyService cur,
|
||||||
WaifuService waifus,
|
WaifuService waifus,
|
||||||
ICoordinator coord,
|
|
||||||
IStatsService stats,
|
IStatsService stats,
|
||||||
IBotCache cache)
|
CommandHandler cmdHandler)
|
||||||
{
|
{
|
||||||
_client = client;
|
_client = client;
|
||||||
_xp = xp;
|
_xp = xp;
|
||||||
_cur = cur;
|
_cur = cur;
|
||||||
_waifus = waifus;
|
_waifus = waifus;
|
||||||
_coord = coord;
|
|
||||||
_stats = stats;
|
_stats = stats;
|
||||||
_cache = cache;
|
_cmdHandler = cmdHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerServiceDefinition Bind()
|
public ServerServiceDefinition Bind()
|
||||||
=> GrpcOther.BindService(this);
|
=> GrpcOther.BindService(this);
|
||||||
|
|
||||||
[GrpcNoAuthRequired]
|
[GrpcNoAuthRequired]
|
||||||
public override async Task<BotOnGuildReply> BotOnGuild(BotOnGuildRequest request, ServerCallContext context)
|
public override Task<BotOnGuildReply> BotOnGuild(BotOnGuildRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
var guild = await _client.GetGuildAsync(request.GuildId);
|
var guild = _client.GetGuild(request.GuildId);
|
||||||
|
|
||||||
var reply = new BotOnGuildReply
|
var reply = new BotOnGuildReply
|
||||||
{
|
{
|
||||||
Success = guild is not null
|
Success = guild is not null
|
||||||
};
|
};
|
||||||
|
|
||||||
return reply;
|
return Task.FromResult(reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<GetRolesReply> GetRoles(GetRolesRequest request, ServerCallContext context)
|
public override Task<GetRolesReply> GetRoles(GetRolesRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
var g = await _client.GetGuildAsync(request.GuildId);
|
var g = _client.GetGuild(request.GuildId);
|
||||||
var roles = g?.Roles;
|
var roles = g?.Roles;
|
||||||
var reply = new GetRolesReply();
|
var reply = new GetRolesReply();
|
||||||
reply.Roles.AddRange(roles?.Select(x => new RoleReply()
|
reply.Roles.AddRange(roles?.Select(x => new RoleReply()
|
||||||
{
|
{
|
||||||
Id = x.Id,
|
Id = x.Id,
|
||||||
Name = x.Name,
|
Name = x.Name,
|
||||||
Color = x.Color.ToString(),
|
Color = x.Color.ToString(),
|
||||||
IconUrl = x.GetIconUrl() ?? string.Empty,
|
IconUrl = x.GetIconUrl() ?? string.Empty,
|
||||||
}) ?? new List<RoleReply>());
|
})
|
||||||
|
?? new List<RoleReply>());
|
||||||
|
|
||||||
return reply;
|
return Task.FromResult(reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<GetTextChannelsReply> GetTextChannels(
|
public override async Task<GetTextChannelsReply> GetTextChannels(
|
||||||
GetTextChannelsRequest request,
|
GetTextChannelsRequest request,
|
||||||
ServerCallContext context)
|
ServerCallContext context)
|
||||||
{
|
{
|
||||||
var g = await _client.GetGuildAsync(request.GuildId);
|
IGuild g = _client.GetGuild(request.GuildId);
|
||||||
var reply = new GetTextChannelsReply();
|
var reply = new GetTextChannelsReply();
|
||||||
|
|
||||||
var chs = await g.GetTextChannelsAsync();
|
var chs = await g.GetTextChannelsAsync();
|
||||||
@@ -89,33 +87,6 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, INService
|
|||||||
return reply;
|
return reply;
|
||||||
}
|
}
|
||||||
|
|
||||||
[GrpcNoAuthRequired]
|
|
||||||
public override async Task<GetGuildsReply> GetGuilds(Empty request, ServerCallContext context)
|
|
||||||
{
|
|
||||||
var guilds = await _client.GetGuildsAsync(CacheMode.CacheOnly);
|
|
||||||
|
|
||||||
var reply = new GetGuildsReply();
|
|
||||||
var userId = context.GetUserId();
|
|
||||||
|
|
||||||
var toReturn = new List<IGuild>();
|
|
||||||
foreach (var g in guilds)
|
|
||||||
{
|
|
||||||
var user = await g.GetUserAsync(userId);
|
|
||||||
if (user is not null && user.GuildPermissions.Has(GuildPermission.Administrator))
|
|
||||||
toReturn.Add(g);
|
|
||||||
}
|
|
||||||
|
|
||||||
reply.Guilds.AddRange(toReturn
|
|
||||||
.Select(x => new GuildReply()
|
|
||||||
{
|
|
||||||
Id = x.Id,
|
|
||||||
Name = x.Name,
|
|
||||||
IconUrl = x.IconUrl
|
|
||||||
}));
|
|
||||||
|
|
||||||
return reply;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[GrpcNoAuthRequired]
|
[GrpcNoAuthRequired]
|
||||||
public override async Task<CurrencyLbReply> GetCurrencyLb(GetLbRequest request, ServerCallContext context)
|
public override async Task<CurrencyLbReply> GetCurrencyLb(GetLbRequest request, ServerCallContext context)
|
||||||
@@ -123,16 +94,16 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, INService
|
|||||||
var users = await _cur.GetTopRichest(_client.CurrentUser.Id, request.Page, request.PerPage);
|
var users = await _cur.GetTopRichest(_client.CurrentUser.Id, request.Page, request.PerPage);
|
||||||
|
|
||||||
var reply = new CurrencyLbReply();
|
var reply = new CurrencyLbReply();
|
||||||
var entries = users.Select(async x =>
|
var entries = users.Select(x =>
|
||||||
{
|
{
|
||||||
var user = await _client.GetUserAsync(x.UserId, CacheMode.CacheOnly);
|
var user = _client.GetUser(x.UserId);
|
||||||
return new CurrencyLbEntryReply()
|
return Task.FromResult(new CurrencyLbEntryReply()
|
||||||
{
|
{
|
||||||
Amount = x.CurrencyAmount,
|
Amount = x.CurrencyAmount,
|
||||||
User = user?.ToString() ?? x.Username,
|
User = user?.ToString() ?? x.Username,
|
||||||
UserId = x.UserId,
|
UserId = x.UserId,
|
||||||
Avatar = user?.RealAvatarUrl().ToString() ?? x.RealAvatarUrl()?.ToString()
|
Avatar = user?.RealAvatarUrl().ToString() ?? x.RealAvatarUrl()?.ToString()
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
reply.Entries.AddRange(await entries.WhenAll());
|
reply.Entries.AddRange(await entries.WhenAll());
|
||||||
@@ -182,26 +153,66 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, INService
|
|||||||
}
|
}
|
||||||
|
|
||||||
[GrpcNoAuthRequired]
|
[GrpcNoAuthRequired]
|
||||||
public override async Task<GetShardStatusesReply> GetShardStatuses(Empty request, ServerCallContext context)
|
public override async Task GetShardStats(
|
||||||
|
Empty request,
|
||||||
|
IServerStreamWriter<ShardStatsReply> responseStream,
|
||||||
|
ServerCallContext context)
|
||||||
{
|
{
|
||||||
var reply = new GetShardStatusesReply();
|
while (true)
|
||||||
|
|
||||||
await _cache.GetOrAddAsync<List<ShardStatus>>("coord:statuses",
|
|
||||||
() => Task.FromResult(_coord.GetAllShardStatuses().ToList())!,
|
|
||||||
TimeSpan.FromMinutes(1));
|
|
||||||
|
|
||||||
var shards = _coord.GetAllShardStatuses();
|
|
||||||
|
|
||||||
reply.Shards.AddRange(shards.Select(x => new ShardStatusReply()
|
|
||||||
{
|
{
|
||||||
Id = x.ShardId,
|
var stats = new ShardStatsReply()
|
||||||
Status = x.ConnectionState.ToString(),
|
{
|
||||||
GuildCount = x.GuildCount,
|
Id = _client.ShardId,
|
||||||
LastUpdate = Timestamp.FromDateTime(x.LastUpdate),
|
Commands = _stats.CommandsRan,
|
||||||
}));
|
Uptime = _stats.GetUptimeString(),
|
||||||
|
Status = GetConnectionState(_client.ConnectionState),
|
||||||
|
GuildCount = _client.Guilds.Count,
|
||||||
|
};
|
||||||
|
|
||||||
return reply;
|
await responseStream.WriteAsync(stats);
|
||||||
|
await Task.Delay(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[GrpcNoAuthRequired]
|
||||||
|
public override async Task GetCommandFeed(
|
||||||
|
Empty request,
|
||||||
|
IServerStreamWriter<CommandFeedEntry> responseStream,
|
||||||
|
ServerCallContext context)
|
||||||
|
{
|
||||||
|
var taskCompletion = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
|
Task OnCommandExecuted(IUserMessage userMessage, CommandInfo commandInfo)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
responseStream.WriteAsync(new()
|
||||||
|
{
|
||||||
|
Command = commandInfo.Name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_cmdHandler.CommandExecuted -= OnCommandExecuted;
|
||||||
|
taskCompletion.TrySetResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
_cmdHandler.CommandExecuted += OnCommandExecuted;
|
||||||
|
|
||||||
|
await taskCompletion.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetConnectionState(ConnectionState clientConnectionState)
|
||||||
|
{
|
||||||
|
return clientConnectionState switch
|
||||||
|
{
|
||||||
|
ConnectionState.Connected => "Connected",
|
||||||
|
ConnectionState.Connecting => "Connecting",
|
||||||
|
_ => "Disconnected"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<GetServerInfoReply> GetServerInfo(ServerInfoRequest request, ServerCallContext context)
|
public override async Task<GetServerInfoReply> GetServerInfo(ServerInfoRequest request, ServerCallContext context)
|
||||||
|
282
src/NadekoBot/Services/GrpcApi/XpSvc.cs
Normal file
282
src/NadekoBot/Services/GrpcApi/XpSvc.cs
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
using Google.Protobuf.WellKnownTypes;
|
||||||
|
using Grpc.Core;
|
||||||
|
using NadekoBot.Db.Models;
|
||||||
|
using NadekoBot.Modules.Gambling.Bank;
|
||||||
|
using NadekoBot.Modules.NadekoExpressions;
|
||||||
|
using NadekoBot.Modules.Utility;
|
||||||
|
using NadekoBot.Modules.Xp.Services;
|
||||||
|
|
||||||
|
namespace NadekoBot.GrpcApi;
|
||||||
|
|
||||||
|
public class XpSvc : GrpcXp.GrpcXpBase, IGrpcSvc, INService
|
||||||
|
{
|
||||||
|
private readonly XpService _xp;
|
||||||
|
private readonly DiscordSocketClient _client;
|
||||||
|
private readonly IUserService _duSvc;
|
||||||
|
|
||||||
|
public XpSvc(XpService xp, DiscordSocketClient client, IUserService duSvc)
|
||||||
|
{
|
||||||
|
_xp = xp;
|
||||||
|
_client = client;
|
||||||
|
_duSvc = duSvc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerServiceDefinition Bind()
|
||||||
|
=> GrpcXp.BindService(this);
|
||||||
|
|
||||||
|
public override async Task<GetXpSettingsReply> GetXpSettings(
|
||||||
|
GetXpSettingsRequest request,
|
||||||
|
ServerCallContext context)
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
|
var guild = _client.GetGuild(request.GuildId);
|
||||||
|
|
||||||
|
if (guild is null)
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, "Guild not found"));
|
||||||
|
|
||||||
|
var excludedChannels = _xp.GetExcludedChannels(request.GuildId);
|
||||||
|
var excludedRoles = _xp.GetExcludedRoles(request.GuildId);
|
||||||
|
var isServerExcluded = _xp.IsServerExcluded(request.GuildId);
|
||||||
|
|
||||||
|
var reply = new GetXpSettingsReply();
|
||||||
|
|
||||||
|
reply.Exclusions.AddRange(excludedChannels
|
||||||
|
.Select(x => new ExclItemReply()
|
||||||
|
{
|
||||||
|
Id = x,
|
||||||
|
Type = "Channel",
|
||||||
|
Name = guild.GetChannel(x)?.Name ?? "????"
|
||||||
|
})
|
||||||
|
.Concat(excludedRoles
|
||||||
|
.Select(x => new ExclItemReply()
|
||||||
|
{
|
||||||
|
Id = x,
|
||||||
|
Type = "Role",
|
||||||
|
Name = guild.GetRole(x)?.Name ?? "????"
|
||||||
|
})));
|
||||||
|
|
||||||
|
var curRews = _xp.GetCurrencyRewards(request.GuildId);
|
||||||
|
var roleRews = _xp.GetRoleRewards(request.GuildId);
|
||||||
|
|
||||||
|
var rews = curRews.Select(x => new RewItemReply()
|
||||||
|
{
|
||||||
|
Level = x.Level,
|
||||||
|
Type = "Currency",
|
||||||
|
Value = x.Amount.ToString()
|
||||||
|
});
|
||||||
|
|
||||||
|
rews = rews.Concat(roleRews.Select(x => new RewItemReply()
|
||||||
|
{
|
||||||
|
Level = x.Level,
|
||||||
|
Type = x.Remove ? "RemoveRole" : "AddRole",
|
||||||
|
Value = guild.GetRole(x.RoleId)?.ToString() ?? x.RoleId.ToString()
|
||||||
|
}))
|
||||||
|
.OrderBy(x => x.Level);
|
||||||
|
|
||||||
|
reply.Rewards.AddRange(rews);
|
||||||
|
|
||||||
|
reply.ServerExcluded = isServerExcluded;
|
||||||
|
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<AddExclusionReply> AddExclusion(AddExclusionRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
|
var success = false;
|
||||||
|
var guild = _client.GetGuild(request.GuildId);
|
||||||
|
|
||||||
|
if (guild is null)
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, "Guild not found"));
|
||||||
|
|
||||||
|
if (request.Type == "Role")
|
||||||
|
{
|
||||||
|
if (guild.GetRole(request.Id) is null)
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Success = false
|
||||||
|
};
|
||||||
|
|
||||||
|
success = _xp.ToggleExcludeRole(request.GuildId, request.Id);
|
||||||
|
}
|
||||||
|
else if (request.Type == "Channel")
|
||||||
|
{
|
||||||
|
if (guild.GetTextChannel(request.Id) is null)
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Success = false
|
||||||
|
};
|
||||||
|
|
||||||
|
success = _xp.ToggleExcludeChannel(request.GuildId, request.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Success = success
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<DeleteExclusionReply> DeleteExclusion(
|
||||||
|
DeleteExclusionRequest request,
|
||||||
|
ServerCallContext context)
|
||||||
|
{
|
||||||
|
var success = false;
|
||||||
|
if (request.Type == "Role")
|
||||||
|
success = _xp.ToggleExcludeRole(request.GuildId, request.Id);
|
||||||
|
else
|
||||||
|
success = _xp.ToggleExcludeChannel(request.GuildId, request.Id);
|
||||||
|
|
||||||
|
return Task.FromResult(new DeleteExclusionReply
|
||||||
|
{
|
||||||
|
Success = success
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<AddRewardReply> AddReward(AddRewardRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
|
var success = false;
|
||||||
|
var guild = _client.GetGuild(request.GuildId);
|
||||||
|
|
||||||
|
if (guild is null)
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, "Guild not found"));
|
||||||
|
|
||||||
|
if (request.Type == "AddRole" || request.Type == "RemoveRole")
|
||||||
|
{
|
||||||
|
if (!ulong.TryParse(request.Value, out var rid))
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Invalid role id"));
|
||||||
|
|
||||||
|
var role = guild.GetRole(rid);
|
||||||
|
if (role is null)
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Success = false
|
||||||
|
};
|
||||||
|
|
||||||
|
_xp.SetRoleReward(request.GuildId, request.Level, rid, request.Type == "RemoveRole");
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
// else if (request.Type == "Currency")
|
||||||
|
// {
|
||||||
|
// if (!int.TryParse(request.Value, out var amount))
|
||||||
|
// throw new RpcException(new Status(StatusCode.InvalidArgument, "Invalid amount"));
|
||||||
|
//
|
||||||
|
// _xp.SetCurrencyReward(request.GuildId, request.Level, amount);
|
||||||
|
// success = true;
|
||||||
|
// }
|
||||||
|
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Success = success
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<DeleteRewardReply> DeleteReward(DeleteRewardRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
var success = false;
|
||||||
|
|
||||||
|
if (request.Type == "AddRole" || request.Type == "RemoveRole")
|
||||||
|
{
|
||||||
|
_xp.ResetRoleReward(request.GuildId, request.Level);
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
else if (request.Type == "Currency")
|
||||||
|
{
|
||||||
|
_xp.SetCurrencyReward(request.GuildId, request.Level, 0);
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(new DeleteRewardReply
|
||||||
|
{
|
||||||
|
Success = success
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<ResetUserXpReply> ResetUserXp(ResetUserXpRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
await _xp.XpReset(request.GuildId, request.UserId);
|
||||||
|
|
||||||
|
return new ResetUserXpReply
|
||||||
|
{
|
||||||
|
Success = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<GetXpLbReply> GetXpLb(GetXpLbRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
if (request.Page < 1)
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument, "Page must be greater than or equal to 1"));
|
||||||
|
|
||||||
|
var guild = _client.GetGuild(request.GuildId);
|
||||||
|
|
||||||
|
if (guild is null)
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, "Guild not found"));
|
||||||
|
|
||||||
|
var data = await _xp.GetGuildUserXps(request.GuildId, request.Page - 1);
|
||||||
|
var total = await _xp.GetTotalGuildUsers(request.GuildId);
|
||||||
|
|
||||||
|
var reply = new GetXpLbReply
|
||||||
|
{
|
||||||
|
Total = total
|
||||||
|
};
|
||||||
|
|
||||||
|
var users = await data
|
||||||
|
.Select(async x =>
|
||||||
|
{
|
||||||
|
var user = guild.GetUser(x.UserId);
|
||||||
|
|
||||||
|
if (user is null)
|
||||||
|
{
|
||||||
|
var du = await _duSvc.GetUserAsync(x.UserId);
|
||||||
|
if (du is null)
|
||||||
|
return new XpLbUserReply
|
||||||
|
{
|
||||||
|
UserId = x.UserId,
|
||||||
|
Avatar = string.Empty,
|
||||||
|
Username = string.Empty,
|
||||||
|
Xp = x.Xp,
|
||||||
|
Level = new LevelStats(x.Xp).Level
|
||||||
|
};
|
||||||
|
|
||||||
|
return new XpLbUserReply()
|
||||||
|
{
|
||||||
|
UserId = x.UserId,
|
||||||
|
Avatar = du.RealAvatarUrl()?.ToString() ?? string.Empty,
|
||||||
|
Username = du.ToString() ?? string.Empty,
|
||||||
|
Xp = x.Xp,
|
||||||
|
Level = new LevelStats(x.Xp).Level
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new XpLbUserReply
|
||||||
|
{
|
||||||
|
UserId = x.UserId,
|
||||||
|
Avatar = user?.GetAvatarUrl() ?? string.Empty,
|
||||||
|
Username = user?.ToString() ?? string.Empty,
|
||||||
|
Xp = x.Xp,
|
||||||
|
Level = new LevelStats(x.Xp).Level
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.WhenAll();
|
||||||
|
|
||||||
|
reply.Users.AddRange(users);
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@@ -14,10 +14,7 @@ public sealed partial class GrpcApiPermsInterceptor : Interceptor
|
|||||||
_client = client;
|
_client = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
|
private async Task RequestHandler(ServerCallContext context)
|
||||||
TRequest request,
|
|
||||||
ServerCallContext context,
|
|
||||||
UnaryServerMethod<TRequest, TResponse> continuation)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -42,7 +39,7 @@ public sealed partial class GrpcApiPermsInterceptor : Interceptor
|
|||||||
|
|
||||||
// if the method is explicitly marked as not requiring auth
|
// if the method is explicitly marked as not requiring auth
|
||||||
if (_noAuthRequired.Contains(method))
|
if (_noAuthRequired.Contains(method))
|
||||||
return await continuation(request, context);
|
return;
|
||||||
|
|
||||||
// otherwise the method requires auth, and if it requires auth then the guildid has to be specified
|
// otherwise the method requires auth, and if it requires auth then the guildid has to be specified
|
||||||
if (string.IsNullOrWhiteSpace(gidString))
|
if (string.IsNullOrWhiteSpace(gidString))
|
||||||
@@ -61,8 +58,6 @@ public sealed partial class GrpcApiPermsInterceptor : Interceptor
|
|||||||
// if not then use the default, which is Administrator permission
|
// if not then use the default, which is Administrator permission
|
||||||
await EnsureUserHasPermission(guildId, userId, DEFAULT_PERMISSION);
|
await EnsureUserHasPermission(guildId, userId, DEFAULT_PERMISSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await continuation(request, context);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -70,7 +65,7 @@ public sealed partial class GrpcApiPermsInterceptor : Interceptor
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task EnsureUserHasPermission(ulong guildId, ulong userId, GuildPerm perm)
|
private async Task EnsureUserHasPermission(ulong guildId, ulong userId, GuildPerm perm)
|
||||||
{
|
{
|
||||||
IGuild guild = _client.GetGuild(guildId);
|
IGuild guild = _client.GetGuild(guildId);
|
||||||
@@ -83,4 +78,42 @@ public sealed partial class GrpcApiPermsInterceptor : Interceptor
|
|||||||
throw new RpcException(new Status(StatusCode.PermissionDenied,
|
throw new RpcException(new Status(StatusCode.PermissionDenied,
|
||||||
$"You need {perm} permission to use this method"));
|
$"You need {perm} permission to use this method"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override async Task<TResponse> ClientStreamingServerHandler<TRequest, TResponse>(
|
||||||
|
IAsyncStreamReader<TRequest> requestStream,
|
||||||
|
ServerCallContext context,
|
||||||
|
ClientStreamingServerMethod<TRequest, TResponse> continuation)
|
||||||
|
{
|
||||||
|
await RequestHandler(context);
|
||||||
|
return await continuation(requestStream, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task DuplexStreamingServerHandler<TRequest, TResponse>(
|
||||||
|
IAsyncStreamReader<TRequest> requestStream,
|
||||||
|
IServerStreamWriter<TResponse> responseStream,
|
||||||
|
ServerCallContext context,
|
||||||
|
DuplexStreamingServerMethod<TRequest, TResponse> continuation)
|
||||||
|
{
|
||||||
|
await RequestHandler(context);
|
||||||
|
await continuation(requestStream, responseStream, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task ServerStreamingServerHandler<TRequest, TResponse>(
|
||||||
|
TRequest request,
|
||||||
|
IServerStreamWriter<TResponse> responseStream,
|
||||||
|
ServerCallContext context,
|
||||||
|
ServerStreamingServerMethod<TRequest, TResponse> continuation)
|
||||||
|
{
|
||||||
|
await RequestHandler(context);
|
||||||
|
await continuation(request, responseStream, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
|
||||||
|
TRequest request,
|
||||||
|
ServerCallContext context,
|
||||||
|
UnaryServerMethod<TRequest, TResponse> continuation)
|
||||||
|
{
|
||||||
|
await RequestHandler(context);
|
||||||
|
return await continuation(request, context);
|
||||||
|
}
|
||||||
}
|
}
|
9
src/NadekoBot/Services/SvcExtensions.cs
Normal file
9
src/NadekoBot/Services/SvcExtensions.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using Grpc.Core;
|
||||||
|
|
||||||
|
namespace NadekoBot.GrpcApi;
|
||||||
|
|
||||||
|
public static class SvcExtensions
|
||||||
|
{
|
||||||
|
public static ulong GetUserId(this Metadata meta)
|
||||||
|
=> ulong.Parse(meta.FirstOrDefault(x => x.Key == "userid")!.Value);
|
||||||
|
}
|
@@ -76,6 +76,9 @@ public readonly struct kwum : IEquatable<kwum>
|
|||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
if (_value == 0)
|
||||||
|
return VALID_CHARACTERS[0].ToString();
|
||||||
|
|
||||||
var count = VALID_CHARACTERS.Length;
|
var count = VALID_CHARACTERS.Length;
|
||||||
var localValue = _value;
|
var localValue = _value;
|
||||||
var arrSize = (int)Math.Log(localValue, count) + 1;
|
var arrSize = (int)Math.Log(localValue, count) + 1;
|
||||||
|
@@ -40,4 +40,11 @@ public interface ICurrencyService
|
|||||||
TxData? txData);
|
TxData? txData);
|
||||||
|
|
||||||
Task<IReadOnlyList<DiscordUser>> GetTopRichest(ulong ignoreId, int page = 0, int perPage = 9);
|
Task<IReadOnlyList<DiscordUser>> GetTopRichest(ulong ignoreId, int page = 0, int perPage = 9);
|
||||||
|
|
||||||
|
Task<IReadOnlyList<CurrencyTransaction>> GetTransactionsAsync(
|
||||||
|
ulong userId,
|
||||||
|
int page,
|
||||||
|
int perPage = 15);
|
||||||
|
|
||||||
|
Task<int> GetTransactionsCountAsync(ulong userId);
|
||||||
}
|
}
|
@@ -4,6 +4,6 @@ namespace NadekoBot.Services;
|
|||||||
|
|
||||||
public interface ITxTracker
|
public interface ITxTracker
|
||||||
{
|
{
|
||||||
Task TrackAdd(long amount, TxData? txData);
|
Task TrackAdd(ulong userId, long amount, TxData? txData);
|
||||||
Task TrackRemove(long amount, TxData? txData);
|
Task TrackRemove(ulong userId, long amount, TxData? txData);
|
||||||
}
|
}
|
@@ -11,9 +11,9 @@ public class SlotGame
|
|||||||
{
|
{
|
||||||
var rolls = new[]
|
var rolls = new[]
|
||||||
{
|
{
|
||||||
(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)
|
(byte)_rng.Next(0, 7)
|
||||||
};
|
};
|
||||||
|
|
||||||
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 == 5)
|
if (a == 6)
|
||||||
{
|
{
|
||||||
winType = SlotWinType.TrippleJoker;
|
winType = SlotWinType.TrippleJoker;
|
||||||
multi = 30;
|
multi = 25;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
winType = SlotWinType.TrippleNormal;
|
winType = SlotWinType.TrippleNormal;
|
||||||
multi = 10;
|
multi = 15;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (a == 5 && (b == 5 || c == 5)
|
else if (a == 6 && (b == 6 || c == 6)
|
||||||
|| (b == 5 && c == 5))
|
|| (b == 6 && c == 6))
|
||||||
{
|
{
|
||||||
winType = SlotWinType.DoubleJoker;
|
winType = SlotWinType.DoubleJoker;
|
||||||
multi = 4;
|
multi = 6;
|
||||||
}
|
}
|
||||||
else if (a == 5 || b == 5 || c == 5)
|
else if (a == 6 || b == 6 || c == 6)
|
||||||
{
|
{
|
||||||
winType = SlotWinType.SingleJoker;
|
winType = SlotWinType.SingleJoker;
|
||||||
multi = 1;
|
multi = 1;
|
||||||
|
@@ -8,7 +8,7 @@ namespace NadekoBot.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; } = 5;
|
public int Version { get; set; } = 6;
|
||||||
|
|
||||||
public CoinData Coins { get; set; }
|
public CoinData Coins { get; set; }
|
||||||
public Uri[] Currency { get; set; }
|
public Uri[] Currency { get; set; }
|
||||||
|
@@ -201,9 +201,12 @@ public sealed partial class GoogleApiService : IGoogleApiService, INService
|
|||||||
{
|
{
|
||||||
string text;
|
string text;
|
||||||
|
|
||||||
if (!Languages.ContainsKey(sourceLanguage) || !Languages.ContainsKey(targetLanguage))
|
if (!Languages.ContainsKey(targetLanguage))
|
||||||
throw new ArgumentException(nameof(sourceLanguage) + "/" + nameof(targetLanguage));
|
throw new ArgumentException(nameof(sourceLanguage) + "/" + nameof(targetLanguage));
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(sourceLanguage) || !Languages.ContainsKey(sourceLanguage))
|
||||||
|
sourceLanguage = "auto";
|
||||||
|
|
||||||
|
|
||||||
var url = new Uri(string.Format(
|
var url = new Uri(string.Format(
|
||||||
"https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}",
|
"https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}",
|
||||||
@@ -223,7 +226,7 @@ public sealed partial class GoogleApiService : IGoogleApiService, INService
|
|||||||
private string ConvertToLanguageCode(string language)
|
private string ConvertToLanguageCode(string language)
|
||||||
{
|
{
|
||||||
Languages.TryGetValue(language, out var mode);
|
Languages.TryGetValue(language, out var mode);
|
||||||
return mode;
|
return string.IsNullOrWhiteSpace(mode) ? language : mode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -154,7 +154,6 @@ public sealed partial class GoogleApiService
|
|||||||
}
|
}
|
||||||
|
|
||||||
Languages = langs;
|
Languages = langs;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -12,7 +12,11 @@ public sealed partial class ReplacementPatternStore
|
|||||||
{
|
{
|
||||||
Register("%bot.time%",
|
Register("%bot.time%",
|
||||||
static ()
|
static ()
|
||||||
=> DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()));
|
=> TimestampTag.FromDateTime(DateTime.UtcNow, TimestampTagStyles.ShortTime).ToString());
|
||||||
|
|
||||||
|
Register("%bot.date%",
|
||||||
|
static ()
|
||||||
|
=> TimestampTag.FromDateTime(DateTime.UtcNow, TimestampTagStyles.ShortDate).ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WithClient()
|
private void WithClient()
|
||||||
|
@@ -165,7 +165,7 @@ public class CommandHandler : INService, 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,
|
usrMsg.Author.Id.ToString(),
|
||||||
usrMsg.Content.TrimTo(10));
|
usrMsg.Content.TrimTo(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -55,14 +55,14 @@ public sealed class CurrencyService : ICurrencyService, INService
|
|||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
await ctx
|
await ctx
|
||||||
.GetTable<DiscordUser>()
|
.GetTable<DiscordUser>()
|
||||||
.Where(x => userIds.Contains(x.UserId))
|
.Where(x => userIds.Contains(x.UserId))
|
||||||
.UpdateAsync(du => new()
|
.UpdateAsync(du => new()
|
||||||
{
|
{
|
||||||
CurrencyAmount = du.CurrencyAmount >= amount
|
CurrencyAmount = du.CurrencyAmount >= amount
|
||||||
? du.CurrencyAmount - amount
|
? du.CurrencyAmount - amount
|
||||||
: 0
|
: 0
|
||||||
});
|
});
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,7 @@ public sealed class CurrencyService : ICurrencyService, INService
|
|||||||
{
|
{
|
||||||
var wallet = await GetWalletAsync(userId);
|
var wallet = await GetWalletAsync(userId);
|
||||||
await wallet.Add(amount, txData);
|
await wallet.Add(amount, txData);
|
||||||
await _txTracker.TrackAdd(amount, txData);
|
await _txTracker.TrackAdd(userId, amount, txData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddAsync(
|
public async Task AddAsync(
|
||||||
@@ -97,7 +97,7 @@ public sealed class CurrencyService : ICurrencyService, INService
|
|||||||
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(amount, txData);
|
await _txTracker.TrackRemove(userId, amount, txData);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,4 +112,29 @@ public sealed class CurrencyService : ICurrencyService, INService
|
|||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
return await uow.Set<DiscordUser>().GetTopRichest(ignoreId, page, perPage);
|
return await uow.Set<DiscordUser>().GetTopRichest(ignoreId, page, perPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<CurrencyTransaction>> GetTransactionsAsync(
|
||||||
|
ulong userId,
|
||||||
|
int page,
|
||||||
|
int perPage = 15)
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
|
||||||
|
var trs = await uow.GetTable<CurrencyTransaction>()
|
||||||
|
.Where(x => x.UserId == userId)
|
||||||
|
.OrderByDescending(x => x.DateAdded)
|
||||||
|
.Skip(perPage * page)
|
||||||
|
.Take(perPage)
|
||||||
|
.ToListAsyncLinqToDB();
|
||||||
|
|
||||||
|
return trs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> GetTransactionsCountAsync(ulong userId)
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
return await uow.GetTable<CurrencyTransaction>()
|
||||||
|
.Where(x => x.UserId == userId)
|
||||||
|
.CountAsyncLinqToDB();
|
||||||
|
}
|
||||||
}
|
}
|
@@ -77,8 +77,7 @@ public class DefaultWallet : IWallet
|
|||||||
.InsertOrUpdateAsync(() => new()
|
.InsertOrUpdateAsync(() => new()
|
||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
Username = "Unknown",
|
Username = "??Unknown",
|
||||||
Discriminator = "????",
|
|
||||||
CurrencyAmount = amount,
|
CurrencyAmount = amount,
|
||||||
},
|
},
|
||||||
(old) => new()
|
(old) => new()
|
||||||
|
@@ -1,8 +1,11 @@
|
|||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
|
using LinqToDB.Data;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using NadekoBot.Common.ModuleBehaviors;
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
using NadekoBot.Services.Currency;
|
using NadekoBot.Services.Currency;
|
||||||
using NadekoBot.Db.Models;
|
using NadekoBot.Db.Models;
|
||||||
|
using NadekoBot.Modules.Gambling;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
namespace NadekoBot.Services;
|
namespace NadekoBot.Services;
|
||||||
|
|
||||||
@@ -10,15 +13,11 @@ public sealed class GamblingTxTracker : ITxTracker, INService, IReadyExecutor
|
|||||||
{
|
{
|
||||||
private static readonly IReadOnlySet<string> _gamblingTypes = new HashSet<string>(new[]
|
private static readonly IReadOnlySet<string> _gamblingTypes = new HashSet<string>(new[]
|
||||||
{
|
{
|
||||||
"lula",
|
"lula", "betroll", "betflip", "blackjack", "betdraw", "slot",
|
||||||
"betroll",
|
|
||||||
"betflip",
|
|
||||||
"blackjack",
|
|
||||||
"betdraw",
|
|
||||||
"slot",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
private ConcurrentDictionary<string, (decimal Bet, decimal PaidOut)> _stats = new();
|
private NonBlocking.ConcurrentDictionary<string, (decimal Bet, decimal PaidOut)> globalStats = new();
|
||||||
|
private ConcurrentBag<UserBetStats> userStats = new();
|
||||||
|
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
|
|
||||||
@@ -28,83 +27,333 @@ public sealed class GamblingTxTracker : ITxTracker, INService, 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
|
||||||
{
|
{
|
||||||
var keys = _stats.Keys;
|
// update betstats
|
||||||
|
var keys = globalStats.Keys;
|
||||||
foreach (var key in keys)
|
foreach (var key in keys)
|
||||||
{
|
{
|
||||||
if (_stats.TryRemove(key, out var stat))
|
if (globalStats.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,
|
{
|
||||||
PaidOut = old.PaidOut + stat.PaidOut,
|
Bet = old.Bet + stat.Bet,
|
||||||
}, () => new()
|
PaidOut = old.PaidOut + stat.PaidOut,
|
||||||
{
|
},
|
||||||
Feature = key
|
() => new()
|
||||||
});
|
{
|
||||||
|
Feature = key
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Error(ex, "An error occurred in gambling tx tracker");
|
Log.Error(ex, "An error occurred in betstats gambling tx tracker");
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
await trans.CommitAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task TrackAdd(long amount, TxData? txData)
|
private async Task RunUserStatsCollector()
|
||||||
|
{
|
||||||
|
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))
|
||||||
{
|
{
|
||||||
_stats.AddOrUpdate(txData.Type,
|
globalStats.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(long amount, TxData? txData)
|
public Task TrackRemove(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))
|
||||||
{
|
{
|
||||||
_stats.AddOrUpdate(txData.Type,
|
globalStats.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; }
|
||||||
}
|
}
|
@@ -4,10 +4,11 @@ using LinqToDB.Data;
|
|||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using NadekoBot.Common.ModuleBehaviors;
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
using NadekoBot.Db.Models;
|
using NadekoBot.Db.Models;
|
||||||
|
using System.Collections.Frozen;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Permissions.Services;
|
namespace NadekoBot.Modules.Permissions.Services;
|
||||||
|
|
||||||
public sealed class BlacklistService : IExecOnMessage
|
public sealed class BlacklistService : IExecOnMessage, IReadyExecutor
|
||||||
{
|
{
|
||||||
public int Priority
|
public int Priority
|
||||||
=> int.MaxValue;
|
=> int.MaxValue;
|
||||||
@@ -15,69 +16,115 @@ public sealed class BlacklistService : IExecOnMessage
|
|||||||
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 IReadOnlyList<BlacklistEntry> blacklist;
|
private readonly DiscordSocketClient _client;
|
||||||
|
|
||||||
private readonly TypedKey<BlacklistEntry[]> _blPubKey = new("blacklist.reload");
|
private FrozenSet<ulong> blacklistedGuilds = new HashSet<ulong>().ToFrozenSet();
|
||||||
|
private FrozenSet<ulong> blacklistedUsers = new HashSet<ulong>().ToFrozenSet();
|
||||||
|
private FrozenSet<ulong> blacklistedChannels = new HashSet<ulong>().ToFrozenSet();
|
||||||
|
|
||||||
public BlacklistService(DbService db, IPubSub pubSub, IBotCreds creds)
|
private readonly TypedKey<bool> _blPubKey = new("blacklist.reload");
|
||||||
|
|
||||||
|
public BlacklistService(
|
||||||
|
DbService db,
|
||||||
|
IPubSub pubSub,
|
||||||
|
IBotCreds creds,
|
||||||
|
DiscordSocketClient client)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_pubSub = pubSub;
|
_pubSub = pubSub;
|
||||||
_creds = creds;
|
_creds = creds;
|
||||||
|
_client = client;
|
||||||
|
|
||||||
Reload(false);
|
_pubSub.Sub(_blPubKey, async _ => await 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)
|
||||||
{
|
{
|
||||||
blacklist = newBlacklist;
|
if (newBlacklist is null)
|
||||||
|
return default;
|
||||||
|
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
foreach (var bl in blacklist)
|
if (blacklistedGuilds.Contains(guild.Id))
|
||||||
{
|
{
|
||||||
if (guild is not null && bl.Type == BlacklistType.Server && bl.ItemId == guild.Id)
|
Log.Information("Blocked input from blacklisted guild: {GuildName} [{GuildId}]",
|
||||||
{
|
guild.Name,
|
||||||
Log.Information("Blocked input from blacklisted guild: {GuildName} [{GuildId}]", guild.Name, guild.Id);
|
guild.Id.ToString());
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
return Task.FromResult(true);
|
if (blacklistedChannels.Contains(usrMsg.Channel.Id))
|
||||||
}
|
{
|
||||||
|
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);
|
|
||||||
|
|
||||||
return Task.FromResult(true);
|
if (blacklistedUsers.Contains(usrMsg.Author.Id))
|
||||||
}
|
{
|
||||||
|
Log.Information("Blocked input from blacklisted user: {UserName} [{UserId}]",
|
||||||
if (bl.Type == BlacklistType.User && bl.ItemId == usrMsg.Author.Id)
|
usrMsg.Author.ToString(),
|
||||||
{
|
usrMsg.Author.Id.ToString());
|
||||||
Log.Information("Blocked input from blacklisted user: {UserName} [{UserId}]",
|
return Task.FromResult(true);
|
||||||
usrMsg.Author.ToString(),
|
|
||||||
usrMsg.Author.Id);
|
|
||||||
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult(false);
|
return Task.FromResult(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<BlacklistEntry> GetBlacklist()
|
public async Task<IReadOnlyList<BlacklistEntry>> GetBlacklist(BlacklistType type)
|
||||||
=> blacklist;
|
|
||||||
|
|
||||||
public void Reload(bool publish = true)
|
|
||||||
{
|
{
|
||||||
using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
var toPublish = uow.GetTable<BlacklistEntry>().ToArray();
|
|
||||||
blacklist = toPublish;
|
return await uow
|
||||||
|
.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)
|
||||||
@@ -88,34 +135,34 @@ public sealed class BlacklistService : IExecOnMessage
|
|||||||
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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Reload();
|
await 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();
|
||||||
|
|
||||||
Reload();
|
await Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task BlacklistUsers(IReadOnlyCollection<ulong> toBlacklist)
|
public async Task BlacklistUsers(IReadOnlyCollection<ulong> toBlacklist)
|
||||||
@@ -130,12 +177,12 @@ public sealed class BlacklistService : IExecOnMessage
|
|||||||
|
|
||||||
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
|
||||||
});
|
});
|
||||||
|
|
||||||
Reload();
|
await Reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -27,5 +27,22 @@ 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -193,7 +193,7 @@ public sealed class StatsService : IStatsService, IReadyExecutor, INService
|
|||||||
Id = g.Id,
|
Id = g.Id,
|
||||||
IconUrl = g.IconUrl,
|
IconUrl = g.IconUrl,
|
||||||
Name = g.Name,
|
Name = g.Name,
|
||||||
Owner = (await ig.GetUserAsync(g.OwnerId))?.Username ?? "Unknown",
|
Owner = (await ig.GetUserAsync(g.OwnerId))?.Username ?? "??Unknown",
|
||||||
OwnerId = g.OwnerId,
|
OwnerId = g.OwnerId,
|
||||||
CreatedAt = g.CreatedAt.UtcDateTime,
|
CreatedAt = g.CreatedAt.UtcDateTime,
|
||||||
VoiceChannels = g.VoiceChannels.Count,
|
VoiceChannels = g.VoiceChannels.Count,
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user