From 3c108e531e1d46f072dfd6442c30df62f2d591cd Mon Sep 17 00:00:00 2001 From: Kwoth Date: Thu, 26 Sep 2024 07:26:18 +0000 Subject: [PATCH] dev: Added initial version of the grpc api. Added relevant dummy settings to creds (they have no effect rn) dev: Yt searches now INTERNALLY return multiple results but there is no way right now to paginate plain text results dev: moved some stuff around --- NadekoBot.sln | 9 + .../NadekoBot.GrpcApiBase.csproj | 21 ++ src/NadekoBot.GrpcApiBase/protos/econ.proto | 26 +++ src/NadekoBot.GrpcApiBase/protos/exprs.proto | 50 +++++ src/NadekoBot.GrpcApiBase/protos/greet.proto | 57 +++++ src/NadekoBot.GrpcApiBase/protos/info.proto | 52 +++++ src/NadekoBot.GrpcApiBase/protos/other.proto | 81 +++++++ src/NadekoBot.GrpcApiBase/protos/warn.proto | 83 +++++++ .../Expressions/NadekoExpressionsService.cs | 2 +- .../Gambling/Waifus/WaifuClaimCommands.cs | 2 +- .../Modules/Gambling/Waifus/WaifuService.cs | 6 +- .../Gambling/Waifus/db/WaifuExtensions.cs | 38 ++-- .../_common/Resolvers/YtdlYoutubeResolver.cs | 10 +- .../Search/DefaultSearchServiceFactory.cs | 14 +- .../Modules/Searches/Search/SearchCommands.cs | 41 ++-- .../Search/Youtube/IYoutubeSearchService.cs | 2 +- .../Youtube/InvidiousYtSearchService.cs | 5 +- .../Youtube/YoutubeDataApiSearchService.cs | 13 +- .../Search/Youtube/YtDlpSearchService.cs | 26 +++ .../Youtube/YtdlYoutubeSearchService.cs | 7 - .../Youtube/YtdlpYoutubeSearchService.cs | 7 - .../Search/Youtube/YtdlxServiceBase.cs | 34 --- .../Searches/_common/Config/SearchesConfig.cs | 8 +- src/NadekoBot/NadekoBot.csproj | 37 ++- src/NadekoBot/Services/GrpcApi/ExprsSvc.cs | 73 ++++++ src/NadekoBot/Services/GrpcApi/GreetByeSvc.cs | 121 ++++++++++ src/NadekoBot/Services/GrpcApi/OtherSvc.cs | 123 ++++++++++ .../Services/GrpcApi/ServerInfoSvc.cs | 49 ++++ src/NadekoBot/Services/GrpcApiService.cs | 63 ++++++ src/NadekoBot/_common/Creds.cs | 210 ++++++++++-------- .../Impl/BotCredsProvider.cs | 10 +- .../Impl/GoogleApiService.cs | 4 +- .../GoogleApiService_SupportedLanguages.cs | 0 .../{Services => _common}/Impl/ImageCache.cs | 0 .../Impl/LocalDataCache.cs | 0 .../Impl/Localization.cs | 0 .../Impl/PubSub/JsonSeria.cs | 0 .../Impl/PubSub/RedisPubSub.cs | 0 .../Impl/PubSub/YamlSeria.cs | 0 .../Impl/RedisBotCache.cs | 0 .../Impl/RedisBotStringsProvider.cs | 0 .../Impl/RemoteGrpcCoordinator.cs | 0 .../_common/Services/IGoogleApiService.cs | 2 +- .../_common/Services/Impl/YtdlOperation.cs | 11 +- src/NadekoBot/data/medusae/medusa.yml | 3 +- 45 files changed, 1039 insertions(+), 261 deletions(-) create mode 100644 src/NadekoBot.GrpcApiBase/NadekoBot.GrpcApiBase.csproj create mode 100644 src/NadekoBot.GrpcApiBase/protos/econ.proto create mode 100644 src/NadekoBot.GrpcApiBase/protos/exprs.proto create mode 100644 src/NadekoBot.GrpcApiBase/protos/greet.proto create mode 100644 src/NadekoBot.GrpcApiBase/protos/info.proto create mode 100644 src/NadekoBot.GrpcApiBase/protos/other.proto create mode 100644 src/NadekoBot.GrpcApiBase/protos/warn.proto create mode 100644 src/NadekoBot/Modules/Searches/Search/Youtube/YtDlpSearchService.cs delete mode 100644 src/NadekoBot/Modules/Searches/Search/Youtube/YtdlYoutubeSearchService.cs delete mode 100644 src/NadekoBot/Modules/Searches/Search/Youtube/YtdlpYoutubeSearchService.cs delete mode 100644 src/NadekoBot/Modules/Searches/Search/Youtube/YtdlxServiceBase.cs create mode 100644 src/NadekoBot/Services/GrpcApi/ExprsSvc.cs create mode 100644 src/NadekoBot/Services/GrpcApi/GreetByeSvc.cs create mode 100644 src/NadekoBot/Services/GrpcApi/OtherSvc.cs create mode 100644 src/NadekoBot/Services/GrpcApi/ServerInfoSvc.cs create mode 100644 src/NadekoBot/Services/GrpcApiService.cs rename src/NadekoBot/{Services => _common}/Impl/BotCredsProvider.cs (94%) rename src/NadekoBot/{Services => _common}/Impl/GoogleApiService.cs (97%) rename src/NadekoBot/{Services => _common}/Impl/GoogleApiService_SupportedLanguages.cs (100%) rename src/NadekoBot/{Services => _common}/Impl/ImageCache.cs (100%) rename src/NadekoBot/{Services => _common}/Impl/LocalDataCache.cs (100%) rename src/NadekoBot/{Services => _common}/Impl/Localization.cs (100%) rename src/NadekoBot/{Services => _common}/Impl/PubSub/JsonSeria.cs (100%) rename src/NadekoBot/{Services => _common}/Impl/PubSub/RedisPubSub.cs (100%) rename src/NadekoBot/{Services => _common}/Impl/PubSub/YamlSeria.cs (100%) rename src/NadekoBot/{Services => _common}/Impl/RedisBotCache.cs (100%) rename src/NadekoBot/{Services => _common}/Impl/RedisBotStringsProvider.cs (100%) rename src/NadekoBot/{Services => _common}/Impl/RemoteGrpcCoordinator.cs (100%) diff --git a/NadekoBot.sln b/NadekoBot.sln index 43cfa0936..36831a32a 100644 --- a/NadekoBot.sln +++ b/NadekoBot.sln @@ -30,6 +30,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nadeko.Medusa", "src\Nadeko EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Generators", "src\NadekoBot.Generators\NadekoBot.Generators.csproj", "{92770AF3-83EE-49F1-A0BB-79124D19A13D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.GrpcApiBase", "src\NadekoBot.GrpcApiBase\NadekoBot.GrpcApiBase.csproj", "{FB74B9EA-10B9-4542-ACB1-35523A95A587}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -79,6 +81,12 @@ Global {92770AF3-83EE-49F1-A0BB-79124D19A13D}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU {92770AF3-83EE-49F1-A0BB-79124D19A13D}.Release|Any CPU.ActiveCfg = Release|Any CPU {92770AF3-83EE-49F1-A0BB-79124D19A13D}.Release|Any CPU.Build.0 = Release|Any CPU + {FB74B9EA-10B9-4542-ACB1-35523A95A587}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FB74B9EA-10B9-4542-ACB1-35523A95A587}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FB74B9EA-10B9-4542-ACB1-35523A95A587}.GlobalNadeko|Any CPU.ActiveCfg = Debug|Any CPU + {FB74B9EA-10B9-4542-ACB1-35523A95A587}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU + {FB74B9EA-10B9-4542-ACB1-35523A95A587}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FB74B9EA-10B9-4542-ACB1-35523A95A587}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -91,6 +99,7 @@ Global {E685977E-31A4-46F4-A5D7-4E3E39E82E43} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2} {92770AF3-83EE-49F1-A0BB-79124D19A13D} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2} {2F4CF6D6-0C2F-4944-B204-9508CDA53195} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2} + {FB74B9EA-10B9-4542-ACB1-35523A95A587} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5F3F555C-855F-4BE8-B526-D062D3E8ACA4} diff --git a/src/NadekoBot.GrpcApiBase/NadekoBot.GrpcApiBase.csproj b/src/NadekoBot.GrpcApiBase/NadekoBot.GrpcApiBase.csproj new file mode 100644 index 000000000..5afe1d8c6 --- /dev/null +++ b/src/NadekoBot.GrpcApiBase/NadekoBot.GrpcApiBase.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + Server + + + + diff --git a/src/NadekoBot.GrpcApiBase/protos/econ.proto b/src/NadekoBot.GrpcApiBase/protos/econ.proto new file mode 100644 index 000000000..e31c9c2b7 --- /dev/null +++ b/src/NadekoBot.GrpcApiBase/protos/econ.proto @@ -0,0 +1,26 @@ +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; +} diff --git a/src/NadekoBot.GrpcApiBase/protos/exprs.proto b/src/NadekoBot.GrpcApiBase/protos/exprs.proto new file mode 100644 index 000000000..3e265e677 --- /dev/null +++ b/src/NadekoBot.GrpcApiBase/protos/exprs.proto @@ -0,0 +1,50 @@ +syntax = "proto3"; + +option csharp_namespace = "NadekoBot.GrpcApi"; + +import "google/protobuf/empty.proto"; + +package exprs; + +service GrpcExprs { + rpc GetExprs(GetExprsRequest) returns (GetExprsReply); + rpc AddExpr(AddExprRequest) returns (AddExprReply); + rpc DeleteExpr(DeleteExprRequest) returns (google.protobuf.Empty); +} + +message DeleteExprRequest { + string id = 1; + uint64 guildId = 2; +} + +message GetExprsRequest { + uint64 guildId = 1; + string query = 2; + int32 page = 3; +} + +message GetExprsReply { + repeated ExprDto expressions = 1; + int32 totalCount = 2; +} + +message ExprDto { + string id = 1; + string trigger = 2; + string response = 3; + + bool ca = 4; + bool ad = 5; + bool dm = 6; + bool at = 7; +} + +message AddExprRequest { + uint64 guildId = 1; + ExprDto expr = 2; +} + +message AddExprReply { + string id = 1; + bool success = 2; +} \ No newline at end of file diff --git a/src/NadekoBot.GrpcApiBase/protos/greet.proto b/src/NadekoBot.GrpcApiBase/protos/greet.proto new file mode 100644 index 000000000..7aab3d5a3 --- /dev/null +++ b/src/NadekoBot.GrpcApiBase/protos/greet.proto @@ -0,0 +1,57 @@ +syntax = "proto3"; + +option csharp_namespace = "NadekoBot.GrpcApi"; + +package greet; + +service GrpcGreet { + rpc GetGreetSettings (GetGreetRequest) returns (GetGreetReply); + 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 message = 2; + bool isEnabled = 3; + GrpcGreetType type = 4; +} + +message GetGreetRequest { + uint64 guildId = 1; +} + +message UpdateGreetRequest { + uint64 guildId = 1; + GrpcGreetSettings settings = 2; +} + +enum GrpcGreetType { + Greet = 0; + GreetDm = 1; + Bye = 2; + Boost = 3; +} + +message UpdateGreetReply { + bool success = 1; +} + +message TestGreetRequest { + uint64 guildId = 1; + uint64 channelId = 2; + uint64 userId = 3; + GrpcGreetType type = 4; +} + +message TestGreetReply { + bool success = 1; + string error = 2; +} \ No newline at end of file diff --git a/src/NadekoBot.GrpcApiBase/protos/info.proto b/src/NadekoBot.GrpcApiBase/protos/info.proto new file mode 100644 index 000000000..2e2a73f46 --- /dev/null +++ b/src/NadekoBot.GrpcApiBase/protos/info.proto @@ -0,0 +1,52 @@ +syntax = "proto3"; + +option csharp_namespace = "NadekoBot.GrpcApi"; + +package info; + +service GrpcInfo { + rpc GetServerInfo(ServerInfoRequest) returns (GetServerInfoReply); +} + +message ServerInfoRequest { + uint64 guildId = 1; +} + +message GetServerInfoReply { + uint64 id = 1; + string name = 2; + string iconUrl = 3; + uint64 ownerId = 4; + string ownerName = 5; + repeated RoleReply roles = 6; + repeated EmojiReply emojis = 7; + repeated string features = 8; + int32 textChannels = 9; + int32 voiceChannels = 10; + int32 memberCount = 11; + int64 createdAt = 12; +} + +message RoleReply { + uint64 id = 1; + string name = 2; + string iconUrl = 3; + string color = 4; +} + +message EmojiReply { + string name = 1; + string url = 2; + string code = 3; +} + +message ChannelReply { + uint64 id = 1; + string name = 2; + ChannelType type = 3; +} + +enum ChannelType { + Text = 0; + Voice = 1; +} \ No newline at end of file diff --git a/src/NadekoBot.GrpcApiBase/protos/other.proto b/src/NadekoBot.GrpcApiBase/protos/other.proto new file mode 100644 index 000000000..fa992a50c --- /dev/null +++ b/src/NadekoBot.GrpcApiBase/protos/other.proto @@ -0,0 +1,81 @@ +syntax = "proto3"; + +option csharp_namespace = "NadekoBot.GrpcApi"; + +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; + +package other; + +service GrpcOther { + rpc GetTextChannels(GetTextChannelsRequest) returns (GetTextChannelsReply); + + rpc GetCurrencyLb(GetLbRequest) returns (CurrencyLbReply); + rpc GetXpLb(GetLbRequest) returns (XpLbReply); + rpc GetWaifuLb(GetLbRequest) returns (WaifuLbReply); + + rpc GetShardStatuses(google.protobuf.Empty) returns (GetShardStatusesReply); +} + +message GetShardStatusesReply { + repeated ShardStatusReply shards = 1; +} + +message ShardStatusReply { + int32 id = 1; + string status = 2; + + int32 guildCount = 3; + google.protobuf.Timestamp lastUpdate = 4; +} + +message GetTextChannelsRequest{ + uint64 guildId = 1; +} + +message GetTextChannelsReply { + repeated TextChannelReply textChannels = 1; +} + +message TextChannelReply { + uint64 id = 1; + string name = 2; +} + +message CurrencyLbReply { + repeated CurrencyLbEntryReply entries = 1; +} + +message CurrencyLbEntryReply { + string user = 1; + uint64 userId = 2; + int64 amount = 3; + string avatar = 4; +} + +message GetLbRequest { + int32 page = 1; + int32 perPage = 2; +} + +message XpLbReply { + repeated XpLbEntryReply entries = 1; +} + +message XpLbEntryReply { + string user = 1; + uint64 userId = 2; + int64 totalXp = 3; + int64 level = 4; +} + +message WaifuLbReply { + repeated WaifuLbEntry entries = 1; +} + +message WaifuLbEntry { + string user = 1; + string claimedBy = 2; + int64 value = 3; + bool isMutual = 4; +} \ No newline at end of file diff --git a/src/NadekoBot.GrpcApiBase/protos/warn.proto b/src/NadekoBot.GrpcApiBase/protos/warn.proto new file mode 100644 index 000000000..ee3b62f3e --- /dev/null +++ b/src/NadekoBot.GrpcApiBase/protos/warn.proto @@ -0,0 +1,83 @@ +syntax = "proto3"; + +option csharp_namespace = "NadekoBot.GrpcApi"; + +package warn; + +service GrpcWarn { + rpc GetWarnSettings (WarnSettingsRequest) returns (WarnSettingsReply); + rpc AddWarnp (AddWarnpRequest) returns (AddWarnpReply); + rpc DeleteWarnp (DeleteWarnpRequest) returns (DeleteWarnpReply); + rpc GetUserWarnings(GetUserWarningsRequest) returns (GetUserWarningsReply); + rpc ClearWarning(ClearWarningRequest) returns (ClearWarningReply); + rpc SetWarnExpiry(SetWarnExpiryRequest) returns (SetWarnExpiryReply); +} +message WarnSettingsRequest { + uint64 guildId = 1; +} + +message WarnPunishment { + int32 threshold = 1; + string action = 2; + int64 duration = 3; +} + +message WarnSettingsReply { + repeated WarnPunishment punishments = 1; + int32 expiryDays = 2; +} + +message AddWarnpRequest { + uint64 guildId = 1; + WarnPunishment punishment = 2; +} + +message AddWarnpReply { + bool success = 1; +} + +message DeleteWarnpRequest { + uint64 guildId = 1; + int32 warnpIndex = 2; +} + +message DeleteWarnpReply { + bool success = 1; +} + +message GetUserWarningsRequest { + uint64 guildId = 1; + uint64 user_id = 2; +} + +message GetUserWarningsReply { + repeated Warning warnings = 1; +} + +message Warning { + int32 id = 1; + string reason = 2; + int64 timestamp = 3; + int64 expiry_timestamp = 4; + bool cleared = 5; + string clearedBy = 6; +} + +message ClearWarningRequest { + uint64 guildId = 1; + uint64 userId = 2; + optional int32 warnId = 3; +} + +message ClearWarningReply { + bool success = 1; +} + +message SetWarnExpiryRequest { + uint64 guildId = 1; + int32 expiryDays = 2; +} + +message SetWarnExpiryReply { + bool success = 1; +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Expressions/NadekoExpressionsService.cs b/src/NadekoBot/Modules/Expressions/NadekoExpressionsService.cs index 2c4fdf64d..04055722a 100644 --- a/src/NadekoBot/Modules/Expressions/NadekoExpressionsService.cs +++ b/src/NadekoBot/Modules/Expressions/NadekoExpressionsService.cs @@ -789,7 +789,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor if (newguildExpressions.TryGetValue(guildId, out var exprs)) { - return (exprs.Where(x => x.Trigger.Contains(query)) + return (exprs.Where(x => x.Trigger.Contains(query) || x.Response.Contains(query)) .Skip(page * 9) .Take(9) .ToArray(), exprs.Length); diff --git a/src/NadekoBot/Modules/Gambling/Waifus/WaifuClaimCommands.cs b/src/NadekoBot/Modules/Gambling/Waifus/WaifuClaimCommands.cs index 877ba0f42..7dcf38a97 100644 --- a/src/NadekoBot/Modules/Gambling/Waifus/WaifuClaimCommands.cs +++ b/src/NadekoBot/Modules/Gambling/Waifus/WaifuClaimCommands.cs @@ -227,7 +227,7 @@ public partial class Gambling if (page > 100) page = 100; - var waifus = _service.GetTopWaifusAtPage(page).ToList(); + var waifus = await _service.GetTopWaifusAtPage(page); if (waifus.Count == 0) { diff --git a/src/NadekoBot/Modules/Gambling/Waifus/WaifuService.cs b/src/NadekoBot/Modules/Gambling/Waifus/WaifuService.cs index 56feeb5d0..dc11e32f9 100644 --- a/src/NadekoBot/Modules/Gambling/Waifus/WaifuService.cs +++ b/src/NadekoBot/Modules/Gambling/Waifus/WaifuService.cs @@ -300,10 +300,10 @@ public class WaifuService : INService, IReadyExecutor return (oldAff, success, remaining); } - public IEnumerable GetTopWaifusAtPage(int page, int perPage = 9) + public async Task> GetTopWaifusAtPage(int page, int perPage = 9) { - using var uow = _db.GetDbContext(); - return uow.Set().GetTop(perPage, page * perPage); + await using var uow = _db.GetDbContext(); + return await uow.Set().GetTop(perPage, page * perPage); } public ulong GetWaifuUserId(ulong ownerId, string name) diff --git a/src/NadekoBot/Modules/Gambling/Waifus/db/WaifuExtensions.cs b/src/NadekoBot/Modules/Gambling/Waifus/db/WaifuExtensions.cs index d9e99ab2e..0a9691f27 100644 --- a/src/NadekoBot/Modules/Gambling/Waifus/db/WaifuExtensions.cs +++ b/src/NadekoBot/Modules/Gambling/Waifus/db/WaifuExtensions.cs @@ -25,30 +25,30 @@ public static class WaifuExtensions return includes(waifus).AsQueryable().FirstOrDefault(wi => wi.Waifu.UserId == userId); } - public static IEnumerable GetTop(this DbSet waifus, int count, int skip = 0) + public static async Task> GetTop(this DbSet waifus, int count, int skip = 0) { ArgumentOutOfRangeException.ThrowIfNegative(count); if (count == 0) return []; - return waifus.Include(wi => wi.Waifu) - .Include(wi => wi.Affinity) - .Include(wi => wi.Claimer) - .OrderByDescending(wi => wi.Price) - .Skip(skip) - .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, - Price = x.Price - }) - .ToList(); + return await waifus.Include(wi => wi.Waifu) + .Include(wi => wi.Affinity) + .Include(wi => wi.Claimer) + .OrderByDescending(wi => wi.Price) + .Skip(skip) + .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, + Price = x.Price + }) + .ToListAsyncEF(); } public static decimal GetTotalValue(this DbSet waifus) @@ -64,7 +64,7 @@ public static class WaifuExtensions public static async Task GetWaifuInfoAsync(this DbContext ctx, ulong userId) { await ctx.EnsureUserCreatedAsync(userId); - + await ctx.Set() .ToLinqToDBTable() .InsertOrUpdateAsync(() => new() diff --git a/src/NadekoBot/Modules/Music/_common/Resolvers/YtdlYoutubeResolver.cs b/src/NadekoBot/Modules/Music/_common/Resolvers/YtdlYoutubeResolver.cs index d7975088a..7bb21d45e 100644 --- a/src/NadekoBot/Modules/Music/_common/Resolvers/YtdlYoutubeResolver.cs +++ b/src/NadekoBot/Modules/Music/_common/Resolvers/YtdlYoutubeResolver.cs @@ -43,8 +43,7 @@ public sealed class YtdlYoutubeResolver : IYoutubeResolver + "--no-check-certificate " + "-i " + "--yes-playlist " - + "-- \"{0}\"", - scs.Data.YtProvider != YoutubeSearcher.Ytdl); + + "-- \"{0}\""); _ytdlIdOperation = new("-4 " + "--geo-bypass " @@ -56,8 +55,7 @@ public sealed class YtdlYoutubeResolver : IYoutubeResolver + "--get-thumbnail " + "--get-duration " + "--no-check-certificate " - + "-- \"{0}\"", - scs.Data.YtProvider != YoutubeSearcher.Ytdl); + + "-- \"{0}\""); _ytdlSearchOperation = new("-4 " + "--geo-bypass " @@ -70,8 +68,7 @@ public sealed class YtdlYoutubeResolver : IYoutubeResolver + "--get-duration " + "--no-check-certificate " + "--default-search " - + "\"ytsearch:\" -- \"{0}\"", - scs.Data.YtProvider != YoutubeSearcher.Ytdl); + + "\"ytsearch:\" -- \"{0}\""); } private YtTrackData ResolveYtdlData(string ytdlOutputString) @@ -291,6 +288,7 @@ public sealed class YtdlYoutubeResolver : IYoutubeResolver public Task GetStreamUrl(string videoId) => CreateCacherFactory(videoId)(); + private readonly struct YtTrackData { public readonly string Title; diff --git a/src/NadekoBot/Modules/Searches/Search/DefaultSearchServiceFactory.cs b/src/NadekoBot/Modules/Searches/Search/DefaultSearchServiceFactory.cs index 6f31dcc8e..886c6af56 100644 --- a/src/NadekoBot/Modules/Searches/Search/DefaultSearchServiceFactory.cs +++ b/src/NadekoBot/Modules/Searches/Search/DefaultSearchServiceFactory.cs @@ -7,10 +7,9 @@ public sealed class DefaultSearchServiceFactory : ISearchServiceFactory, INServi { private readonly SearchesConfigService _scs; private readonly SearxSearchService _sss; + private readonly YtDlpSearchService _ytdlp; private readonly GoogleSearchService _gss; - private readonly YtdlpYoutubeSearchService _ytdlp; - private readonly YtdlYoutubeSearchService _ytdl; private readonly YoutubeDataApiSearchService _ytdata; private readonly InvidiousYtSearchService _iYtSs; private readonly GoogleScrapeService _gscs; @@ -20,19 +19,17 @@ public sealed class DefaultSearchServiceFactory : ISearchServiceFactory, INServi GoogleSearchService gss, GoogleScrapeService gscs, SearxSearchService sss, - YtdlpYoutubeSearchService ytdlp, - YtdlYoutubeSearchService ytdl, + YtDlpSearchService ytdlp, YoutubeDataApiSearchService ytdata, InvidiousYtSearchService iYtSs) { _scs = scs; _sss = sss; + _ytdlp = ytdlp; _gss = gss; _gscs = gscs; _iYtSs = iYtSs; - _ytdlp = ytdlp; - _ytdl = ytdl; _ytdata = ytdata; } @@ -57,9 +54,8 @@ public sealed class DefaultSearchServiceFactory : ISearchServiceFactory, INServi => _scs.Data.YtProvider switch { YoutubeSearcher.YtDataApiv3 => _ytdata, - YoutubeSearcher.Ytdlp => _ytdlp, - YoutubeSearcher.Ytdl => _ytdl, YoutubeSearcher.Invidious => _iYtSs, - _ => _ytdl + YoutubeSearcher.Ytdlp => _ytdlp, + _ => throw new ArgumentOutOfRangeException() }; } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Search/SearchCommands.cs b/src/NadekoBot/Modules/Searches/Search/SearchCommands.cs index 7c3ea34a6..77a803303 100644 --- a/src/NadekoBot/Modules/Searches/Search/SearchCommands.cs +++ b/src/NadekoBot/Modules/Searches/Search/SearchCommands.cs @@ -93,16 +93,12 @@ public partial class Searches return; } - var embeds = new List(4); - - EmbedBuilder CreateEmbed(IImageSearchResultEntry entry) { return _sender.CreateEmbed() .WithOkColor() .WithAuthor(ctx.User) .WithTitle(query) - .WithUrl("https://google.com") .WithImageUrl(entry.Link); } @@ -120,55 +116,50 @@ public partial class Searches .WithDescription(GetText(strs.no_search_results)); var embed = CreateEmbed(item); - embeds.Add(embed); return embed; }) .SendAsync(); } - private TypedKey GetYtCacheKey(string query) - => new($"search:youtube:{query}"); + private TypedKey GetYtCacheKey(string query) + => new($"search:yt:{query}"); - private async Task AddYoutubeUrlToCacheAsync(string query, string url) + private async Task AddYoutubeUrlToCacheAsync(string query, string[] url) => await _cache.AddAsync(GetYtCacheKey(query), url, expiry: 1.Hours()); - private async Task GetYoutubeUrlFromCacheAsync(string query) + private async Task GetYoutubeUrlFromCacheAsync(string query) { var result = await _cache.GetAsync(GetYtCacheKey(query)); - if (!result.TryGetValue(out var url) || string.IsNullOrWhiteSpace(url)) + if (!result.TryGetValue(out var urls) || urls.Length == 0) return null; - return new VideoInfo() + return urls.Map(url => new VideoInfo() { Url = url - }; + }); } [Cmd] - public async Task Youtube([Leftover] string? query = null) + public async Task Youtube([Leftover] string query) { - query = query?.Trim(); - - if (string.IsNullOrWhiteSpace(query)) - { - await Response().Error(strs.specify_search_params).SendAsync(); - return; - } + query = query.Trim(); _ = ctx.Channel.TriggerTypingAsync(); - var maybeResult = await GetYoutubeUrlFromCacheAsync(query) - ?? await _searchFactory.GetYoutubeSearchService().SearchAsync(query); - if (maybeResult is not { } result || result is { Url: null }) + var maybeResults = await GetYoutubeUrlFromCacheAsync(query) + ?? await _searchFactory.GetYoutubeSearchService().SearchAsync(query); + + if (maybeResults is not { } result || result.Length == 0) { await Response().Error(strs.no_results).SendAsync(); return; } - await AddYoutubeUrlToCacheAsync(query, result.Url); - await Response().Text(result.Url).SendAsync(); + await AddYoutubeUrlToCacheAsync(query, result.Map(x => x.Url)); + + await Response().Text(result[0].Url).SendAsync(); } // [Cmd] diff --git a/src/NadekoBot/Modules/Searches/Search/Youtube/IYoutubeSearchService.cs b/src/NadekoBot/Modules/Searches/Search/Youtube/IYoutubeSearchService.cs index b69b5fd91..439acfec2 100644 --- a/src/NadekoBot/Modules/Searches/Search/Youtube/IYoutubeSearchService.cs +++ b/src/NadekoBot/Modules/Searches/Search/Youtube/IYoutubeSearchService.cs @@ -2,5 +2,5 @@ public interface IYoutubeSearchService { - Task SearchAsync(string query); + Task SearchAsync(string query); } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Search/Youtube/InvidiousYtSearchService.cs b/src/NadekoBot/Modules/Searches/Search/Youtube/InvidiousYtSearchService.cs index a0c1dc35a..aabc8c60d 100644 --- a/src/NadekoBot/Modules/Searches/Search/Youtube/InvidiousYtSearchService.cs +++ b/src/NadekoBot/Modules/Searches/Search/Youtube/InvidiousYtSearchService.cs @@ -18,7 +18,7 @@ public sealed class InvidiousYtSearchService : IYoutubeSearchService, INService _rng = new(); } - public async Task SearchAsync(string query) + public async Task SearchAsync(string query) { ArgumentNullException.ThrowIfNull(query); @@ -35,6 +35,7 @@ public sealed class InvidiousYtSearchService : IYoutubeSearchService, INService var url = $"{instance}/api/v1/search" + $"?q={query}" + $"&type=video"; + using var http = _http.CreateClient(); var res = await http.GetFromJsonAsync>( url); @@ -42,6 +43,6 @@ public sealed class InvidiousYtSearchService : IYoutubeSearchService, INService if (res is null or { Count: 0 }) return null; - return new VideoInfo(res[0].VideoId); + return res.Map(r => new VideoInfo(r.VideoId)); } } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Search/Youtube/YoutubeDataApiSearchService.cs b/src/NadekoBot/Modules/Searches/Search/Youtube/YoutubeDataApiSearchService.cs index 386a83d15..6284dc27a 100644 --- a/src/NadekoBot/Modules/Searches/Search/Youtube/YoutubeDataApiSearchService.cs +++ b/src/NadekoBot/Modules/Searches/Search/Youtube/YoutubeDataApiSearchService.cs @@ -9,18 +9,15 @@ public sealed class YoutubeDataApiSearchService : IYoutubeSearchService, INServi _gapi = gapi; } - public async Task SearchAsync(string query) + public async Task SearchAsync(string query) { ArgumentNullException.ThrowIfNull(query); var results = await _gapi.GetVideoLinksByKeywordAsync(query); - var first = results.FirstOrDefault(); - if (first is null) + + if(results.Count == 0) return null; - - return new() - { - Url = first - }; + + return results.Map(r => new VideoInfo(r)); } } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Search/Youtube/YtDlpSearchService.cs b/src/NadekoBot/Modules/Searches/Search/Youtube/YtDlpSearchService.cs new file mode 100644 index 000000000..07cf3f42e --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Search/Youtube/YtDlpSearchService.cs @@ -0,0 +1,26 @@ +namespace NadekoBot.Modules.Searches.Youtube; + +public class YtDlpSearchService : IYoutubeSearchService, INService +{ + private YtdlOperation CreateYtdlOp(int count) + => new YtdlOperation("-4 " + + "--ignore-errors --flat-playlist --skip-download --quiet " + + "--geo-bypass " + + "--encoding UTF8 " + + "--get-id " + + "--no-check-certificate " + + "--default-search " + + $"\"ytsearch{count}:\" -- \"{{0}}\""); + + public async Task SearchAsync(string query) + { + var op = CreateYtdlOp(5); + var data = await op.GetDataAsync(query); + var items = data?.Split('\n'); + if (items is null or { Length: 0 }) + return null; + + return items + .Map(x => new VideoInfo(x)); + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Search/Youtube/YtdlYoutubeSearchService.cs b/src/NadekoBot/Modules/Searches/Search/Youtube/YtdlYoutubeSearchService.cs deleted file mode 100644 index 6c1223c2c..000000000 --- a/src/NadekoBot/Modules/Searches/Search/Youtube/YtdlYoutubeSearchService.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NadekoBot.Modules.Searches.Youtube; - -public sealed class YtdlYoutubeSearchService : YoutubedlxServiceBase, INService -{ - public override async Task SearchAsync(string query) - => await InternalGetInfoAsync(query, false); -} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Search/Youtube/YtdlpYoutubeSearchService.cs b/src/NadekoBot/Modules/Searches/Search/Youtube/YtdlpYoutubeSearchService.cs deleted file mode 100644 index b39f4d6bd..000000000 --- a/src/NadekoBot/Modules/Searches/Search/Youtube/YtdlpYoutubeSearchService.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NadekoBot.Modules.Searches.Youtube; - -public sealed class YtdlpYoutubeSearchService : YoutubedlxServiceBase, INService -{ - public override async Task SearchAsync(string query) - => await InternalGetInfoAsync(query, true); -} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Search/Youtube/YtdlxServiceBase.cs b/src/NadekoBot/Modules/Searches/Search/Youtube/YtdlxServiceBase.cs deleted file mode 100644 index a9ac1b7f9..000000000 --- a/src/NadekoBot/Modules/Searches/Search/Youtube/YtdlxServiceBase.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace NadekoBot.Modules.Searches.Youtube; - -public abstract class YoutubedlxServiceBase : IYoutubeSearchService -{ - private YtdlOperation CreateYtdlOp(bool isYtDlp) - => new YtdlOperation("-4 " - + "--geo-bypass " - + "--encoding UTF8 " - + "--get-id " - + "--no-check-certificate " - + "--default-search " - + "\"ytsearch:\" -- \"{0}\"", - isYtDlp: isYtDlp); - - protected async Task InternalGetInfoAsync(string query, bool isYtDlp) - { - var op = CreateYtdlOp(isYtDlp); - var data = await op.GetDataAsync(query); - var items = data?.Split('\n'); - if (items is null or { Length: 0 }) - return null; - - var id = items.FirstOrDefault(x => x.Length is > 5 and < 15); - if (id is null) - return null; - - return new VideoInfo() - { - Url = $"https://youtube.com/watch?v={id}" - }; - } - - public abstract Task SearchAsync(string query); -} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/_common/Config/SearchesConfig.cs b/src/NadekoBot/Modules/Searches/_common/Config/SearchesConfig.cs index 214e35b65..32b618187 100644 --- a/src/NadekoBot/Modules/Searches/_common/Config/SearchesConfig.cs +++ b/src/NadekoBot/Modules/Searches/_common/Config/SearchesConfig.cs @@ -77,9 +77,9 @@ public sealed class FollowedStreamConfig public enum YoutubeSearcher { - YtDataApiv3, - Ytdl, - Ytdlp, - Invid, + YtDataApiv3 = 0, + Ytdl = 1, + Ytdlp = 1, + Invid = 3, Invidious = 3 } \ No newline at end of file diff --git a/src/NadekoBot/NadekoBot.csproj b/src/NadekoBot/NadekoBot.csproj index a94febaf1..25fe4de81 100644 --- a/src/NadekoBot/NadekoBot.csproj +++ b/src/NadekoBot/NadekoBot.csproj @@ -34,13 +34,12 @@ - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + + @@ -69,19 +68,19 @@ - - - + + + - + - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -89,7 +88,7 @@ - + @@ -103,20 +102,17 @@ + - - - Protos\coordinator.proto - true PreserveNewest @@ -132,7 +128,10 @@ - + + _common\CoordinatorProtos\coordinator.proto + + diff --git a/src/NadekoBot/Services/GrpcApi/ExprsSvc.cs b/src/NadekoBot/Services/GrpcApi/ExprsSvc.cs new file mode 100644 index 000000000..42613a8c3 --- /dev/null +++ b/src/NadekoBot/Services/GrpcApi/ExprsSvc.cs @@ -0,0 +1,73 @@ +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using NadekoBot.Db.Models; +using NadekoBot.Modules.NadekoExpressions; + +namespace NadekoBot.GrpcApi; + +public class ExprsSvc : GrpcExprs.GrpcExprsBase, INService +{ + private readonly NadekoExpressionsService _svc; + + public ExprsSvc(NadekoExpressionsService svc) + { + _svc = svc; + } + + public override async Task AddExpr(AddExprRequest request, ServerCallContext context) + { + NadekoExpression expr; + if (!string.IsNullOrWhiteSpace(request.Expr.Id)) + { + expr = await _svc.EditAsync(request.GuildId, + new kwum(request.Expr.Id), + request.Expr.Response, + request.Expr.Ca, + request.Expr.Ad, + request.Expr.Dm); + } + else + { + expr = await _svc.AddAsync(request.GuildId, + request.Expr.Trigger, + request.Expr.Response, + request.Expr.Ca, + request.Expr.Ad, + request.Expr.Dm); + } + + + return new AddExprReply() + { + Id = new kwum(expr.Id).ToString(), + Success = true, + }; + } + + public override async Task GetExprs(GetExprsRequest request, ServerCallContext context) + { + var (exprs, totalCount) = await _svc.FindExpressionsAsync(request.GuildId, request.Query, request.Page); + + var reply = new GetExprsReply(); + reply.TotalCount = totalCount; + reply.Expressions.AddRange(exprs.Select(x => new ExprDto() + { + Ad = x.AutoDeleteTrigger, + At = x.AllowTarget, + Ca = x.ContainsAnywhere, + Dm = x.DmResponse, + Response = x.Response, + Id = new kwum(x.Id).ToString(), + Trigger = x.Trigger, + })); + + return reply; + } + + public override async Task DeleteExpr(DeleteExprRequest request, ServerCallContext context) + { + await _svc.DeleteAsync(request.GuildId, new kwum(request.Id)); + + return new Empty(); + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/GrpcApi/GreetByeSvc.cs b/src/NadekoBot/Services/GrpcApi/GreetByeSvc.cs new file mode 100644 index 000000000..feea93276 --- /dev/null +++ b/src/NadekoBot/Services/GrpcApi/GreetByeSvc.cs @@ -0,0 +1,121 @@ +using Grpc.Core; +using GreetType = NadekoBot.Services.GreetType; + +namespace NadekoBot.GrpcApi; + +public sealed class GreetByeSvc : GrpcGreet.GrpcGreetBase, INService +{ + private readonly GreetService _gs; + private readonly DiscordSocketClient _client; + + public GreetByeSvc(GreetService gs, DiscordSocketClient client) + { + _gs = gs; + _client = client; + } + + public GreetSettings GetDefaultGreet(GreetType type) + => new GreetSettings() + { + GreetType = type + }; + + private static GrpcGreetSettings ToConf(GreetSettings? conf) + { + if (conf is null) + return new GrpcGreetSettings(); + + return new GrpcGreetSettings() + { + Message = conf.MessageText, + Type = (GrpcGreetType)conf.GreetType, + ChannelId = conf.ChannelId ?? 0, + IsEnabled = conf.IsEnabled + }; + } + + public override async Task 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 + + return new GetGreetReply() + { + Greet = ToConf(greetConf), + Bye = ToConf(byeConf), + Boost = ToConf(boostConf), + GreetDm = ToConf(greetDmConf) + }; + } + + public override async Task UpdateGreet(UpdateGreetRequest request, ServerCallContext context) + { + var gid = request.GuildId; + var s = request.Settings; + var msg = s.Message; + + await _gs.SetMessage(gid, GetGreetType(s.Type), msg); + await _gs.SetGreet(gid, s.ChannelId, GetGreetType(s.Type), s.IsEnabled); + + return new() + { + Success = true + }; + } + + public override Task TestGreet(TestGreetRequest request, ServerCallContext context) + => TestGreet(request.GuildId, request.ChannelId, request.UserId, request.Type); + + private async Task TestGreet( + ulong guildId, + ulong channelId, + ulong userId, + GrpcGreetType gtDto) + { + var g = _client.GetGuild(guildId) as IGuild; + if (g is null) + { + return new() + { + Error = "Guild doesn't exist", + Success = false, + }; + } + + var gu = await g.GetUserAsync(userId); + var ch = await g.GetTextChannelAsync(channelId); + + if (gu is null || ch is null) + return new TestGreetReply() + { + Error = "Guild or channel doesn't exist", + Success = false, + }; + + + var gt = GetGreetType(gtDto); + + await _gs.Test(guildId, gt, ch, gu); + return new TestGreetReply() + { + Success = true + }; + } + + private static GreetType GetGreetType(GrpcGreetType gtDto) + { + return gtDto switch + { + GrpcGreetType.Greet => GreetType.Greet, + GrpcGreetType.GreetDm => GreetType.GreetDm, + GrpcGreetType.Bye => GreetType.Bye, + GrpcGreetType.Boost => GreetType.Boost, + _ => throw new ArgumentOutOfRangeException(nameof(gtDto), gtDto, null) + }; + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/GrpcApi/OtherSvc.cs b/src/NadekoBot/Services/GrpcApi/OtherSvc.cs new file mode 100644 index 000000000..bb7608cd1 --- /dev/null +++ b/src/NadekoBot/Services/GrpcApi/OtherSvc.cs @@ -0,0 +1,123 @@ +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using NadekoBot.Modules.Gambling.Services; +using NadekoBot.Modules.Xp.Services; + +namespace NadekoBot.GrpcApi; + +public sealed class OtherSvc : GrpcOther.GrpcOtherBase, INService +{ + private readonly IDiscordClient _client; + private readonly XpService _xp; + private readonly ICurrencyService _cur; + private readonly WaifuService _waifus; + private readonly ICoordinator _coord; + + public OtherSvc( + DiscordSocketClient client, + XpService xp, + ICurrencyService cur, + WaifuService waifus, + ICoordinator coord) + { + _client = client; + _xp = xp; + _cur = cur; + _waifus = waifus; + _coord = coord; + } + + public override async Task GetTextChannels(GetTextChannelsRequest request, ServerCallContext context) + { + var g = await _client.GetGuildAsync(request.GuildId); + var reply = new GetTextChannelsReply(); + + var chs = await g.GetTextChannelsAsync(); + + reply.TextChannels.AddRange(chs.Select(x => new TextChannelReply() + { + Id = x.Id, + Name = x.Name, + })); + + return reply; + } + + public override async Task 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 user = await _client.GetUserAsync(x.UserId, CacheMode.CacheOnly); + return new CurrencyLbEntryReply() + { + Amount = x.CurrencyAmount, + User = user.ToString(), + UserId = x.UserId, + Avatar = user.RealAvatarUrl().ToString() + }; + }); + + reply.Entries.AddRange(await entries.WhenAll()); + + return reply; + } + + public override async Task GetXpLb(GetLbRequest request, ServerCallContext context) + { + var users = await _xp.GetUserXps(request.Page, request.PerPage); + + var reply = new XpLbReply(); + + var entries = users.Select(x => + { + var lvl = new LevelStats(x.TotalXp); + + return new XpLbEntryReply() + { + Level = lvl.Level, + TotalXp = x.TotalXp, + User = x.Username, + UserId = x.UserId + }; + }); + + reply.Entries.AddRange(entries); + + return reply; + } + + public override async Task GetWaifuLb(GetLbRequest request, ServerCallContext context) + { + var waifus = await _waifus.GetTopWaifusAtPage(request.Page, request.PerPage); + + var reply = new WaifuLbReply(); + reply.Entries.AddRange(waifus.Select(x => new WaifuLbEntry() + { + ClaimedBy = x.Claimer, + IsMutual = x.Claimer == x.Affinity, + Value = x.Price, + User = x.Username, + })); + return reply; + } + + public override Task GetShardStatuses(Empty request, ServerCallContext context) + { + var reply = new GetShardStatusesReply(); + + var shards = _coord.GetAllShardStatuses(); + + reply.Shards.AddRange(shards.Select(x => new ShardStatusReply() + { + Id = x.ShardId, + Status = x.ConnectionState.ToString(), + GuildCount = x.GuildCount, + LastUpdate = Timestamp.FromDateTime(x.LastUpdate), + })); + + return Task.FromResult(reply); + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/GrpcApi/ServerInfoSvc.cs b/src/NadekoBot/Services/GrpcApi/ServerInfoSvc.cs new file mode 100644 index 000000000..b2a2b830b --- /dev/null +++ b/src/NadekoBot/Services/GrpcApi/ServerInfoSvc.cs @@ -0,0 +1,49 @@ +using Grpc.Core; + +namespace NadekoBot.GrpcApi; + +public sealed class ServerInfoSvc : GrpcInfo.GrpcInfoBase, INService +{ + private readonly IStatsService _stats; + + public ServerInfoSvc(IStatsService stats) + { + _stats = stats; + } + + public override Task GetServerInfo(ServerInfoRequest request, ServerCallContext context) + { + var info = _stats.GetGuildInfo(request.GuildId); + + var reply = new GetServerInfoReply() + { + Id = info.Id, + Name = info.Name, + IconUrl = info.IconUrl, + OwnerId = info.OwnerId, + OwnerName = info.Owner, + TextChannels = info.TextChannels, + VoiceChannels = info.VoiceChannels, + MemberCount = info.MemberCount, + CreatedAt = info.CreatedAt.Ticks, + }; + + reply.Features.AddRange(info.Features); + reply.Emojis.AddRange(info.Emojis.Select(x => new EmojiReply() + { + Name = x.Name, + Url = x.Url, + Code = x.ToString() + })); + + reply.Roles.AddRange(info.Roles.Select(x => new RoleReply() + { + Id = x.Id, + Name = x.Name, + IconUrl = x.GetIconUrl() ?? string.Empty, + Color = x.Color.ToString() + })); + + return Task.FromResult(reply); + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/GrpcApiService.cs b/src/NadekoBot/Services/GrpcApiService.cs new file mode 100644 index 000000000..ce1175192 --- /dev/null +++ b/src/NadekoBot/Services/GrpcApiService.cs @@ -0,0 +1,63 @@ +using Grpc; +using Grpc.Core; +using NadekoBot.Common.ModuleBehaviors; + +namespace NadekoBot.GrpcApi; + +public class GrpcApiService : INService, IReadyExecutor +{ + private Server? _app; + + private static readonly bool _isEnabled = true; + private readonly string _host = "localhost"; + private readonly int _port = 5030; + private readonly ServerCredentials _creds = ServerCredentials.Insecure; + + private readonly OtherSvc _other; + private readonly ExprsSvc _exprs; + private readonly ServerInfoSvc _info; + private readonly GreetByeSvc _greet; + + public GrpcApiService( + OtherSvc other, + ExprsSvc exprs, + ServerInfoSvc info, + GreetByeSvc greet) + { + _other = other; + _exprs = exprs; + _info = info; + _greet = greet; + } + + public async Task OnReadyAsync() + { + if (!_isEnabled) + return; + + try + { + _app = new() + { + Services = + { + GrpcOther.BindService(_other), + GrpcExprs.BindService(_exprs), + GrpcInfo.BindService(_info), + GrpcGreet.BindService(_greet) + }, + Ports = + { + new(_host, _port, _creds), + } + }; + _app.Start(); + } + finally + { + _app?.ShutdownAsync().GetAwaiter().GetResult(); + } + + Log.Information("Grpc Api Server started on port {Host}:{Port}", _host, _port); + } +} \ No newline at end of file diff --git a/src/NadekoBot/_common/Creds.cs b/src/NadekoBot/_common/Creds.cs index af3e37c39..735c7821a 100644 --- a/src/NadekoBot/_common/Creds.cs +++ b/src/NadekoBot/_common/Creds.cs @@ -6,27 +6,28 @@ namespace NadekoBot.Common; public sealed class Creds : IBotCredentials { [Comment("""DO NOT CHANGE""")] - public int Version { get; set; } + public int Version { get; set; } = 10; [Comment("""Bot token. Do not share with anyone ever -> https://discordapp.com/developers/applications/""")] public string Token { get; set; } [Comment(""" - List of Ids of the users who have bot owner permissions - **DO NOT ADD PEOPLE YOU DON'T TRUST** - """)] + List of Ids of the users who have bot owner permissions + **DO NOT ADD PEOPLE YOU DON'T TRUST** + """)] public ICollection OwnerIds { get; set; } - - [Comment("Keep this on 'true' unless you're sure your bot shouldn't use privileged intents or you're waiting to be accepted")] + + [Comment( + "Keep this on 'true' unless you're sure your bot shouldn't use privileged intents or you're waiting to be accepted")] public bool UsePrivilegedIntents { get; set; } [Comment(""" - The number of shards that the bot will be running on. - Leave at 1 if you don't know what you're doing. - - note: If you are planning to have more than one shard, then you must change botCache to 'redis'. - Also, in that case you should be using NadekoBot.Coordinator to start the bot, and it will correctly override this value. - """)] + The number of shards that the bot will be running on. + Leave at 1 if you don't know what you're doing. + + note: If you are planning to have more than one shard, then you must change botCache to 'redis'. + Also, in that case you should be using NadekoBot.Coordinator to start the bot, and it will correctly override this value. + """)] public int TotalShards { get; set; } [Comment(""" @@ -37,34 +38,34 @@ public sealed class Creds : IBotCredentials For example '@Bot how's the weather in Paris' will return the current weather in Paris as if you were to run `.weather Paris` command. """)] public string NadekoAiToken { get; set; } - - [Comment( + + [Comment( """ - Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it. - Then, go to APIs and Services -> Credentials and click Create credentials -> API key. - Used only for Youtube Data Api (at the moment). - """)] + Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it. + Then, go to APIs and Services -> Credentials and click Create credentials -> API key. + Used only for Youtube Data Api (at the moment). + """)] public string GoogleApiKey { get; set; } - - [Comment( + + [Comment( """ - Create a new custom search here https://programmablesearchengine.google.com/cse/create/new - Enable SafeSearch - Remove all Sites to Search - Enable Search the entire web - Copy the 'Search Engine ID' to the SearchId field - - Do all steps again but enable image search for the ImageSearchId - """)] + Create a new custom search here https://programmablesearchengine.google.com/cse/create/new + Enable SafeSearch + Remove all Sites to Search + Enable Search the entire web + Copy the 'Search Engine ID' to the SearchId field + + Do all steps again but enable image search for the ImageSearchId + """)] public GoogleApiConfig Google { get; set; } [Comment("""Settings for voting system for discordbots. Meant for use on global Nadeko.""")] public VotesSettings Votes { get; set; } [Comment(""" - Patreon auto reward system settings. - go to https://www.patreon.com/portal -> my clients -> create client - """)] + Patreon auto reward system settings. + go to https://www.patreon.com/portal -> my clients -> create client + """)] public PatreonSettings Patreon { get; set; } [Comment("""Api key for sending stats to DiscordBotList.""")] @@ -75,27 +76,27 @@ public sealed class Creds : IBotCredentials [Comment(@"OpenAi api key.")] public string Gpt3ApiKey { get; set; } - + [Comment(""" - Which cache implementation should bot use. - 'memory' - Cache will be in memory of the bot's process itself. Only use this on bots with a single shard. When the bot is restarted the cache is reset. - 'redis' - Uses redis (which needs to be separately downloaded and installed). The cache will persist through bot restarts. You can configure connection string in creds.yml - """)] + Which cache implementation should bot use. + 'memory' - Cache will be in memory of the bot's process itself. Only use this on bots with a single shard. When the bot is restarted the cache is reset. + 'redis' - Uses redis (which needs to be separately downloaded and installed). The cache will persist through bot restarts. You can configure connection string in creds.yml + """)] public BotCacheImplemenation BotCache { get; set; } - + [Comment(""" - Redis connection string. Don't change if you don't know what you're doing. - Only used if botCache is set to 'redis' - """)] + Redis connection string. Don't change if you don't know what you're doing. + Only used if botCache is set to 'redis' + """)] public string RedisOptions { get; set; } [Comment("""Database options. Don't change if you don't know what you're doing. Leave null for default values""")] public DbOptions Db { get; set; } [Comment(""" - Address and port of the coordinator endpoint. Leave empty for default. - Change only if you've changed the coordinator address or port. - """)] + Address and port of the coordinator endpoint. Leave empty for default. + Change only if you've changed the coordinator address or port. + """)] public string CoordinatorUrl { get; set; } [Comment( @@ -103,23 +104,23 @@ public sealed class Creds : IBotCredentials public string RapidApiKey { get; set; } [Comment(""" - https://locationiq.com api key (register and you will receive the token in the email). - Used only for .time command. - """)] + https://locationiq.com api key (register and you will receive the token in the email). + Used only for .time command. + """)] public string LocationIqApiKey { get; set; } [Comment(""" - https://timezonedb.com api key (register and you will receive the token in the email). - Used only for .time command - """)] + https://timezonedb.com api key (register and you will receive the token in the email). + Used only for .time command + """)] public string TimezoneDbApiKey { get; set; } [Comment(""" - https://pro.coinmarketcap.com/account/ api key. There is a free plan for personal use. - Used for cryptocurrency related commands. - """)] + https://pro.coinmarketcap.com/account/ api key. There is a free plan for personal use. + Used for cryptocurrency related commands. + """)] public string CoinmarketcapApiKey { get; set; } - + // [Comment(@"https://polygon.io/dashboard/api-keys api key. Free plan allows for 5 queries per minute. // Used for stocks related commands.")] // public string PolygonIoApiKey { get; set; } @@ -128,9 +129,9 @@ public sealed class Creds : IBotCredentials public string OsuApiKey { get; set; } [Comment(""" - Optional Trovo client id. - You should use this if Trovo stream notifications stopped working or you're getting ratelimit errors. - """)] + Optional Trovo client id. + You should use this if Trovo stream notifications stopped working or you're getting ratelimit errors. + """)] public string TrovoClientId { get; set; } [Comment("""Obtain by creating an application at https://dev.twitch.tv/console/apps""")] @@ -140,23 +141,30 @@ public sealed class Creds : IBotCredentials public string TwitchClientSecret { get; set; } [Comment(""" - Command and args which will be used to restart the bot. - Only used if bot is executed directly (NOT through the coordinator) - placeholders: - {0} -> shard id - {1} -> total shards - Linux default - cmd: dotnet - args: "NadekoBot.dll -- {0}" - Windows default - cmd: NadekoBot.exe - args: "{0}" - """)] + Command and args which will be used to restart the bot. + Only used if bot is executed directly (NOT through the coordinator) + placeholders: + {0} -> shard id + {1} -> total shards + Linux default + cmd: dotnet + args: "NadekoBot.dll -- {0}" + Windows default + cmd: NadekoBot.exe + args: "{0}" + """)] public RestartConfig RestartCommand { get; set; } + + [Comment(""" + Settings for the grpc api. + We don't provide support for this. + If you leave certPath empty, the api will run on http. + """)] + public ApiConfig Api { get; set; } + public Creds() { - Version = 9; Token = string.Empty; UsePrivilegedIntents = true; OwnerIds = new List(); @@ -179,24 +187,26 @@ public sealed class Creds : IBotCredentials RestartCommand = new RestartConfig(); Google = new GoogleApiConfig(); + + Api = new ApiConfig(); } - + public class DbOptions : IDbOptions { [Comment(""" - Database type. "sqlite", "mysql" and "postgresql" are supported. - Default is "sqlite" - """)] + Database type. "sqlite", "mysql" and "postgresql" are supported. + Default is "sqlite" + """)] public string Type { get; set; } [Comment(""" - Database connection string. - You MUST change this if you're not using "sqlite" type. - Default is "Data Source=data/NadekoBot.db" - Example for mysql: "Server=localhost;Port=3306;Uid=root;Pwd=my_super_secret_mysql_password;Database=nadeko" - Example for postgresql: "Server=localhost;Port=5432;User Id=postgres;Password=my_super_secret_postgres_password;Database=nadeko;" - """)] + Database connection string. + You MUST change this if you're not using "sqlite" type. + Default is "Data Source=data/NadekoBot.db" + Example for mysql: "Server=localhost;Port=3306;Uid=root;Pwd=my_super_secret_mysql_password;Database=nadeko" + Example for postgresql: "Server=localhost;Port=5432;User Id=postgres;Password=my_super_secret_postgres_password;Database=nadeko;" + """)] public string ConnectionString { get; set; } } @@ -231,29 +241,29 @@ public sealed class Creds : IBotCredentials public sealed record VotesSettings : IVotesSettings { [Comment(""" - top.gg votes service url - This is the url of your instance of the NadekoBot.Votes api - Example: https://votes.my.cool.bot.com - """)] + top.gg votes service url + This is the url of your instance of the NadekoBot.Votes api + Example: https://votes.my.cool.bot.com + """)] public string TopggServiceUrl { get; set; } [Comment(""" - Authorization header value sent to the TopGG service url with each request - This should be equivalent to the TopggKey in your NadekoBot.Votes api appsettings.json file - """)] + Authorization header value sent to the TopGG service url with each request + This should be equivalent to the TopggKey in your NadekoBot.Votes api appsettings.json file + """)] public string TopggKey { get; set; } [Comment(""" - discords.com votes service url - This is the url of your instance of the NadekoBot.Votes api - Example: https://votes.my.cool.bot.com - """)] + discords.com votes service url + This is the url of your instance of the NadekoBot.Votes api + Example: https://votes.my.cool.bot.com + """)] public string DiscordsServiceUrl { get; set; } [Comment(""" - Authorization header value sent to the Discords service url with each request - This should be equivalent to the DiscordsKey in your NadekoBot.Votes api appsettings.json file - """)] + Authorization header value sent to the Discords service url with each request + This should be equivalent to the DiscordsKey in your NadekoBot.Votes api appsettings.json file + """)] public string DiscordsKey { get; set; } public VotesSettings() @@ -272,13 +282,19 @@ public sealed class Creds : IBotCredentials DiscordsKey = discordsKey; } } + + public sealed record ApiConfig + { + public bool Enabled { get; set; } = false; + public string CertPath { get; set; } = string.Empty; + public string CertPassword { get; set; } = string.Empty; + public string Host { get; set; } = "localhost"; + public int Port { get; set; } = 43120; + } } public class GoogleApiConfig : IGoogleApiConfig { public string SearchId { get; init; } public string ImageSearchId { get; init; } -} - - - +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/BotCredsProvider.cs b/src/NadekoBot/_common/Impl/BotCredsProvider.cs similarity index 94% rename from src/NadekoBot/Services/Impl/BotCredsProvider.cs rename to src/NadekoBot/_common/Impl/BotCredsProvider.cs index 6c3b08e90..a4cd337a3 100644 --- a/src/NadekoBot/Services/Impl/BotCredsProvider.cs +++ b/src/NadekoBot/_common/Impl/BotCredsProvider.cs @@ -140,15 +140,9 @@ public sealed class BotCredsProvider : IBotCredsProvider creds.BotCache = BotCacheImplemenation.Redis; } - if (creds.Version <= 6) + if (creds.Version <= 9) { - creds.Version = 7; - File.WriteAllText(CREDS_FILE_NAME, Yaml.Serializer.Serialize(creds)); - } - - if (creds.Version <= 8) - { - creds.Version = 9; + creds.Version = 10; File.WriteAllText(CREDS_FILE_NAME, Yaml.Serializer.Serialize(creds)); } } diff --git a/src/NadekoBot/Services/Impl/GoogleApiService.cs b/src/NadekoBot/_common/Impl/GoogleApiService.cs similarity index 97% rename from src/NadekoBot/Services/Impl/GoogleApiService.cs rename to src/NadekoBot/_common/Impl/GoogleApiService.cs index d90422be1..b1da4d7ac 100644 --- a/src/NadekoBot/Services/Impl/GoogleApiService.cs +++ b/src/NadekoBot/_common/Impl/GoogleApiService.cs @@ -75,7 +75,7 @@ public sealed partial class GoogleApiService : IGoogleApiService, INService return (await query.ExecuteAsync()).Items.Select(i => "https://www.youtube.com/watch?v=" + i.Id.VideoId).Skip(1); } - public async Task> GetVideoLinksByKeywordAsync(string keywords, int count = 1) + public async Task> GetVideoLinksByKeywordAsync(string keywords, int count = 1) { if (string.IsNullOrWhiteSpace(keywords)) throw new ArgumentNullException(nameof(keywords)); @@ -87,7 +87,7 @@ public sealed partial class GoogleApiService : IGoogleApiService, INService query.Q = keywords; query.Type = "video"; query.SafeSearch = SearchResource.ListRequest.SafeSearchEnum.Strict; - return (await query.ExecuteAsync()).Items.Select(i => "https://www.youtube.com/watch?v=" + i.Id.VideoId); + return (await query.ExecuteAsync()).Items.Select(i => "https://www.youtube.com/watch?v=" + i.Id.VideoId).ToArray(); } public async Task> GetVideoInfosByKeywordAsync( diff --git a/src/NadekoBot/Services/Impl/GoogleApiService_SupportedLanguages.cs b/src/NadekoBot/_common/Impl/GoogleApiService_SupportedLanguages.cs similarity index 100% rename from src/NadekoBot/Services/Impl/GoogleApiService_SupportedLanguages.cs rename to src/NadekoBot/_common/Impl/GoogleApiService_SupportedLanguages.cs diff --git a/src/NadekoBot/Services/Impl/ImageCache.cs b/src/NadekoBot/_common/Impl/ImageCache.cs similarity index 100% rename from src/NadekoBot/Services/Impl/ImageCache.cs rename to src/NadekoBot/_common/Impl/ImageCache.cs diff --git a/src/NadekoBot/Services/Impl/LocalDataCache.cs b/src/NadekoBot/_common/Impl/LocalDataCache.cs similarity index 100% rename from src/NadekoBot/Services/Impl/LocalDataCache.cs rename to src/NadekoBot/_common/Impl/LocalDataCache.cs diff --git a/src/NadekoBot/Services/Impl/Localization.cs b/src/NadekoBot/_common/Impl/Localization.cs similarity index 100% rename from src/NadekoBot/Services/Impl/Localization.cs rename to src/NadekoBot/_common/Impl/Localization.cs diff --git a/src/NadekoBot/Services/Impl/PubSub/JsonSeria.cs b/src/NadekoBot/_common/Impl/PubSub/JsonSeria.cs similarity index 100% rename from src/NadekoBot/Services/Impl/PubSub/JsonSeria.cs rename to src/NadekoBot/_common/Impl/PubSub/JsonSeria.cs diff --git a/src/NadekoBot/Services/Impl/PubSub/RedisPubSub.cs b/src/NadekoBot/_common/Impl/PubSub/RedisPubSub.cs similarity index 100% rename from src/NadekoBot/Services/Impl/PubSub/RedisPubSub.cs rename to src/NadekoBot/_common/Impl/PubSub/RedisPubSub.cs diff --git a/src/NadekoBot/Services/Impl/PubSub/YamlSeria.cs b/src/NadekoBot/_common/Impl/PubSub/YamlSeria.cs similarity index 100% rename from src/NadekoBot/Services/Impl/PubSub/YamlSeria.cs rename to src/NadekoBot/_common/Impl/PubSub/YamlSeria.cs diff --git a/src/NadekoBot/Services/Impl/RedisBotCache.cs b/src/NadekoBot/_common/Impl/RedisBotCache.cs similarity index 100% rename from src/NadekoBot/Services/Impl/RedisBotCache.cs rename to src/NadekoBot/_common/Impl/RedisBotCache.cs diff --git a/src/NadekoBot/Services/Impl/RedisBotStringsProvider.cs b/src/NadekoBot/_common/Impl/RedisBotStringsProvider.cs similarity index 100% rename from src/NadekoBot/Services/Impl/RedisBotStringsProvider.cs rename to src/NadekoBot/_common/Impl/RedisBotStringsProvider.cs diff --git a/src/NadekoBot/Services/Impl/RemoteGrpcCoordinator.cs b/src/NadekoBot/_common/Impl/RemoteGrpcCoordinator.cs similarity index 100% rename from src/NadekoBot/Services/Impl/RemoteGrpcCoordinator.cs rename to src/NadekoBot/_common/Impl/RemoteGrpcCoordinator.cs diff --git a/src/NadekoBot/_common/Services/IGoogleApiService.cs b/src/NadekoBot/_common/Services/IGoogleApiService.cs index a02a56fe5..f205cb9b1 100644 --- a/src/NadekoBot/_common/Services/IGoogleApiService.cs +++ b/src/NadekoBot/_common/Services/IGoogleApiService.cs @@ -6,7 +6,7 @@ public interface IGoogleApiService { IReadOnlyDictionary Languages { get; } - Task> GetVideoLinksByKeywordAsync(string keywords, int count = 1); + Task> GetVideoLinksByKeywordAsync(string keywords, int count = 1); Task> GetVideoInfosByKeywordAsync(string keywords, int count = 1); Task> GetPlaylistIdsByKeywordsAsync(string keywords, int count = 1); Task> GetRelatedVideosAsync(string id, int count = 1, string user = null); diff --git a/src/NadekoBot/_common/Services/Impl/YtdlOperation.cs b/src/NadekoBot/_common/Services/Impl/YtdlOperation.cs index 0f85f0b43..86afb600f 100644 --- a/src/NadekoBot/_common/Services/Impl/YtdlOperation.cs +++ b/src/NadekoBot/_common/Services/Impl/YtdlOperation.cs @@ -10,10 +10,9 @@ public class YtdlOperation private readonly string _baseArgString; private readonly bool _isYtDlp; - public YtdlOperation(string baseArgString, bool isYtDlp = false) + public YtdlOperation(string baseArgString) { _baseArgString = baseArgString; - _isYtDlp = isYtDlp; } private Process CreateProcess(string[] args) @@ -23,7 +22,7 @@ public class YtdlOperation { StartInfo = new() { - FileName = _isYtDlp ? "yt-dlp" : "youtube-dl", + FileName = "yt-dlp", Arguments = string.Format(_baseArgString, newArgs), UseShellExecute = false, RedirectStandardError = true, @@ -47,18 +46,18 @@ public class YtdlOperation var str = await process.StandardOutput.ReadToEndAsync(); var err = await process.StandardError.ReadToEndAsync(); if (!string.IsNullOrEmpty(err)) - Log.Warning("YTDL warning: {YtdlWarning}", err); + Log.Warning("yt-dlp warning: {YtdlWarning}", err); return str; } catch (Win32Exception) { - Log.Error("youtube-dl is likely not installed. Please install it before running the command again"); + Log.Error("yt-dlp is likely not installed. Please install it before running the command again"); return default; } catch (Exception ex) { - Log.Error(ex, "Exception running youtube-dl: {ErrorMessage}", ex.Message); + Log.Error(ex, "Exception running yt-dlp: {ErrorMessage}", ex.Message); return default; } } diff --git a/src/NadekoBot/data/medusae/medusa.yml b/src/NadekoBot/data/medusae/medusa.yml index 8baf6a6d6..89426a9ba 100644 --- a/src/NadekoBot/data/medusae/medusa.yml +++ b/src/NadekoBot/data/medusae/medusa.yml @@ -1,4 +1,5 @@ # DO NOT CHANGE version: 1 # List of medusae automatically loaded at startup -loaded: [] +loaded: + - ngrpc