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";
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{

View file

@ -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;

View file

@ -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<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
{
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 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<RoleReply>());
})
?? new List<RoleReply>());
return reply;
return Task.FromResult(reply);
}
public override async Task<GetTextChannelsReply> GetTextChannels(
GetTextChannelsRequest request,
ServerCallContext context)
{
var g = await _client.GetGuildAsync(request.GuildId);
IGuild g = _client.GetGuild(request.GuildId);
var reply = new GetTextChannelsReply();
var chs = await g.GetTextChannelsAsync();
@ -89,33 +87,6 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IGrpcSvc, IEService
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]
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 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<GetShardStatusesReply> GetShardStatuses(Empty request, ServerCallContext context)
public override async Task GetShardStats(
Empty request,
IServerStreamWriter<ShardStatsReply> responseStream,
ServerCallContext context)
{
var reply = new GetShardStatusesReply();
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()
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<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)

View file

@ -14,10 +14,7 @@ public sealed partial class GrpcApiPermsInterceptor : Interceptor
_client = client;
}
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> 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<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
# 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