diff --git a/src/EllieBot.GrpcApiBase/protos/other.proto b/src/EllieBot.GrpcApiBase/protos/other.proto index 664fee7..64a3170 100644 --- a/src/EllieBot.GrpcApiBase/protos/other.proto +++ b/src/EllieBot.GrpcApiBase/protos/other.proto @@ -3,13 +3,11 @@ option csharp_namespace = "EllieBot.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/EllieBot/Modules/Administration/UserPunish/UserPunishService.cs b/src/EllieBot/Modules/Administration/UserPunish/UserPunishService.cs index 2cfaaab..7b31658 100644 --- a/src/EllieBot/Modules/Administration/UserPunish/UserPunishService.cs +++ b/src/EllieBot/Modules/Administration/UserPunish/UserPunishService.cs @@ -2,7 +2,6 @@ using LinqToDB; using LinqToDB.EntityFrameworkCore; using EllieBot.Common.ModuleBehaviors; -using EllieBot.Common.TypeReaders.Models; using EllieBot.Modules.Permissions.Services; using EllieBot.Db.Models; using Newtonsoft.Json; diff --git a/src/EllieBot/Services/GrpcApi/OtherSvc.cs b/src/EllieBot/Services/GrpcApi/OtherSvc.cs index e2c1e6f..8138345 100644 --- a/src/EllieBot/Services/GrpcApi/OtherSvc.cs +++ b/src/EllieBot/Services/GrpcApi/OtherSvc.cs @@ -13,51 +13,48 @@ public static class GrpcApiExtensions public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, IEService { - 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() @@ -66,16 +63,17 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, IEService Name = x.Name, Color = x.Color.ToString(), IconUrl = x.GetIconUrl() ?? string.Empty, - }) ?? new List()); + }) + ?? 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, IEService 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, IEService 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, IEService } [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, + }; + await responseStream.WriteAsync(stats); + await Task.Delay(1000); + } + } - return reply; + [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/EllieBot/Services/GrpcApiPermsInterceptor.cs b/src/EllieBot/Services/GrpcApiPermsInterceptor.cs index 05e1842..6e4b6cf 100644 --- a/src/EllieBot/Services/GrpcApiPermsInterceptor.cs +++ b/src/EllieBot/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) + public 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) { @@ -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/EllieBot/data/bot.yml b/src/EllieBot/data/bot.yml index 22b08ed..e15bbc1 100644 --- a/src/EllieBot/data/bot.yml +++ b/src/EllieBot/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