mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
Compare commits
50 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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 | ||
|
477581f616 | ||
|
ff30105816 | ||
|
49f04a594b | ||
|
716090a132 | ||
|
c835514c7b | ||
|
b136e7ff0e | ||
|
9dd2997b0f | ||
|
fde5309ea4 | ||
|
a8e4173e9b | ||
|
74b4c4b64d | ||
|
6cc5a160a2 | ||
|
ca8e022db6 | ||
|
cd8c14c607 | ||
|
1340533c21 | ||
|
14d86b9042 | ||
|
3a504a954f | ||
|
822ce0b8de | ||
|
40490a4656 | ||
|
0cf7909fef |
755
CHANGELOG.md
755
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
|
||||
|
@@ -12,13 +12,14 @@ namespace NadekoBot.Generators
|
||||
{
|
||||
public readonly record struct MethodPermData
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly string Value;
|
||||
public readonly ImmutableArray<(string Name, string Value)> MethodPerms;
|
||||
public readonly ImmutableArray<string> NoAuthRequired;
|
||||
|
||||
public MethodPermData(string name, string value)
|
||||
public MethodPermData(ImmutableArray<(string Name, string Value)> methodPerms,
|
||||
ImmutableArray<string> noAuthRequired)
|
||||
{
|
||||
Name = name;
|
||||
Value = value;
|
||||
MethodPerms = methodPerms;
|
||||
NoAuthRequired = noAuthRequired;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +27,7 @@ namespace NadekoBot.Generators
|
||||
[Generator]
|
||||
public class GrpcApiPermGenerator : IIncrementalGenerator
|
||||
{
|
||||
public const string Attribute =
|
||||
public const string GRPC_API_PERM_ATTRIBUTE =
|
||||
"""
|
||||
namespace NadekoBot.GrpcApi;
|
||||
|
||||
@@ -37,13 +38,26 @@ namespace NadekoBot.Generators
|
||||
public GrpcApiPermAttribute(GuildPerm value) => Value = value;
|
||||
}
|
||||
""";
|
||||
|
||||
public const string GRPC_NO_AUTH_REQUIRED_ATTRIBUTE =
|
||||
"""
|
||||
namespace NadekoBot.GrpcApi;
|
||||
|
||||
[System.AttributeUsage(System.AttributeTargets.Method)]
|
||||
public class GrpcNoAuthRequiredAttribute : System.Attribute
|
||||
{
|
||||
}
|
||||
""";
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
context.RegisterPostInitializationOutput(ctx => ctx.AddSource("GrpcApiPermAttribute.cs",
|
||||
SourceText.From(Attribute, Encoding.UTF8)));
|
||||
SourceText.From(GRPC_API_PERM_ATTRIBUTE, Encoding.UTF8)));
|
||||
|
||||
context.RegisterPostInitializationOutput(ctx => ctx.AddSource("GrpcNoAuthRequiredAttribute.cs",
|
||||
SourceText.From(GRPC_NO_AUTH_REQUIRED_ATTRIBUTE, Encoding.UTF8)));
|
||||
|
||||
var enumsToGenerate = context.SyntaxProvider
|
||||
var perms = context.SyntaxProvider
|
||||
.ForAttributeWithMetadataName(
|
||||
"NadekoBot.GrpcApi.GrpcApiPermAttribute",
|
||||
predicate: static (s, _) => s is MethodDeclarationSyntax,
|
||||
@@ -52,11 +66,24 @@ namespace NadekoBot.Generators
|
||||
.Select(static (x, _) => x!.Value)
|
||||
.Collect();
|
||||
|
||||
context.RegisterSourceOutput(enumsToGenerate,
|
||||
|
||||
var all = context.SyntaxProvider
|
||||
.ForAttributeWithMetadataName(
|
||||
"NadekoBot.GrpcApi.GrpcNoAuthRequiredAttribute",
|
||||
predicate: static (s, _) => s is MethodDeclarationSyntax,
|
||||
transform: static (ctx, _) => GetNoAuthMethodName(ctx.SemanticModel, ctx.TargetNode))
|
||||
.Collect()
|
||||
.Combine(perms)
|
||||
.Select((x, _) => new MethodPermData(x.Right, x.Left));
|
||||
|
||||
context.RegisterSourceOutput(all,
|
||||
static (spc, source) => Execute(source, spc));
|
||||
}
|
||||
|
||||
private static MethodPermData? GetMethodSemanticTargets(SemanticModel model, SyntaxNode node)
|
||||
private static string GetNoAuthMethodName(SemanticModel model, SyntaxNode node)
|
||||
=> ((MethodDeclarationSyntax)node).Identifier.Text;
|
||||
|
||||
private static (string Name, string Value)? GetMethodSemanticTargets(SemanticModel model, SyntaxNode node)
|
||||
{
|
||||
var method = (MethodDeclarationSyntax)node;
|
||||
|
||||
@@ -64,20 +91,14 @@ namespace NadekoBot.Generators
|
||||
var attr = method.AttributeLists
|
||||
.SelectMany(x => x.Attributes)
|
||||
.FirstOrDefault();
|
||||
// .FirstOrDefault(x => x.Name.ToString() == "GrpcApiPermAttribute");
|
||||
|
||||
|
||||
if (attr is null)
|
||||
return null;
|
||||
|
||||
// if (model.GetSymbolInfo(attr).Symbol is not IMethodSymbol attrSymbol)
|
||||
// return null;
|
||||
|
||||
return new MethodPermData(name, attr.ArgumentList?.Arguments[0].ToString() ?? "__missing_perm__");
|
||||
// return new MethodPermData(name, attrSymbol.Parameters[0].ContainingType.ToDisplayString() + "." + attrSymbol.Parameters[0].Name);
|
||||
return (name, attr.ArgumentList?.Arguments[0].ToString() ?? "__missing_perm__");
|
||||
}
|
||||
|
||||
private static void Execute(ImmutableArray<MethodPermData> fields, SourceProductionContext ctx)
|
||||
private static void Execute(MethodPermData data, SourceProductionContext ctx)
|
||||
{
|
||||
using (var stringWriter = new StringWriter())
|
||||
using (var sw = new IndentedTextWriter(stringWriter))
|
||||
@@ -87,16 +108,17 @@ namespace NadekoBot.Generators
|
||||
sw.WriteLine("namespace NadekoBot.GrpcApi;");
|
||||
sw.WriteLine();
|
||||
|
||||
sw.WriteLine("public partial class PermsInterceptor");
|
||||
sw.WriteLine("public partial class GrpcApiPermsInterceptor");
|
||||
sw.WriteLine("{");
|
||||
|
||||
sw.Indent++;
|
||||
|
||||
sw.WriteLine("public static FrozenDictionary<string, GuildPerm> perms = new Dictionary<string, GuildPerm>()");
|
||||
sw.WriteLine(
|
||||
"private static FrozenDictionary<string, GuildPerm> _perms = new Dictionary<string, GuildPerm>()");
|
||||
sw.WriteLine("{");
|
||||
|
||||
sw.Indent++;
|
||||
foreach (var field in fields)
|
||||
foreach (var field in data.MethodPerms)
|
||||
{
|
||||
sw.WriteLine("{{ \"{0}\", {1} }},", field.Name, field.Value);
|
||||
}
|
||||
@@ -104,6 +126,21 @@ namespace NadekoBot.Generators
|
||||
sw.Indent--;
|
||||
sw.WriteLine("}.ToFrozenDictionary();");
|
||||
|
||||
sw.WriteLine();
|
||||
sw.WriteLine("private static FrozenSet<string> _noAuthRequired = new HashSet<string>()");
|
||||
sw.WriteLine("{");
|
||||
|
||||
sw.Indent++;
|
||||
foreach (var noauth in data.NoAuthRequired)
|
||||
{
|
||||
sw.WriteLine("{{ \"{0}\" }},", noauth);
|
||||
}
|
||||
|
||||
sw.WriteLine("");
|
||||
|
||||
sw.Indent--;
|
||||
sw.WriteLine("}.ToFrozenSet();");
|
||||
|
||||
sw.Indent--;
|
||||
sw.WriteLine("}");
|
||||
|
||||
|
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;
|
||||
}
|
@@ -2,7 +2,7 @@ syntax = "proto3";
|
||||
|
||||
option csharp_namespace = "NadekoBot.GrpcApi";
|
||||
|
||||
import "google/protobuf/empty.proto";
|
||||
import "google/protobuf/empty.proto";
|
||||
|
||||
package exprs;
|
||||
|
||||
@@ -10,6 +10,10 @@ service GrpcExprs {
|
||||
rpc GetExprs(GetExprsRequest) returns (GetExprsReply);
|
||||
rpc AddExpr(AddExprRequest) returns (AddExprReply);
|
||||
rpc DeleteExpr(DeleteExprRequest) returns (google.protobuf.Empty);
|
||||
|
||||
rpc GetQuotes(GetQuotesRequest) returns (GetQuotesReply);
|
||||
rpc AddQuote(AddQuoteRequest) returns (AddQuoteReply);
|
||||
rpc DeleteQuote(DeleteQuoteRequest) returns (google.protobuf.Empty);
|
||||
}
|
||||
|
||||
message DeleteExprRequest {
|
||||
@@ -32,7 +36,7 @@ message ExprDto {
|
||||
string id = 1;
|
||||
string trigger = 2;
|
||||
string response = 3;
|
||||
|
||||
|
||||
bool ca = 4;
|
||||
bool ad = 5;
|
||||
bool dm = 6;
|
||||
@@ -47,4 +51,39 @@ message AddExprRequest {
|
||||
message AddExprReply {
|
||||
string id = 1;
|
||||
bool success = 2;
|
||||
}
|
||||
|
||||
message GetQuotesRequest {
|
||||
uint64 guildId = 1;
|
||||
string query = 2;
|
||||
int32 page = 3;
|
||||
}
|
||||
|
||||
message GetQuotesReply {
|
||||
repeated QuoteDto quotes = 1;
|
||||
int32 totalCount = 2;
|
||||
}
|
||||
|
||||
message QuoteDto {
|
||||
string id = 1;
|
||||
string trigger = 2;
|
||||
string response = 3;
|
||||
|
||||
uint64 authorId = 4;
|
||||
string authorName = 5;
|
||||
}
|
||||
|
||||
message AddQuoteRequest {
|
||||
uint64 guildId = 1;
|
||||
QuoteDto quote = 2;
|
||||
}
|
||||
|
||||
message AddQuoteReply {
|
||||
string id = 1;
|
||||
bool success = 2;
|
||||
}
|
||||
|
||||
message DeleteQuoteRequest {
|
||||
string id = 1;
|
||||
uint64 guildId = 2;
|
||||
}
|
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;
|
||||
}
|
@@ -5,20 +5,13 @@ option csharp_namespace = "NadekoBot.GrpcApi";
|
||||
package greet;
|
||||
|
||||
service GrpcGreet {
|
||||
rpc GetGreetSettings (GetGreetRequest) returns (GetGreetReply);
|
||||
rpc GetGreetSettings (GetGreetRequest) returns (GrpcGreetSettings);
|
||||
rpc UpdateGreet (UpdateGreetRequest) returns (UpdateGreetReply);
|
||||
rpc TestGreet (TestGreetRequest) returns (TestGreetReply);
|
||||
}
|
||||
|
||||
message GetGreetReply {
|
||||
GrpcGreetSettings greet = 1;
|
||||
GrpcGreetSettings greetDm = 2;
|
||||
GrpcGreetSettings bye = 3;
|
||||
GrpcGreetSettings boost = 4;
|
||||
}
|
||||
|
||||
message GrpcGreetSettings {
|
||||
optional uint64 channelId = 1;
|
||||
string channelId = 1;
|
||||
string message = 2;
|
||||
bool isEnabled = 3;
|
||||
GrpcGreetType type = 4;
|
||||
@@ -26,6 +19,7 @@ message GrpcGreetSettings {
|
||||
|
||||
message GetGreetRequest {
|
||||
uint64 guildId = 1;
|
||||
GrpcGreetType type = 2;
|
||||
}
|
||||
|
||||
message UpdateGreetRequest {
|
||||
@@ -41,7 +35,7 @@ enum GrpcGreetType {
|
||||
}
|
||||
|
||||
message UpdateGreetReply {
|
||||
bool success = 1;
|
||||
bool Success = 1;
|
||||
}
|
||||
|
||||
message TestGreetRequest {
|
||||
|
@@ -3,43 +3,50 @@ syntax = "proto3";
|
||||
option csharp_namespace = "NadekoBot.GrpcApi";
|
||||
|
||||
import "google/protobuf/empty.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
package other;
|
||||
|
||||
service GrpcOther {
|
||||
|
||||
rpc GetGuilds(google.protobuf.Empty) returns (GetGuildsReply);
|
||||
rpc BotOnGuild(BotOnGuildRequest) returns (BotOnGuildReply);
|
||||
rpc GetTextChannels(GetTextChannelsRequest) returns (GetTextChannelsReply);
|
||||
rpc GetRoles(GetRolesRequest) returns (GetRolesReply);
|
||||
|
||||
rpc GetCurrencyLb(GetLbRequest) returns (CurrencyLbReply);
|
||||
rpc GetXpLb(GetLbRequest) returns (XpLbReply);
|
||||
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);
|
||||
}
|
||||
|
||||
message GetGuildsReply {
|
||||
repeated GuildReply guilds = 1;
|
||||
message CommandFeedEntry {
|
||||
string command = 1;
|
||||
}
|
||||
|
||||
message GuildReply {
|
||||
uint64 id = 1;
|
||||
string name = 2;
|
||||
string iconUrl = 3;
|
||||
message GetRolesRequest {
|
||||
uint64 guildId = 1;
|
||||
}
|
||||
|
||||
message GetShardStatusesReply {
|
||||
repeated ShardStatusReply shards = 1;
|
||||
message GetRolesReply {
|
||||
repeated RoleReply roles = 1;
|
||||
}
|
||||
|
||||
message ShardStatusReply {
|
||||
message BotOnGuildRequest {
|
||||
uint64 guildId = 1;
|
||||
}
|
||||
|
||||
message BotOnGuildReply {
|
||||
bool success = 1;
|
||||
}
|
||||
|
||||
message ShardStatsReply {
|
||||
int32 id = 1;
|
||||
string status = 2;
|
||||
|
||||
int32 guildCount = 3;
|
||||
google.protobuf.Timestamp lastUpdate = 4;
|
||||
string uptime = 4;
|
||||
int64 commands = 5;
|
||||
}
|
||||
|
||||
message GetTextChannelsRequest{
|
||||
|
@@ -6,11 +6,17 @@ package warn;
|
||||
|
||||
service GrpcWarn {
|
||||
rpc GetWarnSettings (WarnSettingsRequest) returns (WarnSettingsReply);
|
||||
|
||||
rpc SetWarnExpiry(SetWarnExpiryRequest) returns (SetWarnExpiryReply);
|
||||
rpc AddWarnp (AddWarnpRequest) returns (AddWarnpReply);
|
||||
rpc DeleteWarnp (DeleteWarnpRequest) returns (DeleteWarnpReply);
|
||||
|
||||
rpc GetLatestWarnings(GetLatestWarningsRequest) returns (GetLatestWarningsReply);
|
||||
rpc GetUserWarnings(GetUserWarningsRequest) returns (GetUserWarningsReply);
|
||||
rpc ClearWarning(ClearWarningRequest) returns (ClearWarningReply);
|
||||
rpc SetWarnExpiry(SetWarnExpiryRequest) returns (SetWarnExpiryReply);
|
||||
|
||||
rpc ForgiveWarning(ForgiveWarningRequest) returns (ForgiveWarningReply);
|
||||
rpc DeleteWarning(ForgiveWarningRequest) returns (ForgiveWarningReply);
|
||||
|
||||
}
|
||||
message WarnSettingsRequest {
|
||||
uint64 guildId = 1;
|
||||
@@ -19,12 +25,14 @@ message WarnSettingsRequest {
|
||||
message WarnPunishment {
|
||||
int32 threshold = 1;
|
||||
string action = 2;
|
||||
int64 duration = 3;
|
||||
int32 duration = 3;
|
||||
string role = 4;
|
||||
}
|
||||
|
||||
message WarnSettingsReply {
|
||||
repeated WarnPunishment punishments = 1;
|
||||
int32 expiryDays = 2;
|
||||
bool deleteOnExpire = 3;
|
||||
}
|
||||
|
||||
message AddWarnpRequest {
|
||||
@@ -38,7 +46,7 @@ message AddWarnpReply {
|
||||
|
||||
message DeleteWarnpRequest {
|
||||
uint64 guildId = 1;
|
||||
int32 warnpIndex = 2;
|
||||
int32 threshold = 2;
|
||||
}
|
||||
|
||||
message DeleteWarnpReply {
|
||||
@@ -47,37 +55,53 @@ message DeleteWarnpReply {
|
||||
|
||||
message GetUserWarningsRequest {
|
||||
uint64 guildId = 1;
|
||||
uint64 user_id = 2;
|
||||
string user = 2;
|
||||
int32 page = 3;
|
||||
}
|
||||
|
||||
message GetUserWarningsReply {
|
||||
repeated Warning warnings = 1;
|
||||
int32 totalCount = 2;
|
||||
}
|
||||
|
||||
message Warning {
|
||||
int32 id = 1;
|
||||
string id = 1;
|
||||
string reason = 2;
|
||||
int64 timestamp = 3;
|
||||
int64 expiry_timestamp = 4;
|
||||
bool cleared = 5;
|
||||
string clearedBy = 6;
|
||||
int64 weight = 4;
|
||||
bool forgiven = 5;
|
||||
string forgivenBy = 6;
|
||||
string user = 7;
|
||||
uint64 userId = 8;
|
||||
string moderator = 9;
|
||||
}
|
||||
|
||||
message ClearWarningRequest {
|
||||
message ForgiveWarningRequest {
|
||||
uint64 guildId = 1;
|
||||
uint64 userId = 2;
|
||||
optional int32 warnId = 3;
|
||||
string warnId = 2;
|
||||
string modName = 3;
|
||||
}
|
||||
|
||||
message ClearWarningReply {
|
||||
message ForgiveWarningReply {
|
||||
bool success = 1;
|
||||
}
|
||||
|
||||
message SetWarnExpiryRequest {
|
||||
uint64 guildId = 1;
|
||||
int32 expiryDays = 2;
|
||||
bool deleteOnExpire = 3;
|
||||
}
|
||||
|
||||
message SetWarnExpiryReply {
|
||||
bool success = 1;
|
||||
}
|
||||
|
||||
message GetLatestWarningsRequest {
|
||||
uint64 guildId = 1;
|
||||
int32 page = 2;
|
||||
}
|
||||
|
||||
message GetLatestWarningsReply {
|
||||
repeated Warning warnings = 1;
|
||||
int32 totalCount = 2;
|
||||
}
|
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,
|
||||
Username = username,
|
||||
Discriminator = discrim,
|
||||
AvatarId = avatarId,
|
||||
TotalXp = 0,
|
||||
CurrencyAmount = 0
|
||||
@@ -33,7 +32,6 @@ public static class DiscordUserExtensions
|
||||
old => new()
|
||||
{
|
||||
Username = username,
|
||||
Discriminator = discrim,
|
||||
AvatarId = avatarId
|
||||
},
|
||||
() => new()
|
||||
@@ -49,8 +47,7 @@ public static class DiscordUserExtensions
|
||||
() => new()
|
||||
{
|
||||
UserId = userId,
|
||||
Username = "Unknown",
|
||||
Discriminator = "????",
|
||||
Username = "??Unknown",
|
||||
AvatarId = string.Empty,
|
||||
TotalXp = 0,
|
||||
CurrencyAmount = 0
|
||||
@@ -87,14 +84,7 @@ public static class DiscordUserExtensions
|
||||
> users.AsQueryable().Where(y => y.UserId == id).Select(y => y.TotalXp).FirstOrDefault())
|
||||
.Count()
|
||||
+ 1;
|
||||
|
||||
public static async Task<IReadOnlyCollection<DiscordUser>> GetUsersXpLeaderboardFor(this DbSet<DiscordUser> users, int page, int perPage)
|
||||
=> await users.ToLinqToDBTable()
|
||||
.OrderByDescending(x => x.TotalXp)
|
||||
.Skip(page * perPage)
|
||||
.Take(perPage)
|
||||
.ToArrayAsyncLinqToDB();
|
||||
|
||||
|
||||
public static Task<List<DiscordUser>> GetTopRichest(
|
||||
this DbSet<DiscordUser> users,
|
||||
ulong botId,
|
||||
|
@@ -57,8 +57,7 @@ public static class GuildConfigExtensions
|
||||
List<ulong> availableGuilds)
|
||||
{
|
||||
var result = await configs
|
||||
.AsQueryable()
|
||||
.Include(x => x.CommandCooldowns)
|
||||
.IncludeEverything()
|
||||
.Where(x => availableGuilds.Contains(x.GuildId))
|
||||
.AsNoTracking()
|
||||
.ToArrayAsync();
|
||||
@@ -96,7 +95,6 @@ public static class GuildConfigExtensions
|
||||
GuildId = guildId,
|
||||
Permissions = Permissionv2.GetDefaultPermlist,
|
||||
WarningsInitialized = true,
|
||||
WarnPunishments = DefaultWarnPunishments
|
||||
});
|
||||
ctx.SaveChanges();
|
||||
}
|
||||
@@ -104,7 +102,6 @@ public static class GuildConfigExtensions
|
||||
if (!config.WarningsInitialized)
|
||||
{
|
||||
config.WarningsInitialized = true;
|
||||
config.WarnPunishments = DefaultWarnPunishments;
|
||||
}
|
||||
|
||||
return config;
|
||||
|
@@ -26,17 +26,6 @@ public static class UserXpExtensions
|
||||
return usr;
|
||||
}
|
||||
|
||||
public static async Task<IReadOnlyCollection<UserXpStats>> GetUsersFor(
|
||||
this DbSet<UserXpStats> xps,
|
||||
ulong guildId,
|
||||
int page)
|
||||
=> await xps.ToLinqToDBTable()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.OrderByDescending(x => x.Xp + x.AwardedXp)
|
||||
.Skip(page * 9)
|
||||
.Take(9)
|
||||
.ToArrayAsyncLinqToDB();
|
||||
|
||||
public static async Task<List<UserXpStats>> GetTopUserXps(this DbSet<UserXpStats> xps, ulong guildId, int count)
|
||||
=> await xps.ToLinqToDBTable()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
@@ -55,9 +44,6 @@ public static class UserXpExtensions
|
||||
.CountAsyncLinqToDB()
|
||||
+ 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)
|
||||
=> xps.Delete(x => x.GuildId == guildId);
|
||||
|
||||
|
@@ -7,7 +7,7 @@ public class DiscordUser : DbEntity
|
||||
{
|
||||
public ulong UserId { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Discriminator { get; set; }
|
||||
// public string Discriminator { get; set; }
|
||||
public string AvatarId { get; set; }
|
||||
|
||||
public int? ClubId { get; set; }
|
||||
@@ -27,9 +27,6 @@ public class DiscordUser : DbEntity
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Discriminator) || Discriminator == "0000")
|
||||
return Username;
|
||||
|
||||
return Username + "#" + Discriminator;
|
||||
return Username;
|
||||
}
|
||||
}
|
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; }
|
||||
}
|
@@ -77,7 +77,6 @@ public class GuildConfig : DbEntity
|
||||
public HashSet<UnroleTimer> UnroleTimer { get; set; } = new();
|
||||
public HashSet<VcRoleInfo> VcRoleInfos { get; set; }
|
||||
public HashSet<CommandAlias> CommandAliases { get; set; } = new();
|
||||
public List<WarningPunishment> WarnPunishments { get; set; } = new();
|
||||
public bool WarningsInitialized { get; set; }
|
||||
public HashSet<SlowmodeIgnoredUser> SlowmodeIgnoredUsers { get; set; }
|
||||
public HashSet<SlowmodeIgnoredRole> SlowmodeIgnoredRoles { 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; }
|
||||
}
|
@@ -3,6 +3,7 @@ namespace NadekoBot.Db.Models;
|
||||
|
||||
public class WarningPunishment : DbEntity
|
||||
{
|
||||
public ulong GuildId { get; set; }
|
||||
public int Count { get; set; }
|
||||
public PunishmentAction Punishment { get; set; }
|
||||
public int Time { get; set; }
|
||||
|
@@ -1,8 +1,12 @@
|
||||
#nullable disable
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace NadekoBot.Db.Models;
|
||||
|
||||
public class PatronUser
|
||||
{
|
||||
// [Key]
|
||||
// public int Id { get; set; }
|
||||
public string UniquePlatformUserId { get; set; }
|
||||
public ulong UserId { get; set; }
|
||||
public int AmountCents { get; set; }
|
||||
|
@@ -61,8 +61,8 @@ public abstract class NadekoContext : DbContext
|
||||
public DbSet<TodoModel> Todos { get; set; }
|
||||
public DbSet<ArchivedTodoListModel> TodosArchive { get; set; }
|
||||
public DbSet<HoneypotChannel> HoneyPotChannels { get; set; }
|
||||
|
||||
|
||||
// todo add guild colors
|
||||
// public DbSet<GuildColors> GuildColors { get; set; }
|
||||
|
||||
|
||||
@@ -74,6 +74,33 @@ public abstract class NadekoContext : DbContext
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
#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
|
||||
|
||||
var quoteEntity = modelBuilder.Entity<Quote>();
|
||||
@@ -195,11 +222,6 @@ public abstract class NadekoContext : DbContext
|
||||
.WithOne()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<GuildConfig>()
|
||||
.HasMany(x => x.WarnPunishments)
|
||||
.WithOne()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<GuildConfig>()
|
||||
.HasMany(x => x.SlowmodeIgnoredRoles)
|
||||
.WithOne()
|
||||
@@ -277,6 +299,18 @@ public abstract class NadekoContext : DbContext
|
||||
|
||||
#endregion
|
||||
|
||||
#region WarningPunishments
|
||||
|
||||
var warnpunishmentEntity = modelBuilder.Entity<WarningPunishment>(b =>
|
||||
{
|
||||
b.HasAlternateKey(x => new
|
||||
{
|
||||
x.GuildId,
|
||||
x.Count
|
||||
});
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
#region Self Assignable Roles
|
||||
|
||||
@@ -339,6 +373,7 @@ public abstract class NadekoContext : DbContext
|
||||
du.HasIndex(x => x.TotalXp);
|
||||
du.HasIndex(x => x.CurrencyAmount);
|
||||
du.HasIndex(x => x.UserId);
|
||||
du.HasIndex(x => x.Username);
|
||||
});
|
||||
|
||||
#endregion
|
||||
@@ -695,7 +730,7 @@ public abstract class NadekoContext : DbContext
|
||||
gs
|
||||
.Property(x => x.IsEnabled)
|
||||
.HasDefaultValue(false);
|
||||
|
||||
|
||||
gs
|
||||
.Property(x => x.AutoDeleteTimer)
|
||||
.HasDefaultValue(0);
|
||||
|
@@ -5,6 +5,11 @@ namespace NadekoBot.Migrations;
|
||||
|
||||
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)
|
||||
{
|
||||
if (migrationBuilder.IsSqlite())
|
||||
@@ -38,6 +43,7 @@ left join guildconfigs on reactionrolemessage.guildconfigid = guildconfigs.id;")
|
||||
DELETE FROM "DelMsgOnCmdChannel" WHERE "GuildConfigId" is NULL;
|
||||
DELETE FROM "WarningPunishment" WHERE "GuildConfigId" NOT IN (SELECT "Id" from "GuildConfigs");
|
||||
DELETE FROM "StreamRoleBlacklistedUser" WHERE "StreamRoleSettingsId" is NULL;
|
||||
DELETE FROM "Permissions" WHERE "GuildConfigId" NOT IN (SELECT "Id" from "GuildConfigs");
|
||||
""");
|
||||
}
|
||||
|
||||
@@ -65,4 +71,20 @@ left join guildconfigs on reactionrolemessage.guildconfigid = guildconfigs.id;")
|
||||
WHERE SendBoostMessage = TRUE;
|
||||
""");
|
||||
}
|
||||
|
||||
public static void AddGuildIdsToWarningPunishment(MigrationBuilder builder)
|
||||
{
|
||||
builder.Sql("""
|
||||
DELETE FROM WarningPunishment WHERE GuildConfigId IS NULL OR GuildConfigId NOT IN (SELECT Id FROM GuildConfigs);
|
||||
UPDATE WarningPunishment
|
||||
SET GuildId = (SELECT GuildId FROM GuildConfigs WHERE Id = GuildConfigId);
|
||||
|
||||
DELETE FROM WarningPunishment as wp
|
||||
WHERE (wp.Count, wp.GuildConfigId) in (
|
||||
SELECT wp2.Count, wp2.GuildConfigId FROM WarningPunishment as wp2
|
||||
GROUP BY wp2.Count, wp2.GuildConfigId
|
||||
HAVING COUNT(id) > 1
|
||||
);
|
||||
""");
|
||||
}
|
||||
}
|
3778
src/NadekoBot/Migrations/PostgreSql/20241018004623_warn-split.Designer.cs
generated
Normal file
3778
src/NadekoBot/Migrations/PostgreSql/20241018004623_warn-split.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,71 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.PostgreSql
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class warnsplit : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "guildid",
|
||||
table: "warningpunishment",
|
||||
type: "numeric(20,0)",
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
MigrationQueries.AddGuildIdsToWarningPunishment(migrationBuilder);
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "fk_warningpunishment_guildconfigs_guildconfigid",
|
||||
table: "warningpunishment");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "ix_warningpunishment_guildconfigid",
|
||||
table: "warningpunishment");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "guildconfigid",
|
||||
table: "warningpunishment");
|
||||
|
||||
migrationBuilder.AddUniqueConstraint(
|
||||
name: "ak_warningpunishment_guildid_count",
|
||||
table: "warningpunishment",
|
||||
columns: new[] { "guildid", "count" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropUniqueConstraint(
|
||||
name: "ak_warningpunishment_guildid_count",
|
||||
table: "warningpunishment");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "guildid",
|
||||
table: "warningpunishment");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "guildconfigid",
|
||||
table: "warningpunishment",
|
||||
type: "integer",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_warningpunishment_guildconfigid",
|
||||
table: "warningpunishment",
|
||||
column: "guildconfigid");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "fk_warningpunishment_guildconfigs_guildconfigid",
|
||||
table: "warningpunishment",
|
||||
column: "guildconfigid",
|
||||
principalTable: "guildconfigs",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
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");
|
||||
}
|
||||
}
|
||||
}
|
@@ -751,10 +751,6 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
.HasColumnType("timestamp without time zone")
|
||||
.HasColumnName("dateadded");
|
||||
|
||||
b.Property<string>("Discriminator")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("discriminator");
|
||||
|
||||
b.Property<bool>("IsClubAdmin")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("boolean")
|
||||
@@ -799,6 +795,9 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
b.HasIndex("UserId")
|
||||
.HasDatabaseName("ix_discorduser_userid");
|
||||
|
||||
b.HasIndex("Username")
|
||||
.HasDatabaseName("ix_discorduser_username");
|
||||
|
||||
b.ToTable("discorduser", (string)null);
|
||||
});
|
||||
|
||||
@@ -995,6 +994,37 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
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 =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -1627,6 +1657,49 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
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 =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -2938,9 +3011,9 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
.HasColumnType("timestamp without time zone")
|
||||
.HasColumnName("dateadded");
|
||||
|
||||
b.Property<int?>("GuildConfigId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("guildconfigid");
|
||||
b.Property<decimal>("GuildId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("guildid");
|
||||
|
||||
b.Property<int>("Punishment")
|
||||
.HasColumnType("integer")
|
||||
@@ -2957,8 +3030,8 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_warningpunishment");
|
||||
|
||||
b.HasIndex("GuildConfigId")
|
||||
.HasDatabaseName("ix_warningpunishment_guildconfigid");
|
||||
b.HasAlternateKey("GuildId", "Count")
|
||||
.HasName("ak_warningpunishment_guildid_count");
|
||||
|
||||
b.ToTable("warningpunishment", (string)null);
|
||||
});
|
||||
@@ -3154,6 +3227,57 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
b.ToTable("greetsettings", (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 =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Db.Models.GuildConfig", null)
|
||||
@@ -3616,15 +3740,6 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.WarningPunishment", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Db.Models.GuildConfig", null)
|
||||
.WithMany("WarnPunishments")
|
||||
.HasForeignKey("GuildConfigId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.HasConstraintName("fk_warningpunishment_guildconfigs_guildconfigid");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.XpCurrencyReward", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Db.Models.XpSettings", "XpSettings")
|
||||
@@ -3740,8 +3855,6 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
|
||||
b.Navigation("VcRoleInfos");
|
||||
|
||||
b.Navigation("WarnPunishments");
|
||||
|
||||
b.Navigation("XpSettings");
|
||||
});
|
||||
|
||||
|
2919
src/NadekoBot/Migrations/Sqlite/20241018004612_warn-split.Designer.cs
generated
Normal file
2919
src/NadekoBot/Migrations/Sqlite/20241018004612_warn-split.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
72
src/NadekoBot/Migrations/Sqlite/20241018004612_warn-split.cs
Normal file
72
src/NadekoBot/Migrations/Sqlite/20241018004612_warn-split.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class warnsplit : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<ulong>(
|
||||
name: "GuildId",
|
||||
table: "WarningPunishment",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0ul);
|
||||
|
||||
MigrationQueries.AddGuildIdsToWarningPunishment(migrationBuilder);
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_WarningPunishment_GuildConfigs_GuildConfigId",
|
||||
table: "WarningPunishment");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_WarningPunishment_GuildConfigId",
|
||||
table: "WarningPunishment");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "GuildConfigId",
|
||||
table: "WarningPunishment");
|
||||
|
||||
migrationBuilder.AddUniqueConstraint(
|
||||
name: "AK_WarningPunishment_GuildId_Count",
|
||||
table: "WarningPunishment",
|
||||
columns: new[] { "GuildId", "Count" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropUniqueConstraint(
|
||||
name: "AK_WarningPunishment_GuildId_Count",
|
||||
table: "WarningPunishment");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "GuildId",
|
||||
table: "WarningPunishment");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "GuildConfigId",
|
||||
table: "WarningPunishment",
|
||||
type: "INTEGER",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WarningPunishment_GuildConfigId",
|
||||
table: "WarningPunishment",
|
||||
column: "GuildConfigId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_WarningPunishment_GuildConfigs_GuildConfigId",
|
||||
table: "WarningPunishment",
|
||||
column: "GuildConfigId",
|
||||
principalTable: "GuildConfigs",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
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");
|
||||
}
|
||||
}
|
||||
}
|
@@ -560,9 +560,6 @@ namespace NadekoBot.Migrations
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Discriminator")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsClubAdmin")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
@@ -596,6 +593,8 @@ namespace NadekoBot.Migrations
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("Username");
|
||||
|
||||
b.ToTable("DiscordUser");
|
||||
});
|
||||
|
||||
@@ -741,6 +740,29 @@ namespace NadekoBot.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -1213,6 +1235,38 @@ namespace NadekoBot.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -2183,7 +2237,7 @@ namespace NadekoBot.Migrations
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("GuildConfigId")
|
||||
b.Property<ulong>("GuildId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Punishment")
|
||||
@@ -2197,7 +2251,7 @@ namespace NadekoBot.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GuildConfigId");
|
||||
b.HasAlternateKey("GuildId", "Count");
|
||||
|
||||
b.ToTable("WarningPunishment");
|
||||
});
|
||||
@@ -2345,6 +2399,44 @@ namespace NadekoBot.Migrations
|
||||
b.ToTable("GreetSettings");
|
||||
});
|
||||
|
||||
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 =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Db.Models.GuildConfig", null)
|
||||
@@ -2760,14 +2852,6 @@ namespace NadekoBot.Migrations
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.WarningPunishment", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Db.Models.GuildConfig", null)
|
||||
.WithMany("WarnPunishments")
|
||||
.HasForeignKey("GuildConfigId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.XpCurrencyReward", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Db.Models.XpSettings", "XpSettings")
|
||||
@@ -2880,8 +2964,6 @@ namespace NadekoBot.Migrations
|
||||
|
||||
b.Navigation("VcRoleInfos");
|
||||
|
||||
b.Navigation("WarnPunishments");
|
||||
|
||||
b.Navigation("XpSettings");
|
||||
});
|
||||
|
||||
|
@@ -24,6 +24,13 @@ public partial class Administration
|
||||
await Response().Error(strs.hierarchy).SendAsync();
|
||||
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);
|
||||
if (roles.Count == 0)
|
||||
|
@@ -1,4 +1,3 @@
|
||||
#nullable disable
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
@@ -11,53 +10,63 @@ public class AutoPublishService : IExecNoCommand, IReadyExecutor, INService
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IBotCredsProvider _creds;
|
||||
private ConcurrentDictionary<ulong, ulong> _enabled;
|
||||
private ConcurrentDictionary<ulong, ulong> _enabled = new();
|
||||
|
||||
public AutoPublishService(DbService db, DiscordSocketClient client, IBotCredsProvider creds)
|
||||
{
|
||||
_db = db;
|
||||
_client = client;
|
||||
_creds = creds;
|
||||
}
|
||||
|
||||
public async Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg)
|
||||
}
|
||||
|
||||
public async Task ExecOnNoCommandAsync(IGuild? guild, IUserMessage msg)
|
||||
{
|
||||
if (guild is null)
|
||||
return;
|
||||
|
||||
|
||||
if (msg.Channel.GetChannelType() != ChannelType.News)
|
||||
return;
|
||||
|
||||
if (!_enabled.TryGetValue(guild.Id, out var cid) || cid != msg.Channel.Id)
|
||||
return;
|
||||
|
||||
|
||||
await msg.CrosspostAsync(new RequestOptions()
|
||||
{
|
||||
RetryMode = RetryMode.AlwaysFail
|
||||
});
|
||||
}
|
||||
|
||||
// todo GUILDS
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
var creds = _creds.GetCreds();
|
||||
|
||||
|
||||
await using var ctx = _db.GetDbContext();
|
||||
var items = await ctx.GetTable<AutoPublishChannel>()
|
||||
.Where(x => Linq2DbExpressions.GuildOnShard(x.GuildId, creds.TotalShards, _client.ShardId))
|
||||
.ToListAsyncLinqToDB();
|
||||
.Where(x => Linq2DbExpressions.GuildOnShard(x.GuildId, creds.TotalShards, _client.ShardId))
|
||||
.ToListAsyncLinqToDB();
|
||||
|
||||
_enabled = items
|
||||
.ToDictionary(x => x.GuildId, x => x.ChannelId)
|
||||
.ToConcurrent();
|
||||
.ToDictionary(x => x.GuildId, x => x.ChannelId)
|
||||
.ToConcurrent();
|
||||
|
||||
_client.LeftGuild += ClientOnLeftGuild;
|
||||
}
|
||||
|
||||
|
||||
private async Task ClientOnLeftGuild(SocketGuild guild)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
_enabled.TryRemove(guild.Id, out _);
|
||||
|
||||
await ctx.GetTable<AutoPublishChannel>()
|
||||
.Where(x => x.GuildId == guild.Id)
|
||||
.DeleteAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> ToggleAutoPublish(ulong guildId, ulong channelId)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
var deleted = await ctx.GetTable<AutoPublishChannel>()
|
||||
.DeleteAsync(x => x.GuildId == guildId && x.ChannelId == channelId);
|
||||
.DeleteAsync(x => x.GuildId == guildId && x.ChannelId == channelId);
|
||||
|
||||
if (deleted != 0)
|
||||
{
|
||||
@@ -66,22 +75,22 @@ public class AutoPublishService : IExecNoCommand, IReadyExecutor, INService
|
||||
}
|
||||
|
||||
await ctx.GetTable<AutoPublishChannel>()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
ChannelId = channelId,
|
||||
DateAdded = DateTime.UtcNow,
|
||||
},
|
||||
old => new()
|
||||
{
|
||||
ChannelId = channelId,
|
||||
DateAdded = DateTime.UtcNow,
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
GuildId = guildId
|
||||
});
|
||||
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
ChannelId = channelId,
|
||||
DateAdded = DateTime.UtcNow,
|
||||
},
|
||||
old => new()
|
||||
{
|
||||
ChannelId = channelId,
|
||||
DateAdded = DateTime.UtcNow,
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
GuildId = guildId
|
||||
});
|
||||
|
||||
_enabled[guildId] = channelId;
|
||||
|
||||
return true;
|
||||
|
@@ -206,6 +206,18 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, INService
|
||||
.Where(x => !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId))
|
||||
.DeleteAsync();
|
||||
|
||||
// delete autopublish channels
|
||||
await ctx.GetTable<AutoPublishChannel>()
|
||||
.Where(x => !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId))
|
||||
.DeleteAsync();
|
||||
|
||||
// delete greet settings
|
||||
await ctx.GetTable<GreetSettings>()
|
||||
.Where(x => !tempTable.Select(x => x.GuildId)
|
||||
.Contains(x.GuildId))
|
||||
.DeleteAsync();
|
||||
|
||||
return new()
|
||||
{
|
||||
|
@@ -138,8 +138,7 @@ public partial class Administration
|
||||
[OwnerOnly]
|
||||
public Task DeleteXp()
|
||||
=> ConfirmActionInternalAsync("Delete Xp", () => _xcs.DeleteXp());
|
||||
|
||||
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public Task DeleteWaifus()
|
||||
|
@@ -200,9 +200,7 @@ public partial class Administration
|
||||
|
||||
if (!isEnabled)
|
||||
{
|
||||
var cmdName = GetCmdName(type);
|
||||
|
||||
await Response().Pending(strs.boostmsg_enable($"`{prefix}{cmdName}`")).SendAsync();
|
||||
await SendGreetEnableHint(type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,19 +224,24 @@ public partial class Administration
|
||||
await _service.Test(ctx.Guild.Id, type, (ITextChannel)ctx.Channel, user);
|
||||
var conf = await _service.GetGreetSettingsAsync(ctx.Guild.Id, type);
|
||||
|
||||
if (conf?.IsEnabled is not true)
|
||||
await SendGreetEnableHint(type);
|
||||
}
|
||||
|
||||
private async Task SendGreetEnableHint(GreetType type)
|
||||
{
|
||||
var cmd = $"`{prefix}{GetCmdName(type)}`";
|
||||
|
||||
var str = type switch
|
||||
{
|
||||
GreetType.Greet => strs.boostmsg_enable(cmd),
|
||||
GreetType.Bye => strs.greetmsg_enable(cmd),
|
||||
GreetType.Boost => strs.byemsg_enable(cmd),
|
||||
GreetType.Greet => strs.greetmsg_enable(cmd),
|
||||
GreetType.Bye => strs.byemsg_enable(cmd),
|
||||
GreetType.Boost => strs.boostmsg_enable(cmd),
|
||||
GreetType.GreetDm => strs.greetdmmsg_enable(cmd),
|
||||
_ => strs.error
|
||||
};
|
||||
|
||||
if (conf?.IsEnabled is not true)
|
||||
await Response().Pending(str).SendAsync();
|
||||
await Response().Pending(str).SendAsync();
|
||||
}
|
||||
}
|
||||
}
|
@@ -339,7 +339,7 @@ public class GreetService : INService, IReadyExecutor
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
@@ -453,7 +453,6 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
|
||||
{
|
||||
x.UserId,
|
||||
x.Username,
|
||||
x.Discriminator
|
||||
})
|
||||
.Where(x => users.Select(y => y.Id).Contains(x.UserId))
|
||||
.ToArrayAsyncEF();
|
||||
@@ -465,12 +464,11 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
|
||||
UserId = x.Id,
|
||||
AvatarId = x.AvatarId,
|
||||
Username = x.Username,
|
||||
Discriminator = x.Discriminator
|
||||
});
|
||||
|
||||
var added = (await ctx.BulkCopyAsync(usersToAdd)).RowsCopied;
|
||||
var toUpdateUserIds = presentDbUsers
|
||||
.Where(x => x.Username == "Unknown" && x.Discriminator == "????")
|
||||
.Where(x => x.Username.StartsWith("??"))
|
||||
.Select(x => x.UserId)
|
||||
.ToArray();
|
||||
|
||||
@@ -481,7 +479,6 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
|
||||
.UpdateAsync(x => new DiscordUser()
|
||||
{
|
||||
Username = user.Username,
|
||||
Discriminator = user.Discriminator,
|
||||
|
||||
// .award tends to set AvatarId and DateAdded to NULL, so account for that.
|
||||
AvatarId = user.AvatarId,
|
||||
|
@@ -66,10 +66,10 @@ public partial class Administration
|
||||
{
|
||||
await _sender.Response(user)
|
||||
.Embed(_sender.CreateEmbed()
|
||||
.WithErrorColor()
|
||||
.WithDescription(GetText(strs.warned_on(ctx.Guild.ToString())))
|
||||
.AddField(GetText(strs.moderator), ctx.User.ToString())
|
||||
.AddField(GetText(strs.reason), reason ?? "-"))
|
||||
.WithErrorColor()
|
||||
.WithDescription(GetText(strs.warned_on(ctx.Guild.ToString())))
|
||||
.AddField(GetText(strs.moderator), ctx.User.ToString())
|
||||
.AddField(GetText(strs.reason), reason ?? "-"))
|
||||
.SendAsync();
|
||||
}
|
||||
catch
|
||||
@@ -85,8 +85,9 @@ public partial class Administration
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Exception occured while warning a user");
|
||||
var errorEmbed = _sender.CreateEmbed().WithErrorColor()
|
||||
.WithDescription(GetText(strs.cant_apply_punishment));
|
||||
var errorEmbed = _sender.CreateEmbed()
|
||||
.WithErrorColor()
|
||||
.WithDescription(GetText(strs.cant_apply_punishment));
|
||||
|
||||
if (dmFailed)
|
||||
errorEmbed.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
|
||||
@@ -117,7 +118,7 @@ public partial class Administration
|
||||
[Priority(1)]
|
||||
public async Task WarnExpire()
|
||||
{
|
||||
var expireDays = await _service.GetWarnExpire(ctx.Guild.Id);
|
||||
var (expireDays, _) = await _service.GetWarnExpire(ctx.Guild.Id);
|
||||
|
||||
if (expireDays == 0)
|
||||
await Response().Confirm(strs.warns_dont_expire).SendAsync();
|
||||
@@ -266,9 +267,9 @@ public partial class Administration
|
||||
});
|
||||
|
||||
return _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.warnings_list))
|
||||
.WithDescription(string.Join("\n", ws));
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.warnings_list))
|
||||
.WithDescription(string.Join("\n", ws));
|
||||
})
|
||||
.SendAsync();
|
||||
}
|
||||
@@ -278,7 +279,7 @@ public partial class Administration
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public Task WarnDelete(IGuildUser user, int index)
|
||||
=> WarnDelete(user.Id, index);
|
||||
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
@@ -286,15 +287,15 @@ public partial class Administration
|
||||
{
|
||||
if (--index < 0)
|
||||
return;
|
||||
|
||||
var warn = await _service.WarnDelete(userId, index);
|
||||
|
||||
var warn = await _service.WarnDelete(ctx.Guild.Id, userId, index);
|
||||
|
||||
if (warn is null)
|
||||
{
|
||||
await Response().Error(strs.warning_not_found).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
await Response().Confirm(strs.warning_deleted(Format.Bold(index.ToString()))).SendAsync();
|
||||
}
|
||||
|
||||
@@ -311,7 +312,7 @@ public partial class Administration
|
||||
{
|
||||
if (index < 0)
|
||||
return;
|
||||
|
||||
|
||||
var success = await _service.WarnClearAsync(ctx.Guild.Id, userId, index, ctx.User.ToString());
|
||||
var userStr = Format.Bold((ctx.Guild as SocketGuild)?.GetUser(userId)?.ToString() ?? userId.ToString());
|
||||
if (index == 0)
|
||||
@@ -344,7 +345,7 @@ public partial class Administration
|
||||
return;
|
||||
}
|
||||
|
||||
var success = _service.WarnPunish(ctx.Guild.Id, number, punish, time, role);
|
||||
var success = await _service.WarnPunish(ctx.Guild.Id, number, punish, time, role);
|
||||
|
||||
if (!success)
|
||||
return;
|
||||
@@ -380,7 +381,7 @@ public partial class Administration
|
||||
if (punish is PunishmentAction.TimeOut && time is null)
|
||||
return;
|
||||
|
||||
var success = _service.WarnPunish(ctx.Guild.Id, number, punish, time);
|
||||
var success = await _service.WarnPunish(ctx.Guild.Id, number, punish, time);
|
||||
|
||||
if (!success)
|
||||
return;
|
||||
@@ -407,7 +408,7 @@ public partial class Administration
|
||||
[UserPerm(GuildPerm.BanMembers)]
|
||||
public async Task WarnPunish(int number)
|
||||
{
|
||||
if (!_service.WarnPunishRemove(ctx.Guild.Id, number))
|
||||
if (!await _service.WarnPunishRemove(ctx.Guild.Id, number))
|
||||
return;
|
||||
|
||||
await Response().Confirm(strs.warn_punish_rem(Format.Bold(number.ToString()))).SendAsync();
|
||||
@@ -417,7 +418,7 @@ public partial class Administration
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task WarnPunishList()
|
||||
{
|
||||
var ps = _service.WarnPunishList(ctx.Guild.Id);
|
||||
var ps = await _service.WarnPunishList(ctx.Guild.Id);
|
||||
|
||||
string list;
|
||||
if (ps.Any())
|
||||
@@ -478,13 +479,13 @@ public partial class Administration
|
||||
var banPrune = await _service.GetBanPruneAsync(ctx.Guild.Id) ?? 7;
|
||||
await _mute.TimedBan(ctx.Guild, userId, time.Time, (ctx.User + " | " + msg).TrimTo(512), banPrune);
|
||||
var toSend = _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
||||
.AddField(GetText(strs.username), user?.ToString() ?? userId.ToString(), true)
|
||||
.AddField("ID", userId.ToString(), true)
|
||||
.AddField(GetText(strs.duration),
|
||||
time.Time.ToPrettyStringHm(),
|
||||
true);
|
||||
.WithOkColor()
|
||||
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
||||
.AddField(GetText(strs.username), user?.ToString() ?? userId.ToString(), true)
|
||||
.AddField("ID", userId.ToString(), true)
|
||||
.AddField(GetText(strs.duration),
|
||||
time.Time.ToPrettyStringHm(),
|
||||
true);
|
||||
|
||||
if (dmFailed)
|
||||
toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
|
||||
@@ -505,11 +506,12 @@ public partial class Administration
|
||||
var banPrune = await _service.GetBanPruneAsync(ctx.Guild.Id) ?? 7;
|
||||
await ctx.Guild.AddBanAsync(userId, banPrune, (ctx.User + " | " + msg).TrimTo(512));
|
||||
|
||||
await Response().Embed(_sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
||||
.AddField("ID", userId.ToString(), true))
|
||||
.SendAsync();
|
||||
await Response()
|
||||
.Embed(_sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
||||
.AddField("ID", userId.ToString(), true))
|
||||
.SendAsync();
|
||||
}
|
||||
else
|
||||
await Ban(user, msg);
|
||||
@@ -543,10 +545,10 @@ public partial class Administration
|
||||
await ctx.Guild.AddBanAsync(user, banPrune, (ctx.User + " | " + msg).TrimTo(512));
|
||||
|
||||
var toSend = _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
||||
.AddField(GetText(strs.username), user.ToString(), true)
|
||||
.AddField("ID", user.Id.ToString(), true);
|
||||
.WithOkColor()
|
||||
.WithTitle("⛔️ " + GetText(strs.banned_user))
|
||||
.AddField(GetText(strs.username), user.ToString(), true)
|
||||
.AddField("ID", user.Id.ToString(), true);
|
||||
|
||||
if (dmFailed)
|
||||
toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
|
||||
@@ -738,10 +740,10 @@ public partial class Administration
|
||||
catch { await ctx.Guild.RemoveBanAsync(user); }
|
||||
|
||||
var toSend = _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle("☣ " + GetText(strs.sb_user))
|
||||
.AddField(GetText(strs.username), user.ToString(), true)
|
||||
.AddField("ID", user.Id.ToString(), true);
|
||||
.WithOkColor()
|
||||
.WithTitle("☣ " + GetText(strs.sb_user))
|
||||
.AddField(GetText(strs.username), user.ToString(), true)
|
||||
.AddField("ID", user.Id.ToString(), true);
|
||||
|
||||
if (dmFailed)
|
||||
toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
|
||||
@@ -793,10 +795,10 @@ public partial class Administration
|
||||
await user.KickAsync((ctx.User + " | " + msg).TrimTo(512));
|
||||
|
||||
var toSend = _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.kicked_user))
|
||||
.AddField(GetText(strs.username), user.ToString(), true)
|
||||
.AddField("ID", user.Id.ToString(), true);
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.kicked_user))
|
||||
.AddField(GetText(strs.username), user.ToString(), true)
|
||||
.AddField("ID", user.Id.ToString(), true);
|
||||
|
||||
if (dmFailed)
|
||||
toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
|
||||
@@ -826,8 +828,8 @@ public partial class Administration
|
||||
var dmMessage = GetText(strs.timeoutdm(Format.Bold(ctx.Guild.Name), msg));
|
||||
await _sender.Response(user)
|
||||
.Embed(_sender.CreateEmbed()
|
||||
.WithPendingColor()
|
||||
.WithDescription(dmMessage))
|
||||
.WithPendingColor()
|
||||
.WithDescription(dmMessage))
|
||||
.SendAsync();
|
||||
}
|
||||
catch
|
||||
@@ -838,10 +840,10 @@ public partial class Administration
|
||||
await user.SetTimeOutAsync(time.Time);
|
||||
|
||||
var toSend = _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle("⏳ " + GetText(strs.timedout_user))
|
||||
.AddField(GetText(strs.username), user.ToString(), true)
|
||||
.AddField("ID", user.Id.ToString(), true);
|
||||
.WithOkColor()
|
||||
.WithTitle("⏳ " + GetText(strs.timedout_user))
|
||||
.AddField(GetText(strs.username), user.ToString(), true)
|
||||
.AddField("ID", user.Id.ToString(), true);
|
||||
|
||||
if (dmFailed)
|
||||
toSend.WithFooter("⚠️ " + GetText(strs.unable_to_dm_user));
|
||||
@@ -899,9 +901,9 @@ public partial class Administration
|
||||
missStr = "-";
|
||||
|
||||
var toSend = _sender.CreateEmbed()
|
||||
.WithDescription(GetText(strs.mass_ban_in_progress(banning.Count)))
|
||||
.AddField(GetText(strs.invalid(missing.Count)), missStr)
|
||||
.WithPendingColor();
|
||||
.WithDescription(GetText(strs.mass_ban_in_progress(banning.Count)))
|
||||
.AddField(GetText(strs.invalid(missing.Count)), missStr)
|
||||
.WithPendingColor();
|
||||
|
||||
var banningMessage = await Response().Embed(toSend).SendAsync();
|
||||
|
||||
@@ -919,11 +921,13 @@ public partial class Administration
|
||||
}
|
||||
|
||||
await banningMessage.ModifyAsync(x => x.Embed = _sender.CreateEmbed()
|
||||
.WithDescription(
|
||||
GetText(strs.mass_ban_completed(banning.Count())))
|
||||
.AddField(GetText(strs.invalid(missing.Count)), missStr)
|
||||
.WithOkColor()
|
||||
.Build());
|
||||
.WithDescription(
|
||||
GetText(strs.mass_ban_completed(
|
||||
banning.Count())))
|
||||
.AddField(GetText(strs.invalid(missing.Count)),
|
||||
missStr)
|
||||
.WithOkColor()
|
||||
.Build());
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
@@ -945,10 +949,10 @@ public partial class Administration
|
||||
//send a message but don't wait for it
|
||||
var banningMessageTask = Response()
|
||||
.Embed(_sender.CreateEmbed()
|
||||
.WithDescription(
|
||||
GetText(strs.mass_kill_in_progress(bans.Count())))
|
||||
.AddField(GetText(strs.invalid(missing)), missStr)
|
||||
.WithPendingColor())
|
||||
.WithDescription(
|
||||
GetText(strs.mass_kill_in_progress(bans.Count())))
|
||||
.AddField(GetText(strs.invalid(missing)), missStr)
|
||||
.WithPendingColor())
|
||||
.SendAsync();
|
||||
|
||||
var banPrune = await _service.GetBanPruneAsync(ctx.Guild.Id) ?? 7;
|
||||
@@ -966,11 +970,11 @@ public partial class Administration
|
||||
var banningMessage = await banningMessageTask;
|
||||
|
||||
await banningMessage.ModifyAsync(x => x.Embed = _sender.CreateEmbed()
|
||||
.WithDescription(
|
||||
GetText(strs.mass_kill_completed(bans.Count())))
|
||||
.AddField(GetText(strs.invalid(missing)), missStr)
|
||||
.WithOkColor()
|
||||
.Build());
|
||||
.WithDescription(
|
||||
GetText(strs.mass_kill_completed(bans.Count())))
|
||||
.AddField(GetText(strs.invalid(missing)), missStr)
|
||||
.WithOkColor()
|
||||
.Build());
|
||||
}
|
||||
|
||||
public class WarnExpireOptions : INadekoCommandOptions
|
||||
|
@@ -1,9 +1,7 @@
|
||||
#nullable disable
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Common.TypeReaders.Models;
|
||||
using NadekoBot.Modules.Permissions.Services;
|
||||
using NadekoBot.Db.Models;
|
||||
using Newtonsoft.Json;
|
||||
@@ -83,17 +81,24 @@ public class UserPunishService : INService, IReadyExecutor
|
||||
};
|
||||
|
||||
long previousCount;
|
||||
List<WarningPunishment> ps;
|
||||
var ps = await WarnPunishList(guildId);
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
ps = uow.GuildConfigsForId(guildId, set => set.Include(x => x.WarnPunishments)).WarnPunishments;
|
||||
|
||||
previousCount = uow.Set<Warning>()
|
||||
.ForId(guildId, userId)
|
||||
.Where(w => !w.Forgiven && w.UserId == userId)
|
||||
previousCount = uow.GetTable<Warning>()
|
||||
.Where(w => w.GuildId == guildId && w.UserId == userId && !w.Forgiven)
|
||||
.Sum(x => x.Weight);
|
||||
|
||||
uow.Set<Warning>().Add(warn);
|
||||
await uow.GetTable<Warning>()
|
||||
.InsertAsync(() => new()
|
||||
{
|
||||
UserId = userId,
|
||||
GuildId = guildId,
|
||||
Forgiven = false,
|
||||
Reason = reason,
|
||||
Moderator = modName,
|
||||
Weight = weight,
|
||||
DateAdded = DateTime.UtcNow,
|
||||
});
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
@@ -260,12 +265,12 @@ public class UserPunishService : INService, IReadyExecutor
|
||||
.ToListAsyncLinqToDB();
|
||||
|
||||
var cleared = await uow.GetTable<Warning>()
|
||||
.Where(x => toClear.Contains(x.Id))
|
||||
.UpdateAsync(_ => new()
|
||||
{
|
||||
Forgiven = true,
|
||||
ForgivenBy = "expiry"
|
||||
});
|
||||
.Where(x => toClear.Contains(x.Id))
|
||||
.UpdateAsync(_ => new()
|
||||
{
|
||||
Forgiven = true,
|
||||
ForgivenBy = "expiry"
|
||||
});
|
||||
|
||||
var toDelete = await uow.GetTable<Warning>()
|
||||
.Where(x => uow.GetTable<GuildConfig>()
|
||||
@@ -282,8 +287,8 @@ public class UserPunishService : INService, IReadyExecutor
|
||||
.ToListAsyncLinqToDB();
|
||||
|
||||
var deleted = await uow.GetTable<Warning>()
|
||||
.Where(x => toDelete.Contains(x.Id))
|
||||
.DeleteAsync();
|
||||
.Where(x => toDelete.Contains(x.Id))
|
||||
.DeleteAsync();
|
||||
|
||||
if (cleared > 0 || deleted > 0)
|
||||
{
|
||||
@@ -324,11 +329,11 @@ public class UserPunishService : INService, IReadyExecutor
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public Task<int> GetWarnExpire(ulong guildId)
|
||||
public Task<(int, bool)> GetWarnExpire(ulong guildId)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var config = uow.GuildConfigsForId(guildId, set => set);
|
||||
return Task.FromResult(config.WarnExpireHours / 24);
|
||||
return Task.FromResult((config.WarnExpireHours / 24, config.WarnExpireAction == WarnExpireAction.Delete));
|
||||
}
|
||||
|
||||
public async Task WarnExpireAsync(ulong guildId, int days, bool delete)
|
||||
@@ -377,18 +382,19 @@ public class UserPunishService : INService, IReadyExecutor
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
public bool WarnPunish(
|
||||
public async Task<bool> WarnPunish(
|
||||
ulong guildId,
|
||||
int number,
|
||||
PunishmentAction punish,
|
||||
StoopidTime time,
|
||||
TimeSpan? time,
|
||||
IRole role = null)
|
||||
{
|
||||
// these 3 don't make sense with time
|
||||
if (punish is PunishmentAction.Softban or PunishmentAction.Kick or PunishmentAction.RemoveRoles
|
||||
&& time is not null)
|
||||
return false;
|
||||
if (number <= 0 || (time is not null && time.Time > TimeSpan.FromDays(49)))
|
||||
|
||||
if (number <= 0 || (time is not null && time > TimeSpan.FromDays(59)))
|
||||
return false;
|
||||
|
||||
if (punish is PunishmentAction.AddRole && role is null)
|
||||
@@ -397,47 +403,51 @@ public class UserPunishService : INService, IReadyExecutor
|
||||
if (punish is PunishmentAction.TimeOut && time is null)
|
||||
return false;
|
||||
|
||||
using var uow = _db.GetDbContext();
|
||||
var ps = uow.GuildConfigsForId(guildId, set => set.Include(x => x.WarnPunishments)).WarnPunishments;
|
||||
var toDelete = ps.Where(x => x.Count == number);
|
||||
|
||||
uow.RemoveRange(toDelete);
|
||||
|
||||
ps.Add(new()
|
||||
{
|
||||
Count = number,
|
||||
Punishment = punish,
|
||||
Time = (int?)time?.Time.TotalMinutes ?? 0,
|
||||
RoleId = punish == PunishmentAction.AddRole ? role!.Id : default(ulong?)
|
||||
});
|
||||
uow.SaveChanges();
|
||||
var timeMinutes = (int?)time?.TotalMinutes ?? 0;
|
||||
var roleId = punish == PunishmentAction.AddRole ? role!.Id : default(ulong?);
|
||||
await using var uow = _db.GetDbContext();
|
||||
await uow.GetTable<WarningPunishment>()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
Count = number,
|
||||
Punishment = punish,
|
||||
Time = timeMinutes,
|
||||
RoleId = roleId
|
||||
},
|
||||
_ => new()
|
||||
{
|
||||
Punishment = punish,
|
||||
Time = timeMinutes,
|
||||
RoleId = roleId
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
Count = number
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool WarnPunishRemove(ulong guildId, int number)
|
||||
public async Task<bool> WarnPunishRemove(ulong guildId, int count)
|
||||
{
|
||||
if (number <= 0)
|
||||
return false;
|
||||
await using var uow = _db.GetDbContext();
|
||||
var numDeleted = await uow.GetTable<WarningPunishment>()
|
||||
.DeleteAsync(x => x.GuildId == guildId && x.Count == count);
|
||||
|
||||
using var uow = _db.GetDbContext();
|
||||
var ps = uow.GuildConfigsForId(guildId, set => set.Include(x => x.WarnPunishments)).WarnPunishments;
|
||||
var p = ps.FirstOrDefault(x => x.Count == number);
|
||||
|
||||
if (p is not null)
|
||||
{
|
||||
uow.Remove(p);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
return true;
|
||||
return numDeleted > 0;
|
||||
}
|
||||
|
||||
public WarningPunishment[] WarnPunishList(ulong guildId)
|
||||
|
||||
public async Task<WarningPunishment[]> WarnPunishList(ulong guildId)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow.GuildConfigsForId(guildId, gc => gc.Include(x => x.WarnPunishments))
|
||||
.WarnPunishments.OrderBy(x => x.Count)
|
||||
.ToArray();
|
||||
await using var uow = _db.GetDbContext();
|
||||
|
||||
var wps = uow.GetTable<WarningPunishment>()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.OrderBy(x => x.Count)
|
||||
.ToArray();
|
||||
return wps;
|
||||
}
|
||||
|
||||
public (IReadOnlyCollection<(string Original, ulong? Id, string Reason)> Bans, int Missing) MassKill(
|
||||
@@ -607,12 +617,12 @@ public class UserPunishService : INService, IReadyExecutor
|
||||
return await _repSvc.ReplaceAsync(output, repCtx);
|
||||
}
|
||||
|
||||
public async Task<Warning> WarnDelete(ulong userId, int index)
|
||||
public async Task<Warning> WarnDelete(ulong guildId, ulong userId, int index)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
|
||||
var warn = await uow.GetTable<Warning>()
|
||||
.Where(x => x.UserId == userId)
|
||||
.Where(x => x.GuildId == guildId && x.UserId == userId)
|
||||
.OrderByDescending(x => x.DateAdded)
|
||||
.Skip(index)
|
||||
.FirstOrDefaultAsyncLinqToDB();
|
||||
@@ -626,4 +636,73 @@ public class UserPunishService : INService, IReadyExecutor
|
||||
|
||||
return warn;
|
||||
}
|
||||
|
||||
public async Task<bool> WarnDelete(ulong guildId, int id)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
|
||||
var numDeleted = await uow.GetTable<Warning>()
|
||||
.Where(x => x.GuildId == guildId && x.Id == id)
|
||||
.DeleteAsync();
|
||||
|
||||
return numDeleted > 0;
|
||||
}
|
||||
|
||||
public async Task<(IReadOnlyCollection<Warning> latest, int totalCount)> GetLatestWarnings(
|
||||
ulong guildId,
|
||||
int page = 1)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(page);
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
var latest = await uow.GetTable<Warning>()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.OrderByDescending(x => x.DateAdded)
|
||||
.Skip(10 * (page - 1))
|
||||
.Take(10)
|
||||
.ToListAsyncLinqToDB();
|
||||
|
||||
var totalCount = await uow.GetTable<Warning>()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.CountAsyncLinqToDB();
|
||||
|
||||
return (latest, totalCount);
|
||||
}
|
||||
|
||||
public async Task<bool> ForgiveWarning(ulong requestGuildId, int warnId, string modName)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var success = await uow.GetTable<Warning>()
|
||||
.Where(x => x.GuildId == requestGuildId && x.Id == warnId)
|
||||
.UpdateAsync(_ => new()
|
||||
{
|
||||
Forgiven = true,
|
||||
ForgivenBy = modName,
|
||||
})
|
||||
== 1;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public async Task<(IReadOnlyCollection<Warning> latest, int totalCount)> GetUserWarnings(
|
||||
ulong guildId,
|
||||
ulong userId,
|
||||
int page)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(page);
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
var latest = await uow.GetTable<Warning>()
|
||||
.Where(x => x.GuildId == guildId && x.UserId == userId)
|
||||
.OrderByDescending(x => x.DateAdded)
|
||||
.Skip(10 * (page - 1))
|
||||
.Take(10)
|
||||
.ToListAsyncLinqToDB();
|
||||
|
||||
var totalCount = await uow.GetTable<Warning>()
|
||||
.Where(x => x.GuildId == guildId && x.UserId == userId)
|
||||
.CountAsyncLinqToDB();
|
||||
|
||||
return (latest, totalCount);
|
||||
}
|
||||
}
|
@@ -67,7 +67,6 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
||||
// private readonly GlobalPermissionService _gperm;
|
||||
// private readonly CmdCdService _cmdCds;
|
||||
private readonly IPermissionChecker _permChecker;
|
||||
private readonly ICommandHandler _cmd;
|
||||
private readonly IBotStrings _strings;
|
||||
private readonly IBot _bot;
|
||||
private readonly IPubSub _pubSub;
|
||||
@@ -84,7 +83,6 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
||||
IBotStrings strings,
|
||||
IBot bot,
|
||||
DiscordSocketClient client,
|
||||
ICommandHandler cmd,
|
||||
IPubSub pubSub,
|
||||
IMessageSenderService sender,
|
||||
IReplacementService repSvc,
|
||||
@@ -93,7 +91,6 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
||||
{
|
||||
_db = db;
|
||||
_client = client;
|
||||
_cmd = cmd;
|
||||
_strings = strings;
|
||||
_bot = bot;
|
||||
_pubSub = pubSub;
|
||||
|
@@ -6,6 +6,10 @@ namespace NadekoBot.Modules.Gambling.Common.AnimalRacing;
|
||||
|
||||
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
|
||||
{
|
||||
WaitingForPlayers,
|
||||
@@ -100,7 +104,7 @@ public sealed class AnimalRace : IDisposable
|
||||
foreach (var user in _users)
|
||||
{
|
||||
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);
|
||||
@@ -116,7 +120,7 @@ public sealed class AnimalRace : IDisposable
|
||||
{
|
||||
foreach (var user in _users)
|
||||
{
|
||||
user.Progress += rng.Next(1, 11);
|
||||
user.Progress += rng.Next(1, 10);
|
||||
if (user.Progress >= 60)
|
||||
user.Progress = 60;
|
||||
}
|
||||
@@ -126,13 +130,15 @@ public sealed class AnimalRace : IDisposable
|
||||
FinishedUsers.AddRange(finished);
|
||||
|
||||
_ = OnStateUpdate?.Invoke(this);
|
||||
await Task.Delay(2500);
|
||||
await Task.Delay(1750);
|
||||
}
|
||||
|
||||
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,
|
||||
FinishedUsers[0].Bet * (_users.Count - 1),
|
||||
(long)(FinishedUsers[0].Bet * Multi),
|
||||
new("animalrace", "win"));
|
||||
}
|
||||
|
||||
@@ -140,6 +146,8 @@ public sealed class AnimalRace : IDisposable
|
||||
});
|
||||
}
|
||||
|
||||
public double Multi { get; set; } = BASE_MULTIPLIER;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CurrentPhase = Phase.Ended;
|
||||
|
@@ -74,10 +74,14 @@ public partial class Gambling
|
||||
if (race.FinishedUsers[0].Bet > 0)
|
||||
{
|
||||
return Response()
|
||||
.Confirm(GetText(strs.animal_race),
|
||||
GetText(strs.animal_race_won_money(Format.Bold(winner.Username),
|
||||
winner.Animal.Icon,
|
||||
(race.FinishedUsers[0].Bet * (race.Users.Count - 1)) + CurrencySign)))
|
||||
.Embed(_sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.animal_race))
|
||||
.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();
|
||||
}
|
||||
|
||||
@@ -113,14 +117,14 @@ public partial class Gambling
|
||||
|
||||
private async Task Ar_OnStateUpdate(AnimalRace race)
|
||||
{
|
||||
var text = $@"|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|
|
||||
var text = $@"|🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁🔚|
|
||||
{string.Join("\n", race.Users.Select(p =>
|
||||
{
|
||||
var index = race.FinishedUsers.IndexOf(p);
|
||||
var extra = index == -1 ? "" : $"#{index + 1} {(index == 0 ? "🏆" : "")}";
|
||||
return $"{(int)(p.Progress / 60f * 100),-2}%|{new string('‣', p.Progress) + p.Animal.Icon + extra}";
|
||||
}))}
|
||||
|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|";
|
||||
|🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🔚|";
|
||||
|
||||
var msg = raceMessage;
|
||||
|
||||
@@ -129,10 +133,10 @@ public partial class Gambling
|
||||
else
|
||||
{
|
||||
await msg.ModifyAsync(x => x.Embed = _sender.CreateEmbed()
|
||||
.WithTitle(GetText(strs.animal_race))
|
||||
.WithDescription(text)
|
||||
.WithOkColor()
|
||||
.Build());
|
||||
.WithTitle(GetText(strs.animal_race))
|
||||
.WithDescription(text)
|
||||
.WithOkColor()
|
||||
.Build());
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -14,6 +14,13 @@ using System.Text;
|
||||
using NadekoBot.Modules.Gambling.Rps;
|
||||
using NadekoBot.Common.TypeReaders;
|
||||
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;
|
||||
|
||||
@@ -26,6 +33,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
private readonly NumberFormatInfo _enUsCulture;
|
||||
private readonly DownloadTracker _tracker;
|
||||
private readonly GamblingConfigService _configService;
|
||||
private readonly FontProvider _fonts;
|
||||
private readonly IBankService _bank;
|
||||
private readonly IRemindService _remind;
|
||||
private readonly GamblingTxTracker _gamblingTxTracker;
|
||||
@@ -38,6 +46,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
DiscordSocketClient client,
|
||||
DownloadTracker tracker,
|
||||
GamblingConfigService configService,
|
||||
FontProvider fonts,
|
||||
IBankService bank,
|
||||
IRemindService remind,
|
||||
IPatronageService patronage,
|
||||
@@ -52,12 +61,14 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
_remind = remind;
|
||||
_gamblingTxTracker = gamblingTxTracker;
|
||||
_ps = patronage;
|
||||
_rng = new NadekoRandom();
|
||||
|
||||
_enUsCulture = new CultureInfo("en-US", false).NumberFormat;
|
||||
_enUsCulture.NumberDecimalDigits = 0;
|
||||
_enUsCulture.NumberGroupSeparator = " ";
|
||||
_tracker = tracker;
|
||||
_configService = configService;
|
||||
_fonts = fonts;
|
||||
}
|
||||
|
||||
public async Task<string> GetBalanceStringAsync(ulong userId)
|
||||
@@ -67,7 +78,65 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
}
|
||||
|
||||
[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();
|
||||
|
||||
@@ -140,7 +209,21 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
(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]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Timely()
|
||||
{
|
||||
var val = Config.Timely.Amount;
|
||||
@@ -151,6 +234,71 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
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)
|
||||
{
|
||||
// Get correct time form remainder
|
||||
@@ -169,6 +317,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 percentBonus = (_ps.PercentBonus(patron) / 100f);
|
||||
@@ -179,7 +351,16 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
|
||||
await _cs.AddAsync(ctx.User.Id, val, new("timely", "claim"));
|
||||
|
||||
await Response().Confirm(strs.timely(N(val), period)).Interaction(inter).SendAsync();
|
||||
if (booster)
|
||||
{
|
||||
var msg = GetText(strs.timely(N(val), period))
|
||||
+ "\n\n"
|
||||
+ $"*+{N(Config.BoostBonus.BaseTimelyBonus)} bonus for boosting {userInfo.guild}!*";
|
||||
|
||||
await Response().Confirm(msg).Interaction(inter).SendAsync();
|
||||
}
|
||||
else
|
||||
await Response().Confirm(strs.timely(N(val), period)).Interaction(inter).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
@@ -290,8 +471,9 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
}
|
||||
|
||||
var embed = _sender.CreateEmbed()
|
||||
.WithTitle(GetText(strs.transactions(((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString()
|
||||
?? $"{userId}")))
|
||||
.WithTitle(GetText(strs.transactions(
|
||||
((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString()
|
||||
?? $"{userId}")))
|
||||
.WithOkColor();
|
||||
|
||||
var sb = new StringBuilder();
|
||||
@@ -547,7 +729,9 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
}
|
||||
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 +752,9 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
}
|
||||
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 +948,8 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
private static readonly ImmutableArray<string> _emojis =
|
||||
new[] { "⬆", "↖", "⬅", "↙", "⬇", "↘", "➡", "↗" }.ToImmutableArray();
|
||||
|
||||
private readonly NadekoRandom _rng;
|
||||
|
||||
|
||||
[Cmd]
|
||||
public async Task LuckyLadder([OverrideTypeReader(typeof(BalanceTypeReader))] long amount)
|
||||
|
@@ -11,7 +11,7 @@ namespace NadekoBot.Modules.Gambling.Common;
|
||||
public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
|
||||
{
|
||||
[Comment("""DO NOT CHANGE""")]
|
||||
public int Version { get; set; } = 8;
|
||||
public int Version { get; set; } = 11;
|
||||
|
||||
[Comment("""Currency settings""")]
|
||||
public CurrencyConfig Currency { get; set; }
|
||||
@@ -67,6 +67,11 @@ public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
|
||||
[Comment("""Slot config""")]
|
||||
public SlotsConfig Slots { get; set; }
|
||||
|
||||
[Comment("""
|
||||
Bonus config for server boosts
|
||||
""")]
|
||||
public BoostBonusConfig BoostBonus { get; set; }
|
||||
|
||||
public GamblingConfig()
|
||||
{
|
||||
BetRoll = new();
|
||||
@@ -79,6 +84,7 @@ public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
|
||||
Slots = new();
|
||||
LuckyLadder = 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
|
||||
setting to 0 or less will disable this feature
|
||||
""")]
|
||||
public int Amount { get; set; } = 0;
|
||||
public long Amount { get; set; } = 0;
|
||||
|
||||
[Comment("""
|
||||
How often (in hours) can users claim currency with .timely command
|
||||
setting to 0 or less will disable this feature
|
||||
""")]
|
||||
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]
|
||||
@@ -408,4 +427,15 @@ public sealed partial class BotCutConfig
|
||||
Default 0.1 (10%).
|
||||
""")]
|
||||
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,
|
||||
val => val >= 0);
|
||||
|
||||
AddParsedProp("timely.prot",
|
||||
gs => gs.Timely.ProtType,
|
||||
ConfigParsers.InsensitiveEnum,
|
||||
ConfigPrinters.ToString);
|
||||
|
||||
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)
|
||||
{
|
||||
ModifyConfig(c =>
|
||||
@@ -199,5 +188,13 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
|
||||
c.Waifu.Decay.UnclaimedDecayPercent = 0;
|
||||
});
|
||||
}
|
||||
|
||||
if (data.Version < 11)
|
||||
{
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
c.Version = 11;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -16,6 +16,7 @@ public class GamblingService : INService, IReadyExecutor
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IBotCache _cache;
|
||||
private readonly GamblingConfigService _gss;
|
||||
private readonly NadekoRandom _rng;
|
||||
|
||||
private static readonly TypedKey<long> _curDecayKey = new("currency:last_decay");
|
||||
|
||||
@@ -29,11 +30,19 @@ public class GamblingService : INService, IReadyExecutor
|
||||
_client = client;
|
||||
_cache = cache;
|
||||
_gss = gss;
|
||||
_rng = new NadekoRandom();
|
||||
}
|
||||
|
||||
public Task OnReadyAsync()
|
||||
=> 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()
|
||||
{
|
||||
if (_client.ShardId != 0)
|
||||
@@ -52,7 +61,7 @@ public class GamblingService : INService, IReadyExecutor
|
||||
var days = TimeSpan.FromDays(lifetime);
|
||||
await using var uow = _db.GetDbContext();
|
||||
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)
|
||||
{
|
||||
@@ -90,11 +99,11 @@ public class GamblingService : INService, IReadyExecutor
|
||||
}
|
||||
|
||||
Log.Information("""
|
||||
--- Decaying users' currency ---
|
||||
| decay: {ConfigDecayPercent}%
|
||||
| max: {MaxDecay}
|
||||
| threshold: {DecayMinTreshold}
|
||||
""",
|
||||
--- Decaying users' currency ---
|
||||
| decay: {ConfigDecayPercent}%
|
||||
| max: {MaxDecay}
|
||||
| threshold: {DecayMinTreshold}
|
||||
""",
|
||||
config.Decay.Percent * 100,
|
||||
maxDecay,
|
||||
config.Decay.MinThreshold);
|
||||
@@ -104,14 +113,14 @@ public class GamblingService : INService, IReadyExecutor
|
||||
|
||||
var decay = (double)config.Decay.Percent;
|
||||
await uow.Set<DiscordUser>()
|
||||
.Where(x => x.CurrencyAmount > config.Decay.MinThreshold && x.UserId != _client.CurrentUser.Id)
|
||||
.UpdateAsync(old => new()
|
||||
{
|
||||
CurrencyAmount =
|
||||
maxDecay > Sql.Round((old.CurrencyAmount * decay) - 0.5)
|
||||
? (long)(old.CurrencyAmount - Sql.Round((old.CurrencyAmount * decay) - 0.5))
|
||||
: old.CurrencyAmount - maxDecay
|
||||
});
|
||||
.Where(x => x.CurrencyAmount > config.Decay.MinThreshold && x.UserId != _client.CurrentUser.Id)
|
||||
.UpdateAsync(old => new()
|
||||
{
|
||||
CurrencyAmount =
|
||||
maxDecay > Sql.Round((old.CurrencyAmount * decay) - 0.5)
|
||||
? (long)(old.CurrencyAmount - Sql.Round((old.CurrencyAmount * decay) - 0.5))
|
||||
: old.CurrencyAmount - maxDecay
|
||||
});
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
|
||||
@@ -133,6 +142,7 @@ public class GamblingService : INService, IReadyExecutor
|
||||
private static TypedKey<Dictionary<ulong, long>> _timelyKey
|
||||
= new("timely:claims");
|
||||
|
||||
|
||||
public async Task<TimeSpan?> ClaimTimelyAsync(ulong userId, int period)
|
||||
{
|
||||
if (period == 0)
|
||||
@@ -178,9 +188,10 @@ public class GamblingService : INService, IReadyExecutor
|
||||
public bool UserHasTimelyReminder(ulong userId)
|
||||
{
|
||||
var db = _db.GetDbContext();
|
||||
return db.GetTable<Reminder>().Any(x => x.UserId == userId
|
||||
&& x.Type == ReminderType.Timely);
|
||||
}
|
||||
return db.GetTable<Reminder>()
|
||||
.Any(x => x.UserId == userId
|
||||
&& x.Type == ReminderType.Timely);
|
||||
}
|
||||
|
||||
public async Task RemoveAllTimelyClaimsAsync()
|
||||
=> await _cache.RemoveAsync(_timelyKey);
|
||||
|
@@ -1,8 +1,11 @@
|
||||
#nullable disable
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Db.Models;
|
||||
using SixLabors.Fonts;
|
||||
using SixLabors.Fonts.Unicode;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Drawing.Processing;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
@@ -25,6 +28,7 @@ public class PlantPickService : INService, IExecNoCommand
|
||||
private readonly NadekoRandom _rng;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly GamblingConfigService _gss;
|
||||
private readonly GamblingService _gs;
|
||||
|
||||
private readonly ConcurrentHashSet<ulong> _generationChannels;
|
||||
private readonly SemaphoreSlim _pickLock = new(1, 1);
|
||||
@@ -37,7 +41,8 @@ public class PlantPickService : INService, IExecNoCommand
|
||||
ICurrencyService cs,
|
||||
CommandHandler cmdHandler,
|
||||
DiscordSocketClient client,
|
||||
GamblingConfigService gss)
|
||||
GamblingConfigService gss,
|
||||
GamblingService gs)
|
||||
{
|
||||
_db = db;
|
||||
_strings = strings;
|
||||
@@ -48,6 +53,7 @@ public class PlantPickService : INService, IExecNoCommand
|
||||
_rng = new();
|
||||
_client = client;
|
||||
_gss = gss;
|
||||
_gs = gs;
|
||||
|
||||
using var uow = db.GetDbContext();
|
||||
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));
|
||||
if (toDelete is not null)
|
||||
uow.Remove(toDelete);
|
||||
|
||||
_generationChannels.TryRemove(cid);
|
||||
enabled = false;
|
||||
}
|
||||
@@ -140,7 +147,7 @@ public class PlantPickService : INService, IExecNoCommand
|
||||
pass = pass.TrimTo(10, true).ToLowerInvariant();
|
||||
using var img = Image.Load<Rgba32>(curImg);
|
||||
// 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 =>
|
||||
{
|
||||
// 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
|
||||
x.FillPolygon(Color.ParseHex("00000080"),
|
||||
new PointF(0, 0),
|
||||
new PointF(1, 1),
|
||||
new PointF(size.Width + 5, 0),
|
||||
new PointF(size.Width + 5, 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
|
||||
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
|
||||
var format = img.Metadata.DecodedImageFormat;
|
||||
@@ -208,7 +233,7 @@ public class PlantPickService : INService, IExecNoCommand
|
||||
+ " "
|
||||
+ 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;
|
||||
var (stream, ext) = await GetRandomCurrencyImageAsync(pw);
|
||||
@@ -232,67 +257,44 @@ public class PlantPickService : INService, IExecNoCommand
|
||||
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(
|
||||
ulong gid,
|
||||
ITextChannel ch,
|
||||
ulong uid,
|
||||
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
|
||||
{
|
||||
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 = 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();
|
||||
_ = ch.DeleteMessagesAsync(ids);
|
||||
}
|
||||
catch { }
|
||||
|
||||
// return the amount of currency the user picked
|
||||
return amount;
|
||||
}
|
||||
|
||||
public async Task<ulong?> SendPlantMessageAsync(
|
||||
|
@@ -252,18 +252,18 @@ public partial class Gambling
|
||||
var claimer = "no one";
|
||||
string status;
|
||||
|
||||
var waifuUsername = w.Username.TrimTo(20);
|
||||
var claimerUsername = w.Claimer?.TrimTo(20);
|
||||
var waifuUsername = w.WaifuName.TrimTo(20);
|
||||
var claimerUsername = w.ClaimerName?.TrimTo(20);
|
||||
|
||||
if (w.Claimer is not null)
|
||||
claimer = $"{claimerUsername}#{w.ClaimerDiscrim}";
|
||||
if (w.ClaimerName is not null)
|
||||
claimer = $"{claimerUsername}";
|
||||
if (w.Affinity is null)
|
||||
status = $"... but {waifuUsername}'s heart is empty";
|
||||
else if (w.Affinity + w.AffinityDiscrim == w.Claimer + w.ClaimerDiscrim)
|
||||
else if (w.Affinity == w.ClaimerName)
|
||||
status = $"... and {waifuUsername} likes {claimerUsername} too <3";
|
||||
else
|
||||
status = $"... but {waifuUsername}'s heart belongs to {w.Affinity.TrimTo(20)}#{w.AffinityDiscrim}";
|
||||
return $"**{waifuUsername}#{w.Discrim}** - claimed by **{claimer}**\n\t{status}";
|
||||
status = $"... but {waifuUsername}'s heart belongs to {w.Affinity.TrimTo(20)}";
|
||||
return $"**{waifuUsername}** - claimed by **{claimer}**\n\t{status}";
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
|
@@ -603,7 +603,7 @@ public class WaifuService : INService, IReadyExecutor
|
||||
.Where(wi => wi.ClaimerId == waifuId)
|
||||
.Select(wi => wi.WaifuId)
|
||||
.Contains(x.Id))
|
||||
.Select(x => $"{x.Username}#{x.Discriminator}")
|
||||
.Select(x => x.Username)
|
||||
.ToListAsyncEF();
|
||||
}
|
||||
|
||||
@@ -615,7 +615,7 @@ public class WaifuService : INService, IReadyExecutor
|
||||
.Where(wi => wi.AffinityId == waifuId)
|
||||
.Select(wi => wi.WaifuId)
|
||||
.Contains(x.Id))
|
||||
.Select(x => $"{x.Username}#{x.Discriminator}")
|
||||
.Select(x => x.Username)
|
||||
.ToListAsyncEF();
|
||||
}
|
||||
|
||||
|
@@ -40,12 +40,14 @@ public static class WaifuExtensions
|
||||
.Take(count)
|
||||
.Select(x => new WaifuLbResult
|
||||
{
|
||||
Affinity = x.Affinity == null ? null : x.Affinity.Username,
|
||||
AffinityDiscrim = x.Affinity == null ? null : x.Affinity.Discriminator,
|
||||
Claimer = x.Claimer == null ? null : x.Claimer.Username,
|
||||
ClaimerDiscrim = x.Claimer == null ? null : x.Claimer.Discriminator,
|
||||
Username = x.Waifu.Username,
|
||||
Discrim = x.Waifu.Discriminator,
|
||||
Affinity = x.Affinity == null
|
||||
? null
|
||||
: x.Affinity.Username,
|
||||
ClaimerName =
|
||||
x.Claimer == null
|
||||
? null
|
||||
: x.Claimer.Username,
|
||||
WaifuName = x.Waifu.Username,
|
||||
Price = x.Price
|
||||
})
|
||||
.ToListAsyncEF();
|
||||
@@ -57,7 +59,7 @@ public static class WaifuExtensions
|
||||
public static ulong GetWaifuUserId(this DbSet<WaifuInfo> waifus, ulong ownerId, string name)
|
||||
=> waifus.AsQueryable()
|
||||
.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)
|
||||
.FirstOrDefault();
|
||||
|
||||
@@ -95,7 +97,7 @@ public static class WaifuExtensions
|
||||
ctx.Set<DiscordUser>()
|
||||
.AsQueryable()
|
||||
.Where(u => u.UserId == userId)
|
||||
.Select(u => u.Username + "#" + u.Discriminator)
|
||||
.Select(u => u.Username)
|
||||
.FirstOrDefault(),
|
||||
AffinityCount =
|
||||
ctx.Set<WaifuUpdate>()
|
||||
@@ -107,14 +109,14 @@ public static class WaifuExtensions
|
||||
ctx.Set<DiscordUser>()
|
||||
.AsQueryable()
|
||||
.Where(u => u.Id == w.AffinityId)
|
||||
.Select(u => u.Username + "#" + u.Discriminator)
|
||||
.Select(u => u.Username)
|
||||
.FirstOrDefault(),
|
||||
ClaimCount = ctx.Set<WaifuInfo>().AsQueryable().Count(x => x.ClaimerId == w.WaifuId),
|
||||
ClaimerName =
|
||||
ctx.Set<DiscordUser>()
|
||||
.AsQueryable()
|
||||
.Where(u => u.Id == w.ClaimerId)
|
||||
.Select(u => u.Username + "#" + u.Discriminator)
|
||||
.Select(u => u.Username)
|
||||
.FirstOrDefault(),
|
||||
DivorceCount =
|
||||
ctx.Set<WaifuUpdate>()
|
||||
|
@@ -3,14 +3,11 @@ namespace NadekoBot.Db.Models;
|
||||
|
||||
public class WaifuLbResult
|
||||
{
|
||||
public string Username { get; set; }
|
||||
public string Discrim { get; set; }
|
||||
public string WaifuName { get; set; }
|
||||
|
||||
public string Claimer { get; set; }
|
||||
public string ClaimerDiscrim { get; set; }
|
||||
public string ClaimerName { get; set; }
|
||||
|
||||
public string Affinity { get; set; }
|
||||
public string AffinityDiscrim { get; set; }
|
||||
|
||||
public long Price { get; set; }
|
||||
}
|
@@ -37,7 +37,7 @@ public sealed class NewGamblingService : IGamblingService, INService
|
||||
var won = (long)result.Won;
|
||||
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;
|
||||
@@ -155,7 +155,7 @@ public sealed class NewGamblingService : IGamblingService, INService
|
||||
var won = (long)result.Won;
|
||||
if (won > 0)
|
||||
{
|
||||
await _cs.AddAsync(userId, won, new("slot", "won"));
|
||||
await _cs.AddAsync(userId, won, new("slot", "win"));
|
||||
}
|
||||
|
||||
return result;
|
||||
|
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()))
|
||||
return;
|
||||
|
||||
await Response().Error(strs.nunchi_joined(nunchi.ParticipantCount)).SendAsync();
|
||||
await Response().Confirm(strs.nunchi_joined(nunchi.ParticipantCount)).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -122,11 +122,11 @@ public sealed class CurrencyRewardService : INService, IReadyExecutor
|
||||
var dollarValue = pledgeCents / 100;
|
||||
percentBonus = dollarValue switch
|
||||
{
|
||||
>= 100 => 100,
|
||||
>= 50 => 50,
|
||||
>= 20 => 20,
|
||||
>= 10 => 10,
|
||||
>= 5 => 5,
|
||||
>= 100 => 20,
|
||||
>= 50 => 10,
|
||||
>= 20 => 5,
|
||||
>= 10 => 3,
|
||||
>= 5 => 1,
|
||||
_ => 0
|
||||
};
|
||||
return (long)(modifiedAmount * (1 + (percentBonus / 100.0f)));
|
||||
|
@@ -67,7 +67,7 @@ public partial class Permissions
|
||||
|
||||
return _sender.CreateEmbed()
|
||||
.WithTitle(title)
|
||||
.WithDescription(allItems.Join('\n'))
|
||||
.WithDescription(pageItems.Join('\n'))
|
||||
.WithOkColor();
|
||||
})
|
||||
.SendAsync();
|
||||
|
@@ -18,6 +18,9 @@ public sealed class YoutubeDataApiSearchService : IYoutubeSearchService, INServi
|
||||
if(results.Count == 0)
|
||||
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)
|
||||
{
|
||||
_atcs[c.ChannelId] = c.AutoDelete;
|
||||
_users[c.ChannelId] =
|
||||
new(c.Users.ToDictionary(x => x.UserId, x => (x.Source.ToLower(), x.Target.ToLower())));
|
||||
_users[c.ChannelId] = new(c.Users.ToDictionary(x => x.UserId, x => (x.Source.ToLower(), x.Target.ToLower())));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg)
|
||||
{
|
||||
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))
|
||||
throw new ArgumentException("Text is empty or null", nameof(text));
|
||||
|
@@ -6,6 +6,14 @@ public partial class Searches
|
||||
[Group]
|
||||
public partial class TranslateCommands : NadekoModule<ITranslateService>
|
||||
{
|
||||
private readonly FlagTranslateService _flagSvc;
|
||||
|
||||
public TranslateCommands(FlagTranslateService flagSvc)
|
||||
{
|
||||
_flagSvc = flagSvc;
|
||||
}
|
||||
|
||||
|
||||
public enum AutoDeleteAutoTranslate
|
||||
{
|
||||
Del,
|
||||
@@ -91,5 +99,18 @@ public partial class Searches
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -100,10 +100,6 @@ public sealed class AiAssistantService
|
||||
|
||||
using var client = _httpFactory.CreateClient();
|
||||
|
||||
// todo customize according to the bot's config
|
||||
// - CurrencyName
|
||||
// -
|
||||
|
||||
using var response = await client.SendAsync(request);
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.TooManyRequests)
|
||||
|
@@ -26,6 +26,8 @@ public interface IQuoteService
|
||||
ulong guildId,
|
||||
string? keyword,
|
||||
string text);
|
||||
|
||||
Task<(IReadOnlyCollection<Quote> quotes, int totalCount)> FindQuotesAsync(ulong guildId, string query, int page);
|
||||
|
||||
Task<IReadOnlyCollection<Quote>> GetGuildQuotesAsync(ulong guildId);
|
||||
Task<int> RemoveAllByKeyword(ulong guildId, string keyword);
|
||||
@@ -39,6 +41,7 @@ public interface IQuoteService
|
||||
string text);
|
||||
|
||||
Task<Quote?> EditQuoteAsync(ulong authorId, int quoteId, string text);
|
||||
Task<Quote?> EditQuoteAsync(ulong guildId, int quoteId, string keyword, string text);
|
||||
|
||||
Task<bool> DeleteQuoteAsync(
|
||||
ulong guildId,
|
||||
|
@@ -169,6 +169,23 @@ public sealed class QuoteService : IQuoteService, INService
|
||||
return q;
|
||||
}
|
||||
|
||||
public async Task<Quote?> EditQuoteAsync(
|
||||
ulong guildId,
|
||||
int quoteId,
|
||||
string keyword,
|
||||
string text)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var result = await uow.GetTable<Quote>()
|
||||
.Where(x => x.Id == quoteId && x.GuildId == guildId)
|
||||
.Set(x => x.Keyword, keyword)
|
||||
.Set(x => x.Text, text)
|
||||
.UpdateWithOutputAsync((del, ins) => ins);
|
||||
|
||||
var q = result.FirstOrDefault();
|
||||
return q;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteQuoteAsync(
|
||||
ulong guildId,
|
||||
ulong authorId,
|
||||
@@ -219,4 +236,24 @@ public sealed class QuoteService : IQuoteService, INService
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<(IReadOnlyCollection<Quote> quotes, int totalCount)> FindQuotesAsync(
|
||||
ulong guildId,
|
||||
string query,
|
||||
int page)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
|
||||
var baseQuery = uow.GetTable<Quote>()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.Where(x => x.Keyword.Contains(query) || x.Text.Contains(query));
|
||||
|
||||
var quotes = await baseQuery
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip((page - 1) * 10)
|
||||
.Take(10)
|
||||
.ToListAsyncLinqToDB();
|
||||
|
||||
return (quotes, await baseQuery.CountAsyncLinqToDB());
|
||||
}
|
||||
}
|
@@ -197,26 +197,29 @@ public class RemindService : INService, IReadyExecutor, IRemindService
|
||||
|
||||
var st = SmartText.CreateFrom(r.Message);
|
||||
|
||||
var res = _sender.Response(ch)
|
||||
.UserBasedMentions(_client.GetGuild(r.ServerId)?.GetUser(r.UserId));
|
||||
|
||||
if (st is SmartEmbedText set)
|
||||
{
|
||||
await _sender.Response(ch).Embed(set.GetEmbed()).SendAsync();
|
||||
await res.Embed(set.GetEmbed()).SendAsync();
|
||||
}
|
||||
else if (st is SmartEmbedTextArray seta)
|
||||
{
|
||||
await _sender.Response(ch).Embeds(seta.GetEmbedBuilders()).SendAsync();
|
||||
await res.Embeds(seta.GetEmbedBuilders()).SendAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await _sender.Response(ch)
|
||||
.Embed(_sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle("Reminder")
|
||||
.AddField("Created At",
|
||||
r.DateAdded.HasValue ? r.DateAdded.Value.ToLongDateString() : "?")
|
||||
.AddField("By",
|
||||
(await ch.GetUserAsync(r.UserId))?.ToString() ?? r.UserId.ToString()))
|
||||
.Text(r.Message)
|
||||
.SendAsync();
|
||||
await res
|
||||
.Embed(_sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle("Reminder")
|
||||
.AddField("Created At",
|
||||
r.DateAdded.HasValue ? r.DateAdded.Value.ToLongDateString() : "?")
|
||||
.AddField("By",
|
||||
(await ch.GetUserAsync(r.UserId))?.ToString() ?? r.UserId.ToString()))
|
||||
.Text(r.Message)
|
||||
.SendAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@@ -107,7 +107,7 @@ public partial class Xp : NadekoModule<XpService>
|
||||
[Cmd]
|
||||
[UserPerm(GuildPerm.ManageChannels)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task XpExclude(Channel _, [Leftover] IChannel channel = null)
|
||||
public async Task XpExclude(Channel _, [Leftover] IChannel? channel = null)
|
||||
{
|
||||
if (channel is null)
|
||||
channel = ctx.Channel;
|
||||
@@ -182,29 +182,28 @@ public partial class Xp : NadekoModule<XpService>
|
||||
var (opts, _) = OptionsParser.ParseFrom(new LbOpts(), args);
|
||||
|
||||
await ctx.Channel.TriggerTypingAsync();
|
||||
|
||||
var socketGuild = (SocketGuild)ctx.Guild;
|
||||
var allCleanUsers = new List<UserXpStats>();
|
||||
if (opts.Clean)
|
||||
{
|
||||
await ctx.Channel.TriggerTypingAsync();
|
||||
await _tracker.EnsureUsersDownloadedAsync(ctx.Guild);
|
||||
|
||||
allCleanUsers = (await _service.GetTopUserXps(ctx.Guild.Id, 1000))
|
||||
.Where(user => socketGuild.GetUser(user.UserId) is not null)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
var res = opts.Clean
|
||||
? Response()
|
||||
.Paginated()
|
||||
.Items(allCleanUsers)
|
||||
: Response()
|
||||
.Paginated()
|
||||
.PageItems((curPage) => _service.GetUserXps(ctx.Guild.Id, curPage));
|
||||
async Task<IReadOnlyCollection<UserXpStats>> GetPageItems(int curPage)
|
||||
{
|
||||
var socketGuild = (SocketGuild)ctx.Guild;
|
||||
if (opts.Clean)
|
||||
{
|
||||
return await _service.GetGuildUserXps(ctx.Guild.Id,
|
||||
socketGuild.Users.Select(x => x.Id).ToList(),
|
||||
curPage);
|
||||
}
|
||||
|
||||
await res
|
||||
.PageSize(9)
|
||||
return await _service.GetGuildUserXps(ctx.Guild.Id, curPage);
|
||||
}
|
||||
|
||||
await Response()
|
||||
.Paginated()
|
||||
.PageItems(GetPageItems)
|
||||
.PageSize(10)
|
||||
.CurrentPage(page)
|
||||
.Page((users, curPage) =>
|
||||
{
|
||||
@@ -226,7 +225,7 @@ public partial class Xp : NadekoModule<XpService>
|
||||
else if (userXpData.AwardedXp < 0)
|
||||
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}");
|
||||
}
|
||||
|
||||
@@ -237,15 +236,33 @@ public partial class Xp : NadekoModule<XpService>
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task XpGlobalLeaderboard(int page = 1)
|
||||
public async Task XpGlobalLeaderboard(int page = 1, params string[] args)
|
||||
{
|
||||
if (--page < 0 || page > 99)
|
||||
return;
|
||||
|
||||
var (opts, _) = OptionsParser.ParseFrom(new LbOpts(), args);
|
||||
|
||||
await ctx.Channel.TriggerTypingAsync();
|
||||
if (opts.Clean)
|
||||
{
|
||||
await _tracker.EnsureUsersDownloadedAsync(ctx.Guild);
|
||||
}
|
||||
|
||||
async Task<IReadOnlyCollection<DiscordUser>> GetPageItems(int curPage)
|
||||
{
|
||||
if (opts.Clean)
|
||||
{
|
||||
return await _service.GetGlobalUserXps(page, ((SocketGuild)ctx.Guild).Users.Select(x => x.Id).ToList());
|
||||
}
|
||||
|
||||
return await _service.GetGlobalUserXps(curPage);
|
||||
}
|
||||
|
||||
await Response()
|
||||
.Paginated()
|
||||
.PageItems(async curPage => await _service.GetUserXps(curPage))
|
||||
.PageSize(9)
|
||||
.PageItems(GetPageItems)
|
||||
.PageSize(10)
|
||||
.Page((users, curPage) =>
|
||||
{
|
||||
var embed = _sender.CreateEmbed()
|
||||
@@ -261,7 +278,7 @@ public partial class Xp : NadekoModule<XpService>
|
||||
for (var i = 0; i < users.Count; 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");
|
||||
}
|
||||
|
||||
@@ -282,7 +299,9 @@ public partial class Xp : NadekoModule<XpService>
|
||||
if (role.IsManaged)
|
||||
return;
|
||||
|
||||
var count = await _service.AddXpToUsersAsync(ctx.Guild.Id, amount, role.Members.Select(x => x.Id).ToArray());
|
||||
var count = await _service.AddXpToUsersAsync(ctx.Guild.Id,
|
||||
amount,
|
||||
role.Members.Select(x => x.Id).ToArray());
|
||||
await Response()
|
||||
.Confirm(
|
||||
strs.xpadd_users(Format.Bold(amount.ToString()), Format.Bold(count.ToString())))
|
||||
@@ -338,7 +357,7 @@ public partial class Xp : NadekoModule<XpService>
|
||||
if (!await PromptUserConfirmAsync(embed))
|
||||
return;
|
||||
|
||||
_service.XpReset(ctx.Guild.Id, userId);
|
||||
await _service.XpReset(ctx.Guild.Id, userId);
|
||||
|
||||
await Response().Confirm(strs.reset_user(userId)).SendAsync();
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using System.Threading.Channels;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using LinqToDB.Tools;
|
||||
using NadekoBot.Modules.Patronage;
|
||||
using Color = SixLabors.ImageSharp.Color;
|
||||
using Exception = System.Exception;
|
||||
@@ -19,6 +20,31 @@ using Image = SixLabors.ImageSharp.Image;
|
||||
|
||||
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
|
||||
{
|
||||
private readonly DbService _db;
|
||||
@@ -563,23 +589,50 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<UserXpStats>> GetUserXps(ulong guildId, int page)
|
||||
public async Task<IReadOnlyCollection<UserXpStats>> GetGuildUserXps(ulong guildId, int page)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
return await uow.Set<UserXpStats>().GetUsersFor(guildId, page);
|
||||
return await uow
|
||||
.UserXpStats
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.OrderByDescending(x => x.Xp + x.AwardedXp)
|
||||
.Skip(page * 10)
|
||||
.Take(10)
|
||||
.ToArrayAsyncLinqToDB();
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<UserXpStats>> GetTopUserXps(ulong guildId, int count)
|
||||
public async Task<IReadOnlyCollection<UserXpStats>> GetGuildUserXps(ulong guildId, List<ulong> users, int page)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
return await uow.Set<UserXpStats>().GetTopUserXps(guildId, count);
|
||||
return await uow.Set<UserXpStats>()
|
||||
.Where(x => x.GuildId == guildId && x.UserId.In(users))
|
||||
.OrderByDescending(x => x.Xp + x.AwardedXp)
|
||||
.Skip(page * 10)
|
||||
.Take(10)
|
||||
.ToArrayAsyncLinqToDB();
|
||||
}
|
||||
|
||||
public Task<IReadOnlyCollection<DiscordUser>> GetUserXps(int page, int perPage = 9)
|
||||
public async Task<IReadOnlyCollection<DiscordUser>> GetGlobalUserXps(int page)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow.Set<DiscordUser>()
|
||||
.GetUsersXpLeaderboardFor(page, perPage);
|
||||
await using var uow = _db.GetDbContext();
|
||||
|
||||
return await uow.GetTable<DiscordUser>()
|
||||
.OrderByDescending(x => x.TotalXp)
|
||||
.Skip(page * 10)
|
||||
.Take(10)
|
||||
.ToArrayAsyncLinqToDB();
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<DiscordUser>> GetGlobalUserXps(int page, List<ulong> users)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
|
||||
return await uow.GetTable<DiscordUser>()
|
||||
.Where(x => x.UserId.In(users))
|
||||
.OrderByDescending(x => x.TotalXp)
|
||||
.Skip(page * 10)
|
||||
.Take(10)
|
||||
.ToArrayAsyncLinqToDB();
|
||||
}
|
||||
|
||||
public async Task ChangeNotificationType(ulong userId, ulong guildId, XpNotificationLocation type)
|
||||
@@ -1409,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();
|
||||
uow.Set<UserXpStats>().ResetGuildUserXp(userId, guildId);
|
||||
uow.SaveChanges();
|
||||
await using var uow = _db.GetDbContext();
|
||||
await uow.GetTable<UserXpStats>()
|
||||
.DeleteAsync(x => x.UserId == userId && x.GuildId == guildId);
|
||||
}
|
||||
|
||||
public void XpReset(ulong guildId)
|
||||
@@ -1609,6 +1662,15 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
||||
|
||||
public bool IsShopEnabled()
|
||||
=> _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
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>true</ImplicitUsings>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
<Version>5.1.13</Version>
|
||||
<Version>5.1.19</Version>
|
||||
|
||||
<!-- Output/build -->
|
||||
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
||||
|
@@ -2,21 +2,31 @@
|
||||
using Grpc.Core;
|
||||
using NadekoBot.Db.Models;
|
||||
using NadekoBot.Modules.NadekoExpressions;
|
||||
using NadekoBot.Modules.Utility;
|
||||
|
||||
namespace NadekoBot.GrpcApi;
|
||||
|
||||
public class ExprsSvc : GrpcExprs.GrpcExprsBase, INService
|
||||
public class ExprsSvc : GrpcExprs.GrpcExprsBase, IGrpcSvc, INService
|
||||
{
|
||||
private readonly NadekoExpressionsService _svc;
|
||||
private readonly IQuoteService _qs;
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public ExprsSvc(NadekoExpressionsService svc)
|
||||
public ExprsSvc(NadekoExpressionsService svc, IQuoteService qs, DiscordSocketClient client)
|
||||
{
|
||||
_svc = svc;
|
||||
_qs = qs;
|
||||
_client = client;
|
||||
}
|
||||
|
||||
[GrpcApiPerm(GuildPerm.Administrator)]
|
||||
public ServerServiceDefinition Bind()
|
||||
=> GrpcExprs.BindService(this);
|
||||
|
||||
public override async Task<AddExprReply> AddExpr(AddExprRequest request, ServerCallContext context)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Expr.Trigger) || string.IsNullOrWhiteSpace(request.Expr.Response))
|
||||
throw new RpcException(new Status(StatusCode.InvalidArgument, "Trigger and response are required"));
|
||||
|
||||
NadekoExpression expr;
|
||||
if (!string.IsNullOrWhiteSpace(request.Expr.Id))
|
||||
{
|
||||
@@ -45,7 +55,6 @@ public class ExprsSvc : GrpcExprs.GrpcExprsBase, INService
|
||||
};
|
||||
}
|
||||
|
||||
[GrpcApiPerm(GuildPerm.Administrator)]
|
||||
public override async Task<GetExprsReply> GetExprs(GetExprsRequest request, ServerCallContext context)
|
||||
{
|
||||
var (exprs, totalCount) = await _svc.FindExpressionsAsync(request.GuildId, request.Query, request.Page);
|
||||
@@ -66,11 +75,75 @@ public class ExprsSvc : GrpcExprs.GrpcExprsBase, INService
|
||||
return reply;
|
||||
}
|
||||
|
||||
[GrpcApiPerm(GuildPerm.Administrator)]
|
||||
public override async Task<Empty> DeleteExpr(DeleteExprRequest request, ServerCallContext context)
|
||||
{
|
||||
await _svc.DeleteAsync(request.GuildId, new kwum(request.Id));
|
||||
if (kwum.TryParse(request.Id, out var id))
|
||||
await _svc.DeleteAsync(request.GuildId, id);
|
||||
|
||||
return new Empty();
|
||||
}
|
||||
|
||||
public override async Task<GetQuotesReply> GetQuotes(GetQuotesRequest request, ServerCallContext context)
|
||||
{
|
||||
if (request.Page < 0)
|
||||
throw new RpcException(new Status(StatusCode.InvalidArgument, "Page must be >= 0"));
|
||||
|
||||
var (quotes, totalCount) = await _qs.FindQuotesAsync(request.GuildId, request.Query, request.Page);
|
||||
|
||||
var reply = new GetQuotesReply();
|
||||
reply.TotalCount = totalCount;
|
||||
reply.Quotes.AddRange(quotes.Select(x => new QuoteDto()
|
||||
{
|
||||
Id = new kwum(x.Id).ToString(),
|
||||
Trigger = x.Keyword,
|
||||
Response = x.Text,
|
||||
AuthorId = x.AuthorId,
|
||||
AuthorName = x.AuthorName
|
||||
}));
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
public override async Task<AddQuoteReply> AddQuote(AddQuoteRequest request, ServerCallContext context)
|
||||
{
|
||||
var userId = context.RequestHeaders.GetUserId();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.Quote.Trigger) || string.IsNullOrWhiteSpace(request.Quote.Response))
|
||||
throw new RpcException(new Status(StatusCode.InvalidArgument, "Trigger and response are required"));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.Quote.Id))
|
||||
{
|
||||
var q = await _qs.AddQuoteAsync(request.GuildId,
|
||||
userId,
|
||||
(await _client.GetUserAsync(userId))?.Username ?? userId.ToString(),
|
||||
request.Quote.Trigger,
|
||||
request.Quote.Response);
|
||||
|
||||
return new()
|
||||
{
|
||||
Id = new kwum(q.Id).ToString()
|
||||
};
|
||||
}
|
||||
|
||||
if (!kwum.TryParse(request.Quote.Id, out var qid))
|
||||
throw new RpcException(new Status(StatusCode.InvalidArgument, "Invalid quote id"));
|
||||
|
||||
await _qs.EditQuoteAsync(
|
||||
request.GuildId,
|
||||
new kwum(request.Quote.Id),
|
||||
request.Quote.Trigger,
|
||||
request.Quote.Response);
|
||||
|
||||
return new()
|
||||
{
|
||||
Id = new kwum(qid).ToString()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public override async Task<Empty> DeleteQuote(DeleteQuoteRequest request, ServerCallContext context)
|
||||
{
|
||||
await _qs.DeleteQuoteAsync(request.GuildId, context.RequestHeaders.GetUserId(), true, new kwum(request.Id));
|
||||
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;
|
||||
}
|
||||
}
|
@@ -3,7 +3,7 @@ using GreetType = NadekoBot.Services.GreetType;
|
||||
|
||||
namespace NadekoBot.GrpcApi;
|
||||
|
||||
public sealed class GreetByeSvc : GrpcGreet.GrpcGreetBase, INService
|
||||
public sealed class GreetByeSvc : GrpcGreet.GrpcGreetBase, IGrpcSvc, INService
|
||||
{
|
||||
private readonly GreetService _gs;
|
||||
private readonly DiscordSocketClient _client;
|
||||
@@ -14,55 +14,53 @@ public sealed class GreetByeSvc : GrpcGreet.GrpcGreetBase, INService
|
||||
_client = client;
|
||||
}
|
||||
|
||||
public GreetSettings GetDefaultGreet(GreetType type)
|
||||
=> new GreetSettings()
|
||||
{
|
||||
GreetType = type
|
||||
};
|
||||
public ServerServiceDefinition Bind()
|
||||
=> GrpcGreet.BindService(this);
|
||||
|
||||
private static GrpcGreetSettings ToConf(GreetSettings? conf)
|
||||
private static GrpcGreetSettings ToConf(GreetSettings? conf, GreetType type)
|
||||
{
|
||||
if (conf is null)
|
||||
return new GrpcGreetSettings();
|
||||
return new GrpcGreetSettings()
|
||||
{
|
||||
Type = (GrpcGreetType)type
|
||||
};
|
||||
|
||||
return new GrpcGreetSettings()
|
||||
{
|
||||
Message = conf.MessageText,
|
||||
Type = (GrpcGreetType)conf.GreetType,
|
||||
ChannelId = conf.ChannelId ?? 0,
|
||||
ChannelId = conf.ChannelId?.ToString() ?? string.Empty,
|
||||
IsEnabled = conf.IsEnabled,
|
||||
};
|
||||
}
|
||||
|
||||
[GrpcApiPerm(GuildPerm.Administrator)]
|
||||
public override async Task<GetGreetReply> GetGreetSettings(GetGreetRequest request, ServerCallContext context)
|
||||
public override async Task<GrpcGreetSettings> GetGreetSettings(GetGreetRequest request, ServerCallContext context)
|
||||
{
|
||||
var guildId = request.GuildId;
|
||||
|
||||
var greetConf = await _gs.GetGreetSettingsAsync(guildId, GreetType.Greet);
|
||||
var byeConf = await _gs.GetGreetSettingsAsync(guildId, GreetType.Bye);
|
||||
var boostConf = await _gs.GetGreetSettingsAsync(guildId, GreetType.Boost);
|
||||
var greetDmConf = await _gs.GetGreetSettingsAsync(guildId, GreetType.GreetDm);
|
||||
// todo timer
|
||||
var type = (GreetType)request.Type;
|
||||
var conf = await _gs.GetGreetSettingsAsync(guildId, type);
|
||||
|
||||
return new GetGreetReply()
|
||||
{
|
||||
Greet = ToConf(greetConf),
|
||||
Bye = ToConf(byeConf),
|
||||
Boost = ToConf(boostConf),
|
||||
GreetDm = ToConf(greetDmConf)
|
||||
};
|
||||
return ToConf(conf, type);
|
||||
}
|
||||
|
||||
[GrpcApiPerm(GuildPerm.Administrator)]
|
||||
public override async Task<UpdateGreetReply> UpdateGreet(UpdateGreetRequest request, ServerCallContext context)
|
||||
{
|
||||
var gid = request.GuildId;
|
||||
var s = request.Settings;
|
||||
var msg = s.Message;
|
||||
|
||||
var type = GetGreetType(s.Type);
|
||||
|
||||
await _gs.SetMessage(gid, GetGreetType(s.Type), msg);
|
||||
await _gs.SetGreet(gid, s.ChannelId, GetGreetType(s.Type), s.IsEnabled);
|
||||
await _gs.SetGreet(gid, ulong.Parse(s.ChannelId), type, s.IsEnabled);
|
||||
var settings = await _gs.GetGreetSettingsAsync(gid, type);
|
||||
|
||||
if (settings is null)
|
||||
return new()
|
||||
{
|
||||
Success = false
|
||||
};
|
||||
|
||||
return new()
|
||||
{
|
||||
@@ -70,7 +68,6 @@ public sealed class GreetByeSvc : GrpcGreet.GrpcGreetBase, INService
|
||||
};
|
||||
}
|
||||
|
||||
[GrpcApiPerm(GuildPerm.Administrator)]
|
||||
public override Task<TestGreetReply> TestGreet(TestGreetRequest request, ServerCallContext context)
|
||||
=> TestGreet(request.GuildId, request.ChannelId, request.UserId, request.Type);
|
||||
|
||||
|
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;
|
||||
}
|
||||
}
|
@@ -11,63 +11,69 @@ public static class GrpcApiExtensions
|
||||
=> ulong.Parse(context.RequestHeaders.FirstOrDefault(x => x.Key == "userid")!.Value);
|
||||
}
|
||||
|
||||
public sealed class OtherSvc : GrpcOther.GrpcOtherBase, INService
|
||||
public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, INService
|
||||
{
|
||||
private readonly IDiscordClient _client;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly XpService _xp;
|
||||
private readonly ICurrencyService _cur;
|
||||
private readonly WaifuService _waifus;
|
||||
private readonly ICoordinator _coord;
|
||||
private readonly IStatsService _stats;
|
||||
private readonly CommandHandler _cmdHandler;
|
||||
|
||||
public OtherSvc(
|
||||
DiscordSocketClient client,
|
||||
XpService xp,
|
||||
ICurrencyService cur,
|
||||
WaifuService waifus,
|
||||
ICoordinator coord,
|
||||
IStatsService stats)
|
||||
IStatsService stats,
|
||||
CommandHandler cmdHandler)
|
||||
{
|
||||
_client = client;
|
||||
_xp = xp;
|
||||
_cur = cur;
|
||||
_waifus = waifus;
|
||||
_coord = coord;
|
||||
_stats = stats;
|
||||
_cmdHandler = cmdHandler;
|
||||
}
|
||||
|
||||
public override async Task<GetGuildsReply> GetGuilds(Empty request, ServerCallContext context)
|
||||
public ServerServiceDefinition Bind()
|
||||
=> GrpcOther.BindService(this);
|
||||
|
||||
[GrpcNoAuthRequired]
|
||||
public override Task<BotOnGuildReply> BotOnGuild(BotOnGuildRequest request, ServerCallContext context)
|
||||
{
|
||||
var guilds = await _client.GetGuildsAsync(CacheMode.CacheOnly);
|
||||
var guild = _client.GetGuild(request.GuildId);
|
||||
|
||||
var reply = new GetGuildsReply();
|
||||
var userId = context.GetUserId();
|
||||
|
||||
var toReturn = new List<IGuild>();
|
||||
foreach (var g in guilds)
|
||||
var reply = new BotOnGuildReply
|
||||
{
|
||||
var user = await g.GetUserAsync(userId, CacheMode.AllowDownload);
|
||||
if (user.GuildPermissions.Has(GuildPermission.Administrator))
|
||||
toReturn.Add(g);
|
||||
}
|
||||
Success = guild is not null
|
||||
};
|
||||
|
||||
reply.Guilds.AddRange(toReturn
|
||||
.Select(x => new GuildReply()
|
||||
{
|
||||
Id = x.Id,
|
||||
Name = x.Name,
|
||||
IconUrl = x.IconUrl
|
||||
}));
|
||||
|
||||
return reply;
|
||||
return Task.FromResult(reply);
|
||||
}
|
||||
|
||||
public override Task<GetRolesReply> GetRoles(GetRolesRequest request, ServerCallContext context)
|
||||
{
|
||||
var g = _client.GetGuild(request.GuildId);
|
||||
var roles = g?.Roles;
|
||||
var reply = new GetRolesReply();
|
||||
reply.Roles.AddRange(roles?.Select(x => new RoleReply()
|
||||
{
|
||||
Id = x.Id,
|
||||
Name = x.Name,
|
||||
Color = x.Color.ToString(),
|
||||
IconUrl = x.GetIconUrl() ?? string.Empty,
|
||||
})
|
||||
?? new List<RoleReply>());
|
||||
|
||||
return Task.FromResult(reply);
|
||||
}
|
||||
|
||||
[GrpcApiPerm(GuildPerm.Administrator)]
|
||||
public override async Task<GetTextChannelsReply> GetTextChannels(
|
||||
GetTextChannelsRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
var g = await _client.GetGuildAsync(request.GuildId);
|
||||
IGuild g = _client.GetGuild(request.GuildId);
|
||||
var reply = new GetTextChannelsReply();
|
||||
|
||||
var chs = await g.GetTextChannelsAsync();
|
||||
@@ -81,21 +87,23 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, INService
|
||||
return reply;
|
||||
}
|
||||
|
||||
|
||||
[GrpcNoAuthRequired]
|
||||
public override async Task<CurrencyLbReply> GetCurrencyLb(GetLbRequest request, ServerCallContext context)
|
||||
{
|
||||
var users = await _cur.GetTopRichest(_client.CurrentUser.Id, request.Page, request.PerPage);
|
||||
|
||||
var reply = new CurrencyLbReply();
|
||||
var entries = users.Select(async x =>
|
||||
var entries = users.Select(x =>
|
||||
{
|
||||
var user = await _client.GetUserAsync(x.UserId, CacheMode.CacheOnly);
|
||||
return new CurrencyLbEntryReply()
|
||||
var user = _client.GetUser(x.UserId);
|
||||
return Task.FromResult(new CurrencyLbEntryReply()
|
||||
{
|
||||
Amount = x.CurrencyAmount,
|
||||
User = user?.ToString() ?? x.Username,
|
||||
UserId = x.UserId,
|
||||
Avatar = user?.RealAvatarUrl().ToString() ?? x.RealAvatarUrl()?.ToString()
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
reply.Entries.AddRange(await entries.WhenAll());
|
||||
@@ -103,9 +111,10 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, INService
|
||||
return reply;
|
||||
}
|
||||
|
||||
[GrpcNoAuthRequired]
|
||||
public override async Task<XpLbReply> GetXpLb(GetLbRequest request, ServerCallContext context)
|
||||
{
|
||||
var users = await _xp.GetUserXps(request.Page, request.PerPage);
|
||||
var users = await _xp.GetGlobalUserXps(request.Page);
|
||||
|
||||
var reply = new XpLbReply();
|
||||
|
||||
@@ -127,6 +136,7 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, INService
|
||||
return reply;
|
||||
}
|
||||
|
||||
[GrpcNoAuthRequired]
|
||||
public override async Task<WaifuLbReply> GetWaifuLb(GetLbRequest request, ServerCallContext context)
|
||||
{
|
||||
var waifus = await _waifus.GetTopWaifusAtPage(request.Page, request.PerPage);
|
||||
@@ -134,33 +144,77 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, INService
|
||||
var reply = new WaifuLbReply();
|
||||
reply.Entries.AddRange(waifus.Select(x => new WaifuLbEntry()
|
||||
{
|
||||
ClaimedBy = x.Claimer ?? string.Empty,
|
||||
IsMutual = x.Claimer == x.Affinity,
|
||||
ClaimedBy = x.ClaimerName ?? string.Empty,
|
||||
IsMutual = x.ClaimerName == x.Affinity,
|
||||
Value = x.Price,
|
||||
User = x.Username,
|
||||
User = x.WaifuName,
|
||||
}));
|
||||
return reply;
|
||||
}
|
||||
|
||||
public override Task<GetShardStatusesReply> GetShardStatuses(Empty request, ServerCallContext context)
|
||||
[GrpcNoAuthRequired]
|
||||
public override async Task GetShardStats(
|
||||
Empty request,
|
||||
IServerStreamWriter<ShardStatsReply> responseStream,
|
||||
ServerCallContext context)
|
||||
{
|
||||
var reply = new GetShardStatusesReply();
|
||||
|
||||
// todo cache
|
||||
var shards = _coord.GetAllShardStatuses();
|
||||
|
||||
reply.Shards.AddRange(shards.Select(x => new ShardStatusReply()
|
||||
while (true)
|
||||
{
|
||||
Id = x.ShardId,
|
||||
Status = x.ConnectionState.ToString(),
|
||||
GuildCount = x.GuildCount,
|
||||
LastUpdate = Timestamp.FromDateTime(x.LastUpdate),
|
||||
}));
|
||||
var stats = new ShardStatsReply()
|
||||
{
|
||||
Id = _client.ShardId,
|
||||
Commands = _stats.CommandsRan,
|
||||
Uptime = _stats.GetUptimeString(),
|
||||
Status = GetConnectionState(_client.ConnectionState),
|
||||
GuildCount = _client.Guilds.Count,
|
||||
};
|
||||
|
||||
return Task.FromResult(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"
|
||||
};
|
||||
}
|
||||
|
||||
[GrpcApiPerm(GuildPerm.Administrator)]
|
||||
public override async Task<GetServerInfoReply> GetServerInfo(ServerInfoRequest request, ServerCallContext context)
|
||||
{
|
||||
var info = await _stats.GetGuildInfoAsync(request.GuildId);
|
||||
|
224
src/NadekoBot/Services/GrpcApi/WarnSvc.cs
Normal file
224
src/NadekoBot/Services/GrpcApi/WarnSvc.cs
Normal file
@@ -0,0 +1,224 @@
|
||||
using Grpc.Core;
|
||||
using NadekoBot.Db.Models;
|
||||
using NadekoBot.Modules.Administration.Services;
|
||||
using Enum = System.Enum;
|
||||
|
||||
namespace NadekoBot.GrpcApi;
|
||||
|
||||
public sealed class WarnSvc : GrpcWarn.GrpcWarnBase, IGrpcSvc, INService
|
||||
{
|
||||
private readonly UserPunishService _ups;
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public WarnSvc(UserPunishService ups, DiscordSocketClient client)
|
||||
{
|
||||
_ups = ups;
|
||||
_client = client;
|
||||
}
|
||||
|
||||
public ServerServiceDefinition Bind()
|
||||
=> GrpcWarn.BindService(this);
|
||||
|
||||
public override async Task<WarnSettingsReply> GetWarnSettings(
|
||||
WarnSettingsRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
var list = await _ups.WarnPunishList(request.GuildId);
|
||||
|
||||
var wsr = new WarnSettingsReply();
|
||||
|
||||
(wsr.ExpiryDays, wsr.DeleteOnExpire) = await _ups.GetWarnExpire(request.GuildId);
|
||||
|
||||
wsr.Punishments.AddRange(list.Select(x => new WarnPunishment()
|
||||
{
|
||||
Action = x.Punishment.ToString(),
|
||||
Duration = x.Time,
|
||||
Threshold = x.Count,
|
||||
Role = x.RoleId is ulong rid
|
||||
? _client.GetGuild(request.GuildId)?.GetRole(rid)?.Name ?? x.RoleId?.ToString() ?? string.Empty
|
||||
: string.Empty
|
||||
}));
|
||||
|
||||
return wsr;
|
||||
}
|
||||
|
||||
public override async Task<SetWarnExpiryReply> SetWarnExpiry(
|
||||
SetWarnExpiryRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
if (request.ExpiryDays > 366)
|
||||
{
|
||||
return new SetWarnExpiryReply()
|
||||
{
|
||||
Success = false
|
||||
};
|
||||
}
|
||||
|
||||
await _ups.WarnExpireAsync(request.GuildId, request.ExpiryDays, request.DeleteOnExpire);
|
||||
|
||||
return new SetWarnExpiryReply()
|
||||
{
|
||||
Success = true
|
||||
};
|
||||
}
|
||||
|
||||
public override async Task<DeleteWarnpReply> DeleteWarnp(DeleteWarnpRequest request, ServerCallContext context)
|
||||
{
|
||||
var succ = await _ups.WarnPunishRemove(request.GuildId, request.Threshold);
|
||||
|
||||
return new DeleteWarnpReply
|
||||
{
|
||||
Success = succ
|
||||
};
|
||||
}
|
||||
|
||||
public override async Task<AddWarnpReply> AddWarnp(AddWarnpRequest request, ServerCallContext context)
|
||||
{
|
||||
if (request.Punishment.Threshold <= 0)
|
||||
throw new RpcException(new Status(StatusCode.InvalidArgument, "Threshold must be greater than 0"));
|
||||
|
||||
var g = _client.GetGuild(request.GuildId);
|
||||
|
||||
if (g is null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "Guild not found"));
|
||||
|
||||
if (!Enum.TryParse<PunishmentAction>(request.Punishment.Action, out var action))
|
||||
throw new RpcException(new Status(StatusCode.InvalidArgument, "Invalid action"));
|
||||
|
||||
IRole? role = null;
|
||||
if (action == PunishmentAction.AddRole && ulong.TryParse(request.Punishment.Role, out var roleId))
|
||||
{
|
||||
role = g.GetRole(roleId);
|
||||
|
||||
if (role is null)
|
||||
return new AddWarnpReply()
|
||||
{
|
||||
Success = false
|
||||
};
|
||||
|
||||
if(!ulong.TryParse(context.RequestHeaders.GetValue("userid"), out var userId))
|
||||
return new AddWarnpReply()
|
||||
{
|
||||
Success = false
|
||||
};
|
||||
|
||||
var user = await ((IGuild)g).GetUserAsync(userId);
|
||||
|
||||
if (user is null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "User not found"));
|
||||
|
||||
var userMaxRole = user.GetRoles().MaxBy(x => x.Position)?.Position ?? 0;
|
||||
if (g.OwnerId != user.Id && userMaxRole <= role.Position)
|
||||
{
|
||||
return new AddWarnpReply()
|
||||
{
|
||||
Success = false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var duration = TimeSpan.FromMinutes(request.Punishment.Duration);
|
||||
|
||||
var succ = await _ups.WarnPunish(request.GuildId,
|
||||
request.Punishment.Threshold,
|
||||
action,
|
||||
duration,
|
||||
role
|
||||
);
|
||||
|
||||
return new AddWarnpReply()
|
||||
{
|
||||
Success = succ
|
||||
};
|
||||
}
|
||||
|
||||
public override async Task<GetLatestWarningsReply> GetLatestWarnings(
|
||||
GetLatestWarningsRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
var (latest, count) = await _ups.GetLatestWarnings(request.GuildId, request.Page);
|
||||
|
||||
var reply = new GetLatestWarningsReply()
|
||||
{
|
||||
TotalCount = count
|
||||
};
|
||||
|
||||
reply.Warnings.AddRange(latest.Select(MapWarningToGrpcWarning));
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
public override async Task<GetUserWarningsReply> GetUserWarnings(
|
||||
GetUserWarningsRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
IReadOnlyCollection<Db.Models.Warning> latest = [];
|
||||
var count = 0;
|
||||
if (ulong.TryParse(request.User, out var userId))
|
||||
{
|
||||
(latest, count) = await _ups.GetUserWarnings(request.GuildId, userId, request.Page);
|
||||
}
|
||||
else if (_client.GetGuild(request.GuildId)?.Users.FirstOrDefault(x => x.Username == request.User) is { } user)
|
||||
{
|
||||
(latest, count) = await _ups.GetUserWarnings(request.GuildId, user.Id, request.Page);
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
|
||||
var reply = new GetUserWarningsReply
|
||||
{
|
||||
TotalCount = count
|
||||
};
|
||||
|
||||
reply.Warnings.AddRange(latest.Select(MapWarningToGrpcWarning));
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
private Warning MapWarningToGrpcWarning(Db.Models.Warning x)
|
||||
{
|
||||
return new Warning
|
||||
{
|
||||
Id = new kwum(x.Id).ToString(),
|
||||
Forgiven = x.Forgiven,
|
||||
ForgivenBy = x.ForgivenBy ?? string.Empty,
|
||||
Reason = x.Reason ?? string.Empty,
|
||||
Timestamp = x.DateAdded is { } da ? Nadeko.Common.Extensions.ToTimestamp(da) : 0,
|
||||
Weight = x.Weight,
|
||||
Moderator = x.Moderator ?? string.Empty,
|
||||
User = _client.GetUser(x.UserId)?.Username ?? x.UserId.ToString(),
|
||||
UserId = x.UserId
|
||||
};
|
||||
}
|
||||
|
||||
public override async Task<ForgiveWarningReply> ForgiveWarning(
|
||||
ForgiveWarningRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
if (!kwum.TryParse(request.WarnId, out var wid))
|
||||
throw new RpcException(new Status(StatusCode.InvalidArgument, "Invalid warning ID"));
|
||||
|
||||
var succ = await _ups.ForgiveWarning(request.GuildId, wid, request.ModName);
|
||||
|
||||
return new ForgiveWarningReply
|
||||
{
|
||||
Success = succ
|
||||
};
|
||||
}
|
||||
|
||||
public override async Task<ForgiveWarningReply> DeleteWarning(
|
||||
ForgiveWarningRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
if (!kwum.TryParse(request.WarnId, out var wid))
|
||||
throw new RpcException(new Status(StatusCode.InvalidArgument, "Invalid warning ID"));
|
||||
|
||||
var succ = await _ups.WarnDelete(request.GuildId, wid);
|
||||
|
||||
return new ForgiveWarningReply
|
||||
{
|
||||
Success = succ
|
||||
};
|
||||
}
|
||||
}
|
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
|
||||
};
|
||||
}
|
||||
}
|
119
src/NadekoBot/Services/GrpcApiPermsInterceptor.cs
Normal file
119
src/NadekoBot/Services/GrpcApiPermsInterceptor.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using Grpc.Core;
|
||||
using Grpc.Core.Interceptors;
|
||||
|
||||
namespace NadekoBot.GrpcApi;
|
||||
|
||||
public sealed partial class GrpcApiPermsInterceptor : Interceptor
|
||||
{
|
||||
private const GuildPerm DEFAULT_PERMISSION = GuildPermission.Administrator;
|
||||
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public GrpcApiPermsInterceptor(DiscordSocketClient client)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
|
||||
private async Task RequestHandler(ServerCallContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var method = context.Method[(context.Method.LastIndexOf('/') + 1)..];
|
||||
|
||||
// get metadata
|
||||
var metadata = context
|
||||
.RequestHeaders
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
Log.Information("grpc | g: {GuildId} | u: {UserID} | cmd: {Method}",
|
||||
metadata.TryGetValue("guildid", out var gidString) ? gidString : "none",
|
||||
metadata.TryGetValue("userid", out var uidString) ? uidString : "none",
|
||||
method);
|
||||
|
||||
|
||||
// there always has to be a user who makes the call
|
||||
if (!metadata.ContainsKey("userid"))
|
||||
throw new RpcException(new(StatusCode.Unauthenticated, "userid has to be specified."));
|
||||
|
||||
// get the method name without the service name
|
||||
|
||||
// if the method is explicitly marked as not requiring auth
|
||||
if (_noAuthRequired.Contains(method))
|
||||
return;
|
||||
|
||||
// otherwise the method requires auth, and if it requires auth then the guildid has to be specified
|
||||
if (string.IsNullOrWhiteSpace(gidString))
|
||||
throw new RpcException(new(StatusCode.Unauthenticated, "guildid has to be specified."));
|
||||
|
||||
var userId = ulong.Parse(metadata["userid"]);
|
||||
var guildId = ulong.Parse(gidString);
|
||||
|
||||
// check if the user has the required permission
|
||||
if (_perms.TryGetValue(method, out var perm))
|
||||
{
|
||||
await EnsureUserHasPermission(guildId, userId, perm);
|
||||
}
|
||||
else
|
||||
{
|
||||
// if not then use the default, which is Administrator permission
|
||||
await EnsureUserHasPermission(guildId, userId, DEFAULT_PERMISSION);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error thrown by {ContextMethod}", context.Method);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task EnsureUserHasPermission(ulong guildId, ulong userId, GuildPerm perm)
|
||||
{
|
||||
IGuild guild = _client.GetGuild(guildId);
|
||||
var user = guild is null ? null : await guild.GetUserAsync(userId);
|
||||
|
||||
if (user is null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "User not found"));
|
||||
|
||||
if (!user.GuildPermissions.Has(perm))
|
||||
throw new RpcException(new Status(StatusCode.PermissionDenied,
|
||||
$"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,22 +9,16 @@ public class GrpcApiService : INService, IReadyExecutor
|
||||
private Server? _app;
|
||||
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly OtherSvc _other;
|
||||
private readonly ExprsSvc _exprs;
|
||||
private readonly GreetByeSvc _greet;
|
||||
private readonly IEnumerable<IGrpcSvc> _svcs;
|
||||
private readonly IBotCredsProvider _creds;
|
||||
|
||||
public GrpcApiService(
|
||||
DiscordSocketClient client,
|
||||
OtherSvc other,
|
||||
ExprsSvc exprs,
|
||||
GreetByeSvc greet,
|
||||
IEnumerable<IGrpcSvc> svcs,
|
||||
IBotCredsProvider creds)
|
||||
{
|
||||
_client = client;
|
||||
_other = other;
|
||||
_exprs = exprs;
|
||||
_greet = greet;
|
||||
_svcs = svcs;
|
||||
_creds = creds;
|
||||
}
|
||||
|
||||
@@ -39,28 +33,40 @@ public class GrpcApiService : INService, IReadyExecutor
|
||||
var host = creds.GrpcApi.Host;
|
||||
var port = creds.GrpcApi.Port + _client.ShardId;
|
||||
|
||||
var interceptor = new PermsInterceptor(_client);
|
||||
var interceptor = new GrpcApiPermsInterceptor(_client);
|
||||
|
||||
_app = new Server()
|
||||
{
|
||||
Services =
|
||||
var serverCreds = ServerCredentials.Insecure;
|
||||
|
||||
if (creds.GrpcApi is
|
||||
{
|
||||
GrpcOther.BindService(_other).Intercept(interceptor),
|
||||
GrpcExprs.BindService(_exprs).Intercept(interceptor),
|
||||
GrpcGreet.BindService(_greet).Intercept(interceptor),
|
||||
},
|
||||
CertPrivateKey: not null and not "",
|
||||
CertChain: not null and not ""
|
||||
} cert)
|
||||
{
|
||||
serverCreds = new SslServerCredentials(
|
||||
new[] { new KeyCertificatePair(cert.CertChain, cert.CertPrivateKey) });
|
||||
}
|
||||
|
||||
_app = new()
|
||||
{
|
||||
Ports =
|
||||
{
|
||||
new(host, port, ServerCredentials.Insecure),
|
||||
new(host, port, serverCreds),
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var svc in _svcs)
|
||||
{
|
||||
_app.Services.Add(svc.Bind().Intercept(interceptor));
|
||||
}
|
||||
|
||||
_app.Start();
|
||||
|
||||
Log.Information("Grpc Api Server started on port {Host}:{Port}", host, port);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error starting Grpc Api Server");
|
||||
_app?.ShutdownAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
|
8
src/NadekoBot/Services/IGrpcSvc.cs
Normal file
8
src/NadekoBot/Services/IGrpcSvc.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Grpc.Core;
|
||||
|
||||
namespace NadekoBot.GrpcApi;
|
||||
|
||||
public interface IGrpcSvc
|
||||
{
|
||||
ServerServiceDefinition Bind();
|
||||
}
|
@@ -1,67 +0,0 @@
|
||||
using Grpc.Core;
|
||||
using Grpc.Core.Interceptors;
|
||||
|
||||
namespace NadekoBot.GrpcApi;
|
||||
|
||||
public sealed partial class PermsInterceptor : Interceptor
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public PermsInterceptor(DiscordSocketClient client)
|
||||
{
|
||||
_client = client;
|
||||
Log.Information("interceptor created");
|
||||
}
|
||||
|
||||
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
|
||||
TRequest request,
|
||||
ServerCallContext context,
|
||||
UnaryServerMethod<TRequest, TResponse> continuation)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Information("Starting receiving call. Type/Method: {Type} / {Method}",
|
||||
MethodType.Unary,
|
||||
context.Method);
|
||||
|
||||
// get metadata
|
||||
var metadata = context
|
||||
.RequestHeaders
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
|
||||
var method = context.Method[(context.Method.LastIndexOf('/') + 1)..];
|
||||
|
||||
if (perms.TryGetValue(method, out var perm))
|
||||
{
|
||||
Log.Information("Required permission for {Method} is {Perm}",
|
||||
method,
|
||||
perm);
|
||||
|
||||
var userId = ulong.Parse(metadata["userid"]);
|
||||
var guildId = ulong.Parse(metadata["guildid"]);
|
||||
|
||||
IGuild guild = _client.GetGuild(guildId);
|
||||
var user = guild is null ? null : await guild.GetUserAsync(userId);
|
||||
|
||||
if (user is null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "User not found"));
|
||||
|
||||
if (!user.GuildPermissions.Has(perm))
|
||||
throw new RpcException(new Status(StatusCode.PermissionDenied,
|
||||
$"You need {perm} permission to use this method"));
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Information("No permission required for {Method}", method);
|
||||
}
|
||||
|
||||
return await continuation(request, context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error thrown by {ContextMethod}", context.Method);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
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()
|
||||
{
|
||||
if (_value == 0)
|
||||
return VALID_CHARACTERS[0].ToString();
|
||||
|
||||
var count = VALID_CHARACTERS.Length;
|
||||
var localValue = _value;
|
||||
var arrSize = (int)Math.Log(localValue, count) + 1;
|
||||
|
@@ -6,7 +6,7 @@ namespace NadekoBot.Common;
|
||||
public sealed class Creds : IBotCreds
|
||||
{
|
||||
[Comment("""DO NOT CHANGE""")]
|
||||
public int Version { get; set; } = 12;
|
||||
public int Version { get; set; } = 13;
|
||||
|
||||
[Comment("""Bot token. Do not share with anyone ever -> https://discordapp.com/developers/applications/""")]
|
||||
public string Token { get; set; }
|
||||
@@ -292,8 +292,8 @@ public sealed class Creds : IBotCreds
|
||||
public sealed record GrpcApiConfig
|
||||
{
|
||||
public bool Enabled { get; set; } = false;
|
||||
public string CertPath { get; set; } = string.Empty;
|
||||
public string CertPassword { get; set; } = string.Empty;
|
||||
public string CertChain { get; set; } = string.Empty;
|
||||
public string CertPrivateKey { get; set; } = string.Empty;
|
||||
public string Host { get; set; } = "localhost";
|
||||
public int Port { get; set; } = 43120;
|
||||
}
|
||||
|
@@ -40,4 +40,11 @@ public interface ICurrencyService
|
||||
TxData? txData);
|
||||
|
||||
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
|
||||
{
|
||||
Task TrackAdd(long amount, TxData? txData);
|
||||
Task TrackRemove(long amount, TxData? txData);
|
||||
Task TrackAdd(ulong userId, long amount, TxData? txData);
|
||||
Task TrackRemove(ulong userId, long amount, TxData? txData);
|
||||
}
|
@@ -140,9 +140,9 @@ public sealed class BotCredsProvider : IBotCredsProvider
|
||||
creds.BotCache = BotCacheImplemenation.Memory;
|
||||
}
|
||||
|
||||
if (creds.Version < 12)
|
||||
if (creds.Version < 13)
|
||||
{
|
||||
creds.Version = 12;
|
||||
creds.Version = 13;
|
||||
File.WriteAllText(CREDS_FILE_NAME, Yaml.Serializer.Serialize(creds));
|
||||
}
|
||||
}
|
||||
|
@@ -201,9 +201,12 @@ public sealed partial class GoogleApiService : IGoogleApiService, INService
|
||||
{
|
||||
string text;
|
||||
|
||||
if (!Languages.ContainsKey(sourceLanguage) || !Languages.ContainsKey(targetLanguage))
|
||||
if (!Languages.ContainsKey(targetLanguage))
|
||||
throw new ArgumentException(nameof(sourceLanguage) + "/" + nameof(targetLanguage));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(sourceLanguage) || !Languages.ContainsKey(sourceLanguage))
|
||||
sourceLanguage = "auto";
|
||||
|
||||
|
||||
var url = new Uri(string.Format(
|
||||
"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)
|
||||
{
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user