From 85c525e19b7c9446520d9b0beecd30cb7d569339 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Wed, 23 Oct 2024 21:29:40 +0000 Subject: [PATCH] api: added command feed and shard update feed --- src/NadekoBot.GrpcApiBase/protos/other.proto | 28 +--- .../UserPunish/UserPunishService.cs | 1 - src/NadekoBot/Services/GrpcApi/OtherSvc.cs | 155 ++++++++++-------- .../Services/GrpcApiPermsInterceptor.cs | 49 +++++- src/NadekoBot/data/bot.yml | 2 +- 5 files changed, 134 insertions(+), 101 deletions(-) diff --git a/src/NadekoBot.GrpcApiBase/protos/other.proto b/src/NadekoBot.GrpcApiBase/protos/other.proto index 74cc27a1c..782ce12a6 100644 --- a/src/NadekoBot.GrpcApiBase/protos/other.proto +++ b/src/NadekoBot.GrpcApiBase/protos/other.proto @@ -3,13 +3,11 @@ syntax = "proto3"; option csharp_namespace = "NadekoBot.GrpcApi"; import "google/protobuf/empty.proto"; -import "google/protobuf/timestamp.proto"; package other; service GrpcOther { rpc BotOnGuild(BotOnGuildRequest) returns (BotOnGuildReply); - rpc GetGuilds(google.protobuf.Empty) returns (GetGuildsReply); rpc GetTextChannels(GetTextChannelsRequest) returns (GetTextChannelsReply); rpc GetRoles(GetRolesRequest) returns (GetRolesReply); @@ -17,10 +15,15 @@ service GrpcOther { 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 CommandFeedEntry { + string command = 1; +} + message GetRolesRequest { uint64 guildId = 1; } @@ -37,26 +40,13 @@ message BotOnGuildReply { bool success = 1; } -message GetGuildsReply { - repeated GuildReply guilds = 1; -} - -message GuildReply { - uint64 id = 1; - string name = 2; - string iconUrl = 3; -} - -message GetShardStatusesReply { - repeated ShardStatusReply shards = 1; -} - -message ShardStatusReply { +message ShardStatsReply { int32 id = 1; string status = 2; int32 guildCount = 3; - google.protobuf.Timestamp lastUpdate = 4; + string uptime = 4; + int64 commands = 5; } message GetTextChannelsRequest{ diff --git a/src/NadekoBot/Modules/Administration/UserPunish/UserPunishService.cs b/src/NadekoBot/Modules/Administration/UserPunish/UserPunishService.cs index 3d023e3de..b1d3305d1 100644 --- a/src/NadekoBot/Modules/Administration/UserPunish/UserPunishService.cs +++ b/src/NadekoBot/Modules/Administration/UserPunish/UserPunishService.cs @@ -2,7 +2,6 @@ using LinqToDB; using LinqToDB.EntityFrameworkCore; using NadekoBot.Common.ModuleBehaviors; -using NadekoBot.Common.TypeReaders.Models; using NadekoBot.Modules.Permissions.Services; using NadekoBot.Db.Models; using Newtonsoft.Json; diff --git a/src/NadekoBot/Services/GrpcApi/OtherSvc.cs b/src/NadekoBot/Services/GrpcApi/OtherSvc.cs index 1ef58893a..9cd82e34a 100644 --- a/src/NadekoBot/Services/GrpcApi/OtherSvc.cs +++ b/src/NadekoBot/Services/GrpcApi/OtherSvc.cs @@ -13,69 +13,67 @@ public static class GrpcApiExtensions 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 IBotCache _cache; + private readonly CommandHandler _cmdHandler; public OtherSvc( DiscordSocketClient client, XpService xp, ICurrencyService cur, WaifuService waifus, - ICoordinator coord, IStatsService stats, - IBotCache cache) + CommandHandler cmdHandler) { _client = client; _xp = xp; _cur = cur; _waifus = waifus; - _coord = coord; _stats = stats; - _cache = cache; + _cmdHandler = cmdHandler; } - + public ServerServiceDefinition Bind() => GrpcOther.BindService(this); [GrpcNoAuthRequired] - public override async Task BotOnGuild(BotOnGuildRequest request, ServerCallContext context) + public override Task BotOnGuild(BotOnGuildRequest request, ServerCallContext context) { - var guild = await _client.GetGuildAsync(request.GuildId); - + var guild = _client.GetGuild(request.GuildId); + var reply = new BotOnGuildReply { Success = guild is not null }; - - return reply; + + return Task.FromResult(reply); } - public override async Task GetRoles(GetRolesRequest request, ServerCallContext context) + public override Task GetRoles(GetRolesRequest request, ServerCallContext context) { - var g = await _client.GetGuildAsync(request.GuildId); + 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()); + { + Id = x.Id, + Name = x.Name, + Color = x.Color.ToString(), + IconUrl = x.GetIconUrl() ?? string.Empty, + }) + ?? new List()); - return reply; + return Task.FromResult(reply); } - + public override async Task 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(); @@ -89,33 +87,6 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, INService return reply; } - [GrpcNoAuthRequired] - public override async Task GetGuilds(Empty request, ServerCallContext context) - { - var guilds = await _client.GetGuildsAsync(CacheMode.CacheOnly); - - var reply = new GetGuildsReply(); - var userId = context.GetUserId(); - - var toReturn = new List(); - foreach (var g in guilds) - { - var user = await g.GetUserAsync(userId); - if (user is not null && user.GuildPermissions.Has(GuildPermission.Administrator)) - toReturn.Add(g); - } - - reply.Guilds.AddRange(toReturn - .Select(x => new GuildReply() - { - Id = x.Id, - Name = x.Name, - IconUrl = x.IconUrl - })); - - return reply; - } - [GrpcNoAuthRequired] public override async Task GetCurrencyLb(GetLbRequest request, ServerCallContext context) @@ -123,16 +94,16 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, INService var users = await _cur.GetTopRichest(_client.CurrentUser.Id, request.Page, request.PerPage); var 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()); @@ -182,26 +153,66 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, INService } [GrpcNoAuthRequired] - public override async Task GetShardStatuses(Empty request, ServerCallContext context) + public override async Task GetShardStats( + Empty request, + IServerStreamWriter responseStream, + ServerCallContext context) { - var reply = new GetShardStatusesReply(); - - await _cache.GetOrAddAsync>("coord:statuses", - () => Task.FromResult(_coord.GetAllShardStatuses().ToList())!, - TimeSpan.FromMinutes(1)); - - 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 reply; + await responseStream.WriteAsync(stats); + await Task.Delay(1000); + } + } + + [GrpcNoAuthRequired] + public override async Task GetCommandFeed( + Empty request, + IServerStreamWriter responseStream, + ServerCallContext context) + { + var taskCompletion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + Task OnCommandExecuted(IUserMessage userMessage, CommandInfo commandInfo) + { + try + { + responseStream.WriteAsync(new() + { + Command = commandInfo.Name + }); + } + catch + { + _cmdHandler.CommandExecuted -= OnCommandExecuted; + taskCompletion.TrySetResult(true); + } + + return Task.CompletedTask; + } + + _cmdHandler.CommandExecuted += OnCommandExecuted; + + await taskCompletion.Task; + } + + private string GetConnectionState(ConnectionState clientConnectionState) + { + return clientConnectionState switch + { + ConnectionState.Connected => "Connected", + ConnectionState.Connecting => "Connecting", + _ => "Disconnected" + }; } public override async Task GetServerInfo(ServerInfoRequest request, ServerCallContext context) diff --git a/src/NadekoBot/Services/GrpcApiPermsInterceptor.cs b/src/NadekoBot/Services/GrpcApiPermsInterceptor.cs index affbaf8dd..f0c4630c0 100644 --- a/src/NadekoBot/Services/GrpcApiPermsInterceptor.cs +++ b/src/NadekoBot/Services/GrpcApiPermsInterceptor.cs @@ -14,10 +14,7 @@ public sealed partial class GrpcApiPermsInterceptor : Interceptor _client = client; } - public override async Task UnaryServerHandler( - TRequest request, - ServerCallContext context, - UnaryServerMethod continuation) + private async Task RequestHandler(ServerCallContext context) { try { @@ -42,7 +39,7 @@ public sealed partial class GrpcApiPermsInterceptor : Interceptor // if the method is explicitly marked as not requiring auth if (_noAuthRequired.Contains(method)) - return await continuation(request, context); + return; // otherwise the method requires auth, and if it requires auth then the guildid has to be specified if (string.IsNullOrWhiteSpace(gidString)) @@ -61,8 +58,6 @@ public sealed partial class GrpcApiPermsInterceptor : Interceptor // if not then use the default, which is Administrator permission await EnsureUserHasPermission(guildId, userId, DEFAULT_PERMISSION); } - - return await continuation(request, context); } catch (Exception ex) { @@ -70,7 +65,7 @@ public sealed partial class GrpcApiPermsInterceptor : Interceptor throw; } } - + private async Task EnsureUserHasPermission(ulong guildId, ulong userId, GuildPerm perm) { IGuild guild = _client.GetGuild(guildId); @@ -83,4 +78,42 @@ public sealed partial class GrpcApiPermsInterceptor : Interceptor throw new RpcException(new Status(StatusCode.PermissionDenied, $"You need {perm} permission to use this method")); } + + public override async Task ClientStreamingServerHandler( + IAsyncStreamReader requestStream, + ServerCallContext context, + ClientStreamingServerMethod continuation) + { + await RequestHandler(context); + return await continuation(requestStream, context); + } + + public override async Task DuplexStreamingServerHandler( + IAsyncStreamReader requestStream, + IServerStreamWriter responseStream, + ServerCallContext context, + DuplexStreamingServerMethod continuation) + { + await RequestHandler(context); + await continuation(requestStream, responseStream, context); + } + + public override async Task ServerStreamingServerHandler( + TRequest request, + IServerStreamWriter responseStream, + ServerCallContext context, + ServerStreamingServerMethod continuation) + { + await RequestHandler(context); + await continuation(request, responseStream, context); + } + + public override async Task UnaryServerHandler( + TRequest request, + ServerCallContext context, + UnaryServerMethod continuation) + { + await RequestHandler(context); + return await continuation(request, context); + } } \ No newline at end of file diff --git a/src/NadekoBot/data/bot.yml b/src/NadekoBot/data/bot.yml index a82fd2400..bd4e5303b 100644 --- a/src/NadekoBot/data/bot.yml +++ b/src/NadekoBot/data/bot.yml @@ -15,7 +15,7 @@ color: pending: faa61a # Default bot language. It has to be in the list of supported languages (.langli) defaultLocale: en-US -# Style in which executed commands will show up in the console. +# Style in which executed commands will show up in the logs. # Allowed values: Simple, Normal, None consoleOutputType: Normal # Whether the bot will check for new releases every hour