added command feed and shard update feed

This commit is contained in:
Toastie (DCS Team) 2024-10-24 11:49:41 +13:00
parent 5af8c0bd11
commit 448624e543
Signed by: toastie_t0ast
GPG key ID: 27F3B6855AFD40A4
5 changed files with 123 additions and 90 deletions

View file

@ -3,13 +3,11 @@
option csharp_namespace = "EllieBot.GrpcApi"; option csharp_namespace = "EllieBot.GrpcApi";
import "google/protobuf/empty.proto"; import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";
package other; package other;
service GrpcOther { service GrpcOther {
rpc BotOnGuild(BotOnGuildRequest) returns (BotOnGuildReply); rpc BotOnGuild(BotOnGuildRequest) returns (BotOnGuildReply);
rpc GetGuilds(google.protobuf.Empty) returns (GetGuildsReply);
rpc GetTextChannels(GetTextChannelsRequest) returns (GetTextChannelsReply); rpc GetTextChannels(GetTextChannelsRequest) returns (GetTextChannelsReply);
rpc GetRoles(GetRolesRequest) returns (GetRolesReply); rpc GetRoles(GetRolesRequest) returns (GetRolesReply);
@ -17,10 +15,15 @@ service GrpcOther {
rpc GetXpLb(GetLbRequest) returns (XpLbReply); rpc GetXpLb(GetLbRequest) returns (XpLbReply);
rpc GetWaifuLb(GetLbRequest) returns (WaifuLbReply); rpc GetWaifuLb(GetLbRequest) returns (WaifuLbReply);
rpc GetShardStatuses(google.protobuf.Empty) returns (GetShardStatusesReply); rpc GetShardStats(google.protobuf.Empty) returns (stream ShardStatsReply);
rpc GetCommandFeed(google.protobuf.Empty) returns (stream CommandFeedEntry);
rpc GetServerInfo(ServerInfoRequest) returns (GetServerInfoReply); rpc GetServerInfo(ServerInfoRequest) returns (GetServerInfoReply);
} }
message CommandFeedEntry {
string command = 1;
}
message GetRolesRequest { message GetRolesRequest {
uint64 guildId = 1; uint64 guildId = 1;
} }
@ -37,26 +40,13 @@ message BotOnGuildReply {
bool success = 1; bool success = 1;
} }
message GetGuildsReply { message ShardStatsReply {
repeated GuildReply guilds = 1;
}
message GuildReply {
uint64 id = 1;
string name = 2;
string iconUrl = 3;
}
message GetShardStatusesReply {
repeated ShardStatusReply shards = 1;
}
message ShardStatusReply {
int32 id = 1; int32 id = 1;
string status = 2; string status = 2;
int32 guildCount = 3; int32 guildCount = 3;
google.protobuf.Timestamp lastUpdate = 4; string uptime = 4;
int64 commands = 5;
} }
message GetTextChannelsRequest{ message GetTextChannelsRequest{

View file

@ -2,7 +2,6 @@
using LinqToDB; using LinqToDB;
using LinqToDB.EntityFrameworkCore; using LinqToDB.EntityFrameworkCore;
using EllieBot.Common.ModuleBehaviors; using EllieBot.Common.ModuleBehaviors;
using EllieBot.Common.TypeReaders.Models;
using EllieBot.Modules.Permissions.Services; using EllieBot.Modules.Permissions.Services;
using EllieBot.Db.Models; using EllieBot.Db.Models;
using Newtonsoft.Json; using Newtonsoft.Json;

View file

@ -13,51 +13,48 @@ public static class GrpcApiExtensions
public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, IEService public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, IEService
{ {
private readonly IDiscordClient _client; private readonly DiscordSocketClient _client;
private readonly XpService _xp; private readonly XpService _xp;
private readonly ICurrencyService _cur; private readonly ICurrencyService _cur;
private readonly WaifuService _waifus; private readonly WaifuService _waifus;
private readonly ICoordinator _coord;
private readonly IStatsService _stats; private readonly IStatsService _stats;
private readonly IBotCache _cache; private readonly CommandHandler _cmdHandler;
public OtherSvc( public OtherSvc(
DiscordSocketClient client, DiscordSocketClient client,
XpService xp, XpService xp,
ICurrencyService cur, ICurrencyService cur,
WaifuService waifus, WaifuService waifus,
ICoordinator coord,
IStatsService stats, IStatsService stats,
IBotCache cache) CommandHandler cmdHandler)
{ {
_client = client; _client = client;
_xp = xp; _xp = xp;
_cur = cur; _cur = cur;
_waifus = waifus; _waifus = waifus;
_coord = coord;
_stats = stats; _stats = stats;
_cache = cache; _cmdHandler = cmdHandler;
} }
public ServerServiceDefinition Bind() public ServerServiceDefinition Bind()
=> GrpcOther.BindService(this); => GrpcOther.BindService(this);
[GrpcNoAuthRequired] [GrpcNoAuthRequired]
public override async Task<BotOnGuildReply> BotOnGuild(BotOnGuildRequest request, ServerCallContext context) public override Task<BotOnGuildReply> BotOnGuild(BotOnGuildRequest request, ServerCallContext context)
{ {
var guild = await _client.GetGuildAsync(request.GuildId); var guild = _client.GetGuild(request.GuildId);
var reply = new BotOnGuildReply var reply = new BotOnGuildReply
{ {
Success = guild is not null Success = guild is not null
}; };
return reply; return Task.FromResult(reply);
} }
public override async Task<GetRolesReply> GetRoles(GetRolesRequest request, ServerCallContext context) public override Task<GetRolesReply> GetRoles(GetRolesRequest request, ServerCallContext context)
{ {
var g = await _client.GetGuildAsync(request.GuildId); var g = _client.GetGuild(request.GuildId);
var roles = g?.Roles; var roles = g?.Roles;
var reply = new GetRolesReply(); var reply = new GetRolesReply();
reply.Roles.AddRange(roles?.Select(x => new RoleReply() reply.Roles.AddRange(roles?.Select(x => new RoleReply()
@ -66,16 +63,17 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, IEService
Name = x.Name, Name = x.Name,
Color = x.Color.ToString(), Color = x.Color.ToString(),
IconUrl = x.GetIconUrl() ?? string.Empty, IconUrl = x.GetIconUrl() ?? string.Empty,
}) ?? new List<RoleReply>()); })
?? new List<RoleReply>());
return reply; return Task.FromResult(reply);
} }
public override async Task<GetTextChannelsReply> GetTextChannels( public override async Task<GetTextChannelsReply> GetTextChannels(
GetTextChannelsRequest request, GetTextChannelsRequest request,
ServerCallContext context) ServerCallContext context)
{ {
var g = await _client.GetGuildAsync(request.GuildId); IGuild g = _client.GetGuild(request.GuildId);
var reply = new GetTextChannelsReply(); var reply = new GetTextChannelsReply();
var chs = await g.GetTextChannelsAsync(); var chs = await g.GetTextChannelsAsync();
@ -89,33 +87,6 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, IEService
return reply; return reply;
} }
[GrpcNoAuthRequired]
public override async Task<GetGuildsReply> GetGuilds(Empty request, ServerCallContext context)
{
var guilds = await _client.GetGuildsAsync(CacheMode.CacheOnly);
var reply = new GetGuildsReply();
var userId = context.GetUserId();
var toReturn = new List<IGuild>();
foreach (var g in guilds)
{
var user = await g.GetUserAsync(userId);
if (user is not null && user.GuildPermissions.Has(GuildPermission.Administrator))
toReturn.Add(g);
}
reply.Guilds.AddRange(toReturn
.Select(x => new GuildReply()
{
Id = x.Id,
Name = x.Name,
IconUrl = x.IconUrl
}));
return reply;
}
[GrpcNoAuthRequired] [GrpcNoAuthRequired]
public override async Task<CurrencyLbReply> GetCurrencyLb(GetLbRequest request, ServerCallContext context) public override async Task<CurrencyLbReply> GetCurrencyLb(GetLbRequest request, ServerCallContext context)
@ -123,16 +94,16 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, IEService
var users = await _cur.GetTopRichest(_client.CurrentUser.Id, request.Page, request.PerPage); var users = await _cur.GetTopRichest(_client.CurrentUser.Id, request.Page, request.PerPage);
var reply = new CurrencyLbReply(); var reply = new CurrencyLbReply();
var entries = users.Select(async x => var entries = users.Select(x =>
{ {
var user = await _client.GetUserAsync(x.UserId, CacheMode.CacheOnly); var user = _client.GetUser(x.UserId);
return new CurrencyLbEntryReply() return Task.FromResult(new CurrencyLbEntryReply()
{ {
Amount = x.CurrencyAmount, Amount = x.CurrencyAmount,
User = user?.ToString() ?? x.Username, User = user?.ToString() ?? x.Username,
UserId = x.UserId, UserId = x.UserId,
Avatar = user?.RealAvatarUrl().ToString() ?? x.RealAvatarUrl()?.ToString() Avatar = user?.RealAvatarUrl().ToString() ?? x.RealAvatarUrl()?.ToString()
}; });
}); });
reply.Entries.AddRange(await entries.WhenAll()); reply.Entries.AddRange(await entries.WhenAll());
@ -182,26 +153,66 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, IEService
} }
[GrpcNoAuthRequired] [GrpcNoAuthRequired]
public override async Task<GetShardStatusesReply> GetShardStatuses(Empty request, ServerCallContext context) public override async Task GetShardStats(
Empty request,
IServerStreamWriter<ShardStatsReply> responseStream,
ServerCallContext context)
{ {
var reply = new GetShardStatusesReply(); while (true)
await _cache.GetOrAddAsync<List<ShardStatus>>("coord:statuses",
() => Task.FromResult(_coord.GetAllShardStatuses().ToList())!,
TimeSpan.FromMinutes(1));
var shards = _coord.GetAllShardStatuses();
reply.Shards.AddRange(shards.Select(x => new ShardStatusReply()
{ {
Id = x.ShardId, var stats = new ShardStatsReply()
Status = x.ConnectionState.ToString(), {
GuildCount = x.GuildCount, Id = _client.ShardId,
LastUpdate = Timestamp.FromDateTime(x.LastUpdate), Commands = _stats.CommandsRan,
})); Uptime = _stats.GetUptimeString(),
Status = GetConnectionState(_client.ConnectionState),
GuildCount = _client.Guilds.Count,
};
await responseStream.WriteAsync(stats);
await Task.Delay(1000);
}
}
return reply; [GrpcNoAuthRequired]
public override async Task GetCommandFeed(
Empty request,
IServerStreamWriter<CommandFeedEntry> responseStream,
ServerCallContext context)
{
var taskCompletion = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
Task OnCommandExecuted(IUserMessage userMessage, CommandInfo commandInfo)
{
try
{
responseStream.WriteAsync(new()
{
Command = commandInfo.Name
});
}
catch
{
_cmdHandler.CommandExecuted -= OnCommandExecuted;
taskCompletion.TrySetResult(true);
}
return Task.CompletedTask;
}
_cmdHandler.CommandExecuted += OnCommandExecuted;
await taskCompletion.Task;
}
private string GetConnectionState(ConnectionState clientConnectionState)
{
return clientConnectionState switch
{
ConnectionState.Connected => "Connected",
ConnectionState.Connecting => "Connecting",
_ => "Disconnected"
};
} }
public override async Task<GetServerInfoReply> GetServerInfo(ServerInfoRequest request, ServerCallContext context) public override async Task<GetServerInfoReply> GetServerInfo(ServerInfoRequest request, ServerCallContext context)

View file

@ -14,10 +14,7 @@ public sealed partial class GrpcApiPermsInterceptor : Interceptor
_client = client; _client = client;
} }
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>( public async Task RequestHandler(ServerCallContext context)
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{ {
try try
{ {
@ -42,7 +39,7 @@ public sealed partial class GrpcApiPermsInterceptor : Interceptor
// if the method is explicitly marked as not requiring auth // if the method is explicitly marked as not requiring auth
if (_noAuthRequired.Contains(method)) if (_noAuthRequired.Contains(method))
return await continuation(request, context); return;
// otherwise the method requires auth, and if it requires auth then the guildid has to be specified // otherwise the method requires auth, and if it requires auth then the guildid has to be specified
if (string.IsNullOrWhiteSpace(gidString)) if (string.IsNullOrWhiteSpace(gidString))
@ -61,8 +58,6 @@ public sealed partial class GrpcApiPermsInterceptor : Interceptor
// if not then use the default, which is Administrator permission // if not then use the default, which is Administrator permission
await EnsureUserHasPermission(guildId, userId, DEFAULT_PERMISSION); await EnsureUserHasPermission(guildId, userId, DEFAULT_PERMISSION);
} }
return await continuation(request, context);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -83,4 +78,42 @@ public sealed partial class GrpcApiPermsInterceptor : Interceptor
throw new RpcException(new Status(StatusCode.PermissionDenied, throw new RpcException(new Status(StatusCode.PermissionDenied,
$"You need {perm} permission to use this method")); $"You need {perm} permission to use this method"));
} }
public override async Task<TResponse> ClientStreamingServerHandler<TRequest, TResponse>(
IAsyncStreamReader<TRequest> requestStream,
ServerCallContext context,
ClientStreamingServerMethod<TRequest, TResponse> continuation)
{
await RequestHandler(context);
return await continuation(requestStream, context);
}
public override async Task DuplexStreamingServerHandler<TRequest, TResponse>(
IAsyncStreamReader<TRequest> requestStream,
IServerStreamWriter<TResponse> responseStream,
ServerCallContext context,
DuplexStreamingServerMethod<TRequest, TResponse> continuation)
{
await RequestHandler(context);
await continuation(requestStream, responseStream, context);
}
public override async Task ServerStreamingServerHandler<TRequest, TResponse>(
TRequest request,
IServerStreamWriter<TResponse> responseStream,
ServerCallContext context,
ServerStreamingServerMethod<TRequest, TResponse> continuation)
{
await RequestHandler(context);
await continuation(request, responseStream, context);
}
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
await RequestHandler(context);
return await continuation(request, context);
}
} }

View file

@ -15,7 +15,7 @@ color:
pending: faa61a pending: faa61a
# Default bot language. It has to be in the list of supported languages (.langli) # Default bot language. It has to be in the list of supported languages (.langli)
defaultLocale: en-US 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 # Allowed values: Simple, Normal, None
consoleOutputType: Normal consoleOutputType: Normal
# Whether the bot will check for new releases every hour # Whether the bot will check for new releases every hour