Added grpc api, perm system

grpc api config in creds
This commit is contained in:
Toastie 2024-10-03 18:46:10 +13:00
parent de97213046
commit 391d2e43e8
Signed by: toastie_t0ast
GPG key ID: 27F3B6855AFD40A4
14 changed files with 410 additions and 42 deletions

View file

@ -9,7 +9,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" /> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" /> <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" PrivateAssets="all" GeneratePathProperty="true" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" PrivateAssets="all" GeneratePathProperty="true" />
</ItemGroup> </ItemGroup>

View file

@ -0,0 +1,148 @@
#nullable enable
using System.CodeDom.Compiler;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using Newtonsoft.Json;
namespace EllieBot.Generators
{
public readonly record struct MethodPermData
{
public readonly string Name;
public readonly string Value;
public MethodPermData(string name, string value)
{
Name = name;
Value = value;
}
}
[Generator]
public class GrpcApiPermGenerator : IIncrementalGenerator
{
public const string Attribute =
"""
namespace EllieBot.GrpcApi;
[System.AttributeUsage(System.AttributeTargets.Method)]
public class GrpcApiPermAttribute : System.Attribute
{
public GuildPerm Value { get; }
public GrpcApiPermAttribute(GuildPerm value) => Value = value;
}
""";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterPostInitializationOutput(ctx => ctx.AddSource("GrpcApiPermAttribute.cs",
SourceText.From(Attribute, Encoding.UTF8)));
var enumsToGenerate = context.SyntaxProvider
.ForAttributeWithMetadataName(
"EllieBot.GrpcApi.GrpcApiPermAttribute",
predicate: static (s, _) => s is MethodDeclarationSyntax,
transform: static (ctx, _) => GetMethodSemanticTargets(ctx.SemanticModel, ctx.TargetNode))
.Where(static m => m is not null)
.Select(static (x, _) => x.Value)
.Collect();
context.RegisterSourceOutput(enumsToGenerate,
static (spc, source) => Execute(source, spc));
}
private static MethodPermData? GetMethodSemanticTargets(SemanticModel model, SyntaxNode node)
{
var method = (MethodDeclarationSyntax)node;
var name = method.Identifier.Text;
var attr = method.AttributeLists
.SelectMany(x => x.Attributes)
.FirstOrDefault();
// .FirstOrDefault(x => x.Name.ToString() == "GrpcApiPermAttribute");
if (attr is null)
return null;
// if (model.GetSymbolInfo(attr).Symbol is not IMethodSymbol attrSymbol)
// return null;
return new MethodPermData(name, attr.ArgumentList.Arguments[0].ToString());
// return new MethodPermData(name, attrSymbol.Parameters[0].ContainingType.ToDisplayString() + "." + attrSymbol.Parameters[0].Name);
}
private static void Execute(ImmutableArray<MethodPermData> fields, SourceProductionContext ctx)
{
using (var stringWriter = new StringWriter())
using (var sw = new IndentedTextWriter(stringWriter))
{
sw.WriteLine("using System.Collections.Frozen;");
sw.WriteLine();
sw.WriteLine("namespace EllieBot.GrpcApi;");
sw.WriteLine();
sw.WriteLine("public partial class PermsInterceptor");
sw.WriteLine("{");
sw.Indent++;
sw.WriteLine("public static FrozenDictionary<string, GuildPerm> perms = new Dictionary<string, GuildPerm>()");
sw.WriteLine("{");
sw.Indent++;
foreach (var field in fields)
{
sw.WriteLine("{{ \"{0}\", {1} }},", field!.Name, field!.Value);
}
sw.Indent--;
sw.WriteLine("}.ToFrozenDictionary();");
sw.Indent--;
sw.WriteLine("}");
sw.Flush();
ctx.AddSource("GrpcApiInterceptor.g.cs", stringWriter.ToString());
}
}
private List<TranslationPair> GetFields(string? dataText)
{
if (string.IsNullOrWhiteSpace(dataText))
return new();
Dictionary<string, string> data;
try
{
var output = JsonConvert.DeserializeObject<Dictionary<string, string>>(dataText!);
if (output is null)
return new();
data = output;
}
catch
{
Debug.WriteLine("Failed parsing responses file.");
return new();
}
var list = new List<TranslationPair>();
foreach (var entry in data)
{
list.Add(new(
entry.Key,
entry.Value
));
}
return list;
}
}
}

View file

@ -8,6 +8,8 @@ import "google/protobuf/timestamp.proto";
package other; package other;
service GrpcOther { service GrpcOther {
rpc GetGuilds(google.protobuf.Empty) returns (GetGuildsReply);
rpc GetTextChannels(GetTextChannelsRequest) returns (GetTextChannelsReply); rpc GetTextChannels(GetTextChannelsRequest) returns (GetTextChannelsReply);
rpc GetCurrencyLb(GetLbRequest) returns (CurrencyLbReply); rpc GetCurrencyLb(GetLbRequest) returns (CurrencyLbReply);
@ -15,6 +17,17 @@ service GrpcOther {
rpc GetWaifuLb(GetLbRequest) returns (WaifuLbReply); rpc GetWaifuLb(GetLbRequest) returns (WaifuLbReply);
rpc GetShardStatuses(google.protobuf.Empty) returns (GetShardStatusesReply); rpc GetShardStatuses(google.protobuf.Empty) returns (GetShardStatusesReply);
rpc GetServerInfo(ServerInfoRequest) returns (GetServerInfoReply);
}
message GetGuildsReply {
repeated GuildReply guilds = 1;
}
message GuildReply {
uint64 id = 1;
string name = 2;
string iconUrl = 3;
} }
message GetShardStatusesReply { message GetShardStatusesReply {
@ -79,3 +92,46 @@ message WaifuLbEntry {
int64 value = 3; int64 value = 3;
bool isMutual = 4; bool isMutual = 4;
} }
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;
}

View file

@ -1,6 +1,7 @@
#nullable disable #nullable disable
using LinqToDB; using LinqToDB;
using LinqToDB.EntityFrameworkCore; using LinqToDB.EntityFrameworkCore;
using LinqToDB.Tools;
using EllieBot.Db.Models; using EllieBot.Db.Models;
using EllieBot.Modules.Gambling.Bank; using EllieBot.Modules.Gambling.Bank;
using EllieBot.Modules.Gambling.Common; using EllieBot.Modules.Gambling.Common;
@ -625,8 +626,6 @@ public partial class Gambling : GamblingModule<GamblingService>
var (opts, _) = OptionsParser.ParseFrom(new LbOpts(), args); var (opts, _) = OptionsParser.ParseFrom(new LbOpts(), args);
// List<DiscordUser> cleanRichest;
// it's pointless to have clean on dm context
if (ctx.Guild is null) if (ctx.Guild is null)
{ {
opts.Clean = false; opts.Clean = false;
@ -640,10 +639,16 @@ public partial class Gambling : GamblingModule<GamblingService>
await ctx.Channel.TriggerTypingAsync(); await ctx.Channel.TriggerTypingAsync();
await _tracker.EnsureUsersDownloadedAsync(ctx.Guild); await _tracker.EnsureUsersDownloadedAsync(ctx.Guild);
await using var uow = _db.GetDbContext(); var users = ((SocketGuild)ctx.Guild).Users.Map(x => x.Id);
var perPage = 9;
var cleanRichest = await uow.Set<DiscordUser>() await using var uow = _db.GetDbContext();
.GetTopRichest(_client.CurrentUser.Id, 0, 1000); var cleanRichest = await uow.GetTable<DiscordUser>()
.Where(x => x.UserId.In(users))
.OrderByDescending(x => x.CurrencyAmount)
.Skip(page * perPage)
.Take(perPage)
.ToListAsync();
var sg = (SocketGuild)ctx.Guild!; var sg = (SocketGuild)ctx.Guild!;
return cleanRichest.Where(x => sg.GetUser(x.UserId) is not null).ToList(); return cleanRichest.Where(x => sg.GetUser(x.UserId) is not null).ToList();
@ -661,7 +666,6 @@ public partial class Gambling : GamblingModule<GamblingService>
await Response() await Response()
.Paginated() .Paginated()
.PageItems(GetTopRichest) .PageItems(GetTopRichest)
.TotalElements(900)
.PageSize(9) .PageSize(9)
.CurrentPage(page) .CurrentPage(page)
.Page((toSend, curPage) => .Page((toSend, curPage) =>

View file

@ -14,6 +14,7 @@ public class ExprsSvc : GrpcExprs.GrpcExprsBase, IEService
_svc = svc; _svc = svc;
} }
[GrpcApiPerm(GuildPerm.Administrator)]
public override async Task<AddExprReply> AddExpr(AddExprRequest request, ServerCallContext context) public override async Task<AddExprReply> AddExpr(AddExprRequest request, ServerCallContext context)
{ {
EllieExpression expr; EllieExpression expr;
@ -44,6 +45,7 @@ public class ExprsSvc : GrpcExprs.GrpcExprsBase, IEService
}; };
} }
[GrpcApiPerm(GuildPerm.Administrator)]
public override async Task<GetExprsReply> GetExprs(GetExprsRequest request, ServerCallContext context) public override async Task<GetExprsReply> GetExprs(GetExprsRequest request, ServerCallContext context)
{ {
var (exprs, totalCount) = await _svc.FindExpressionsAsync(request.GuildId, request.Query, request.Page); var (exprs, totalCount) = await _svc.FindExpressionsAsync(request.GuildId, request.Query, request.Page);
@ -64,6 +66,7 @@ public class ExprsSvc : GrpcExprs.GrpcExprsBase, IEService
return reply; return reply;
} }
[GrpcApiPerm(GuildPerm.Administrator)]
public override async Task<Empty> DeleteExpr(DeleteExprRequest request, ServerCallContext context) public override async Task<Empty> DeleteExpr(DeleteExprRequest request, ServerCallContext context)
{ {
await _svc.DeleteAsync(request.GuildId, new kwum(request.Id)); await _svc.DeleteAsync(request.GuildId, new kwum(request.Id));

View file

@ -30,10 +30,11 @@ public sealed class GreetByeSvc : GrpcGreet.GrpcGreetBase, IEService
Message = conf.MessageText, Message = conf.MessageText,
Type = (GrpcGreetType)conf.GreetType, Type = (GrpcGreetType)conf.GreetType,
ChannelId = conf.ChannelId ?? 0, ChannelId = conf.ChannelId ?? 0,
IsEnabled = conf.IsEnabled IsEnabled = conf.IsEnabled,
}; };
} }
[GrpcApiPerm(GuildPerm.Administrator)]
public override async Task<GetGreetReply> GetGreetSettings(GetGreetRequest request, ServerCallContext context) public override async Task<GetGreetReply> GetGreetSettings(GetGreetRequest request, ServerCallContext context)
{ {
var guildId = request.GuildId; var guildId = request.GuildId;
@ -53,6 +54,7 @@ public sealed class GreetByeSvc : GrpcGreet.GrpcGreetBase, IEService
}; };
} }
[GrpcApiPerm(GuildPerm.Administrator)]
public override async Task<UpdateGreetReply> UpdateGreet(UpdateGreetRequest request, ServerCallContext context) public override async Task<UpdateGreetReply> UpdateGreet(UpdateGreetRequest request, ServerCallContext context)
{ {
var gid = request.GuildId; var gid = request.GuildId;
@ -68,6 +70,7 @@ public sealed class GreetByeSvc : GrpcGreet.GrpcGreetBase, IEService
}; };
} }
[GrpcApiPerm(GuildPerm.Administrator)]
public override Task<TestGreetReply> TestGreet(TestGreetRequest request, ServerCallContext context) public override Task<TestGreetReply> TestGreet(TestGreetRequest request, ServerCallContext context)
=> TestGreet(request.GuildId, request.ChannelId, request.UserId, request.Type); => TestGreet(request.GuildId, request.ChannelId, request.UserId, request.Type);

View file

@ -5,6 +5,12 @@ using EllieBot.Modules.Xp.Services;
namespace EllieBot.GrpcApi; namespace EllieBot.GrpcApi;
public static class GrpcApiExtensions
{
public static ulong GetUserId(this ServerCallContext context)
=> ulong.Parse(context.RequestHeaders.FirstOrDefault(x => x.Key == "userid")!.Value);
}
public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IEService public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IEService
{ {
private readonly IDiscordClient _client; private readonly IDiscordClient _client;
@ -12,22 +18,54 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IEService
private readonly ICurrencyService _cur; private readonly ICurrencyService _cur;
private readonly WaifuService _waifus; private readonly WaifuService _waifus;
private readonly ICoordinator _coord; private readonly ICoordinator _coord;
private readonly IStatsService _stats;
public OtherSvc( public OtherSvc(
DiscordSocketClient client, DiscordSocketClient client,
XpService xp, XpService xp,
ICurrencyService cur, ICurrencyService cur,
WaifuService waifus, WaifuService waifus,
ICoordinator coord) ICoordinator coord,
IStatsService stats)
{ {
_client = client; _client = client;
_xp = xp; _xp = xp;
_cur = cur; _cur = cur;
_waifus = waifus; _waifus = waifus;
_coord = coord; _coord = coord;
_stats = stats;
} }
public override async Task<GetTextChannelsReply> GetTextChannels(GetTextChannelsRequest request, ServerCallContext context) 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, CacheMode.AllowDownload);
if (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;
}
[GrpcApiPerm(GuildPerm.Administrator)]
public override async Task<GetTextChannelsReply> GetTextChannels(
GetTextChannelsRequest request,
ServerCallContext context)
{ {
var g = await _client.GetGuildAsync(request.GuildId); var g = await _client.GetGuildAsync(request.GuildId);
var reply = new GetTextChannelsReply(); var reply = new GetTextChannelsReply();
@ -54,9 +92,9 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IEService
return new CurrencyLbEntryReply() return new CurrencyLbEntryReply()
{ {
Amount = x.CurrencyAmount, Amount = x.CurrencyAmount,
User = user.ToString(), User = user?.ToString() ?? x.Username,
UserId = x.UserId, UserId = x.UserId,
Avatar = user.RealAvatarUrl().ToString() Avatar = user?.RealAvatarUrl().ToString() ?? x.RealAvatarUrl()?.ToString()
}; };
}); });
@ -96,7 +134,7 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IEService
var reply = new WaifuLbReply(); var reply = new WaifuLbReply();
reply.Entries.AddRange(waifus.Select(x => new WaifuLbEntry() reply.Entries.AddRange(waifus.Select(x => new WaifuLbEntry()
{ {
ClaimedBy = x.Claimer, ClaimedBy = x.Claimer ?? string.Empty,
IsMutual = x.Claimer == x.Affinity, IsMutual = x.Claimer == x.Affinity,
Value = x.Price, Value = x.Price,
User = x.Username, User = x.Username,
@ -108,6 +146,7 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IEService
{ {
var reply = new GetShardStatusesReply(); var reply = new GetShardStatusesReply();
// todo cache
var shards = _coord.GetAllShardStatuses(); var shards = _coord.GetAllShardStatuses();
reply.Shards.AddRange(shards.Select(x => new ShardStatusReply() reply.Shards.AddRange(shards.Select(x => new ShardStatusReply()
@ -120,4 +159,41 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IEService
return Task.FromResult(reply); return Task.FromResult(reply);
} }
[GrpcApiPerm(GuildPerm.Administrator)]
public override async Task<GetServerInfoReply> GetServerInfo(ServerInfoRequest request, ServerCallContext context)
{
var info = await _stats.GetGuildInfoAsync(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 reply;
}
} }

View file

@ -1,5 +1,5 @@
using Grpc; using Grpc.Core;
using Grpc.Core; using Grpc.Core.Interceptors;
using EllieBot.Common.ModuleBehaviors; using EllieBot.Common.ModuleBehaviors;
namespace EllieBot.GrpcApi; namespace EllieBot.GrpcApi;
@ -9,51 +9,61 @@ public class GrpcApiService : IEService, IReadyExecutor
private Server? _app; private Server? _app;
private static readonly bool _isEnabled = true; private static readonly bool _isEnabled = true;
private readonly string _host = "localhost";
private readonly int _port = 5030;
private readonly ServerCredentials _creds = ServerCredentials.Insecure;
private readonly DiscordSocketClient _client;
private readonly OtherSvc _other; private readonly OtherSvc _other;
private readonly ExprsSvc _exprs; private readonly ExprsSvc _exprs;
private readonly GreetByeSvc _greet; private readonly GreetByeSvc _greet;
private readonly IBotCredsProvider _creds;
public GrpcApiService( public GrpcApiService(
DiscordSocketClient client,
OtherSvc other, OtherSvc other,
ExprsSvc exprs, ExprsSvc exprs,
GreetByeSvc greet) GreetByeSvc greet,
IBotCredsProvider creds)
{ {
_client = client;
_other = other; _other = other;
_exprs = exprs; _exprs = exprs;
_greet = greet; _greet = greet;
_creds = creds;
} }
public async Task OnReadyAsync() public async Task OnReadyAsync()
{ {
if (!_isEnabled) var creds = _creds.GetCreds();
if (creds.GrpcApi is null || creds.GrpcApi.Enabled)
return; return;
try try
{ {
_app = new() var host = creds.GrpcApi.Host;
var port = creds.GrpcApi.Port + _client.ShardId;
var interceptor = new PermsInterceptor(_client);
_app = new Server()
{ {
Services = Services =
{ {
GrpcOther.BindService(_other), GrpcOther.BindService(_other).Intercept(interceptor),
GrpcExprs.BindService(_exprs), GrpcExprs.BindService(_exprs).Intercept(interceptor),
GrpcGreet.BindService(_greet) GrpcGreet.BindService(_greet).Intercept(interceptor),
}, },
Ports = Ports =
{ {
new(_host, _port, _creds), new(host, port, ServerCredentials.Insecure),
} }
}; };
_app.Start(); _app.Start();
Log.Information("Grpc Api Server started on port {Host}:{Port}", host, port);
} }
finally catch
{ {
_app?.ShutdownAsync().GetAwaiter().GetResult(); _app?.ShutdownAsync().GetAwaiter().GetResult();
} }
Log.Information("Grpc Api Server started on port {Host}:{Port}", _host, _port);
} }
} }

View file

@ -0,0 +1,67 @@
using Grpc.Core;
using Grpc.Core.Interceptors;
namespace EllieBot.GrpcApi;
public sealed partial class PermsInterceptor : Interceptor
{
private readonly DiscordSocketClient _client;
public PermsInterceptor(DiscordSocketClient client)
{
_client = client;
Log.Information("interceptor created");
}
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
try
{
Log.Information("Starting receiving call. Type/Method: {Type} / {Method}",
MethodType.Unary,
context.Method);
// get metadata
var metadata = context
.RequestHeaders
.ToDictionary(x => x.Key, x => x.Value);
var method = context.Method[(context.Method.LastIndexOf('/') + 1)..];
if (perms.TryGetValue(method, out var perm))
{
Log.Information("Required permission for {Method} is {Perm}",
method,
perm);
var userId = ulong.Parse(metadata["userid"]);
var guildId = ulong.Parse(metadata["guildid"]);
IGuild guild = _client.GetGuild(guildId);
var user = guild is null ? null : await guild.GetUserAsync(userId);
if (user is null)
throw new RpcException(new Status(StatusCode.NotFound, "User not found"));
if (!user.GuildPermissions.Has(perm))
throw new RpcException(new Status(StatusCode.PermissionDenied,
$"You need {perm} permission to use this method"));
}
else
{
Log.Information("No permission required for {Method}", method);
}
return await continuation(request, context);
}
catch (Exception ex)
{
Log.Error(ex, "Error thrown by {ContextMethod}", context.Method);
throw;
}
}
}

View file

@ -29,6 +29,7 @@ public interface IBotCredentials
string TwitchClientSecret { get; set; } string TwitchClientSecret { get; set; }
GoogleApiConfig Google { get; set; } GoogleApiConfig Google { get; set; }
BotCacheImplemenation BotCache { get; set; } BotCacheImplemenation BotCache { get; set; }
Creds.GrpcApiConfig GrpcApi { get; set; }
} }
public interface IVotesSettings public interface IVotesSettings

View file

@ -162,7 +162,7 @@ public sealed class Creds : IBotCredentials
We don't provide support for this. We don't provide support for this.
If you leave certPath empty, the api will run on http. If you leave certPath empty, the api will run on http.
""")] """)]
public ApiConfig Api { get; set; } public GrpcApiConfig GrpcApi { get; set; }
public Creds() public Creds()
{ {
@ -189,7 +189,7 @@ public sealed class Creds : IBotCredentials
RestartCommand = new RestartConfig(); RestartCommand = new RestartConfig();
Google = new GoogleApiConfig(); Google = new GoogleApiConfig();
Api = new ApiConfig(); GrpcApi = new GrpcApiConfig();
} }
public class DbOptions public class DbOptions
@ -284,7 +284,7 @@ public sealed class Creds : IBotCredentials
} }
} }
public sealed record ApiConfig public sealed record GrpcApiConfig
{ {
public bool Enabled { get; set; } = false; public bool Enabled { get; set; } = false;
public string CertPath { get; set; } = string.Empty; public string CertPath { get; set; } = string.Empty;

View file

@ -90,8 +90,7 @@ public class RemoteGrpcCoordinator : ICoordinator, IReadyExecutor
{ {
if (!gracefulImminent) if (!gracefulImminent)
{ {
Log.Warning(ex, Log.Warning(ex, "Hearbeat failed and graceful shutdown was not expected: {Message}",
"Hearbeat failed and graceful shutdown was not expected: {Message}",
ex.Message); ex.Message);
break; break;
} }

View file

@ -49,8 +49,8 @@ public interface IStatsService
/// </summary> /// </summary>
double GetPrivateMemoryMegabytes(); double GetPrivateMemoryMegabytes();
GuildInfo GetGuildInfo(string name); GuildInfo GetGuildInfoAsync(string name);
GuildInfo GetGuildInfo(ulong id); Task<GuildInfo> GetGuildInfoAsync(ulong id);
} }
public record struct GuildInfo public record struct GuildInfo

View file

@ -180,19 +180,20 @@ public sealed class StatsService : IStatsService, IReadyExecutor, IEService
return _currentProcess.PrivateMemorySize64 / 1.Megabytes(); return _currentProcess.PrivateMemorySize64 / 1.Megabytes();
} }
public GuildInfo GetGuildInfo(string name) public GuildInfo GetGuildInfoAsync(string name)
=> throw new NotImplementedException(); => throw new NotImplementedException();
public GuildInfo GetGuildInfo(ulong id) public async Task<GuildInfo> GetGuildInfoAsync(ulong id)
{ {
var g = _client.GetGuild(id); var g = _client.GetGuild(id);
var ig = (IGuild)g;
return new GuildInfo() return new GuildInfo()
{ {
Id = g.Id, Id = g.Id,
IconUrl = g.IconUrl, IconUrl = g.IconUrl,
Name = g.Name, Name = g.Name,
Owner = g.Owner.Username, Owner = (await ig.GetUserAsync(g.OwnerId))?.Username ?? "Unknown",
OwnerId = g.OwnerId, OwnerId = g.OwnerId,
CreatedAt = g.CreatedAt.UtcDateTime, CreatedAt = g.CreatedAt.UtcDateTime,
VoiceChannels = g.VoiceChannels.Count, VoiceChannels = g.VoiceChannels.Count,