diff --git a/src/EllieBot.Generators/EllieBot.Generators.csproj b/src/EllieBot.Generators/EllieBot.Generators.csproj
index 1dbbc1d..4e94449 100644
--- a/src/EllieBot.Generators/EllieBot.Generators.csproj
+++ b/src/EllieBot.Generators/EllieBot.Generators.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/src/EllieBot.Generators/GrpcApiPermGenerator.cs b/src/EllieBot.Generators/GrpcApiPermGenerator.cs
new file mode 100644
index 0000000..545fa3a
--- /dev/null
+++ b/src/EllieBot.Generators/GrpcApiPermGenerator.cs
@@ -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 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 perms = new Dictionary()");
+ 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 GetFields(string? dataText)
+ {
+ if (string.IsNullOrWhiteSpace(dataText))
+ return new();
+
+ Dictionary data;
+ try
+ {
+ var output = JsonConvert.DeserializeObject>(dataText!);
+ if (output is null)
+ return new();
+
+ data = output;
+ }
+ catch
+ {
+ Debug.WriteLine("Failed parsing responses file.");
+ return new();
+ }
+
+ var list = new List();
+ foreach (var entry in data)
+ {
+ list.Add(new(
+ entry.Key,
+ entry.Value
+ ));
+ }
+
+ return list;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/EllieBot.GrpcApiBase/protos/other.proto b/src/EllieBot.GrpcApiBase/protos/other.proto
index 3a7aecf..9a5f9f8 100644
--- a/src/EllieBot.GrpcApiBase/protos/other.proto
+++ b/src/EllieBot.GrpcApiBase/protos/other.proto
@@ -8,13 +8,26 @@ import "google/protobuf/timestamp.proto";
package other;
service GrpcOther {
+
+ rpc GetGuilds(google.protobuf.Empty) returns (GetGuildsReply);
rpc GetTextChannels(GetTextChannelsRequest) returns (GetTextChannelsReply);
-
+
rpc GetCurrencyLb(GetLbRequest) returns (CurrencyLbReply);
rpc GetXpLb(GetLbRequest) returns (XpLbReply);
rpc GetWaifuLb(GetLbRequest) returns (WaifuLbReply);
-
+
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 {
@@ -24,7 +37,7 @@ message GetShardStatusesReply {
message ShardStatusReply {
int32 id = 1;
string status = 2;
-
+
int32 guildCount = 3;
google.protobuf.Timestamp lastUpdate = 4;
}
@@ -79,3 +92,46 @@ message WaifuLbEntry {
int64 value = 3;
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;
+}
diff --git a/src/EllieBot/Modules/Gambling/Gambling.cs b/src/EllieBot/Modules/Gambling/Gambling.cs
index 64fedf5..427274f 100644
--- a/src/EllieBot/Modules/Gambling/Gambling.cs
+++ b/src/EllieBot/Modules/Gambling/Gambling.cs
@@ -1,6 +1,7 @@
#nullable disable
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
+using LinqToDB.Tools;
using EllieBot.Db.Models;
using EllieBot.Modules.Gambling.Bank;
using EllieBot.Modules.Gambling.Common;
@@ -625,8 +626,6 @@ public partial class Gambling : GamblingModule
var (opts, _) = OptionsParser.ParseFrom(new LbOpts(), args);
- // List cleanRichest;
- // it's pointless to have clean on dm context
if (ctx.Guild is null)
{
opts.Clean = false;
@@ -640,10 +639,16 @@ public partial class Gambling : GamblingModule
await ctx.Channel.TriggerTypingAsync();
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()
- .GetTopRichest(_client.CurrentUser.Id, 0, 1000);
+ await using var uow = _db.GetDbContext();
+ var cleanRichest = await uow.GetTable()
+ .Where(x => x.UserId.In(users))
+ .OrderByDescending(x => x.CurrencyAmount)
+ .Skip(page * perPage)
+ .Take(perPage)
+ .ToListAsync();
var sg = (SocketGuild)ctx.Guild!;
return cleanRichest.Where(x => sg.GetUser(x.UserId) is not null).ToList();
@@ -661,7 +666,6 @@ public partial class Gambling : GamblingModule
await Response()
.Paginated()
.PageItems(GetTopRichest)
- .TotalElements(900)
.PageSize(9)
.CurrentPage(page)
.Page((toSend, curPage) =>
diff --git a/src/EllieBot/Services/GrpcApi/ExprsSvc.cs b/src/EllieBot/Services/GrpcApi/ExprsSvc.cs
index 57c49d9..970c3c3 100644
--- a/src/EllieBot/Services/GrpcApi/ExprsSvc.cs
+++ b/src/EllieBot/Services/GrpcApi/ExprsSvc.cs
@@ -14,6 +14,7 @@ public class ExprsSvc : GrpcExprs.GrpcExprsBase, IEService
_svc = svc;
}
+ [GrpcApiPerm(GuildPerm.Administrator)]
public override async Task AddExpr(AddExprRequest request, ServerCallContext context)
{
EllieExpression expr;
@@ -44,6 +45,7 @@ public class ExprsSvc : GrpcExprs.GrpcExprsBase, IEService
};
}
+ [GrpcApiPerm(GuildPerm.Administrator)]
public override async Task GetExprs(GetExprsRequest request, ServerCallContext context)
{
var (exprs, totalCount) = await _svc.FindExpressionsAsync(request.GuildId, request.Query, request.Page);
@@ -64,6 +66,7 @@ public class ExprsSvc : GrpcExprs.GrpcExprsBase, IEService
return reply;
}
+ [GrpcApiPerm(GuildPerm.Administrator)]
public override async Task DeleteExpr(DeleteExprRequest request, ServerCallContext context)
{
await _svc.DeleteAsync(request.GuildId, new kwum(request.Id));
diff --git a/src/EllieBot/Services/GrpcApi/GreetByeSvc.cs b/src/EllieBot/Services/GrpcApi/GreetByeSvc.cs
index c0fec41..a617eea 100644
--- a/src/EllieBot/Services/GrpcApi/GreetByeSvc.cs
+++ b/src/EllieBot/Services/GrpcApi/GreetByeSvc.cs
@@ -30,10 +30,11 @@ public sealed class GreetByeSvc : GrpcGreet.GrpcGreetBase, IEService
Message = conf.MessageText,
Type = (GrpcGreetType)conf.GreetType,
ChannelId = conf.ChannelId ?? 0,
- IsEnabled = conf.IsEnabled
+ IsEnabled = conf.IsEnabled,
};
}
+ [GrpcApiPerm(GuildPerm.Administrator)]
public override async Task GetGreetSettings(GetGreetRequest request, ServerCallContext context)
{
var guildId = request.GuildId;
@@ -53,6 +54,7 @@ public sealed class GreetByeSvc : GrpcGreet.GrpcGreetBase, IEService
};
}
+ [GrpcApiPerm(GuildPerm.Administrator)]
public override async Task UpdateGreet(UpdateGreetRequest request, ServerCallContext context)
{
var gid = request.GuildId;
@@ -68,6 +70,7 @@ public sealed class GreetByeSvc : GrpcGreet.GrpcGreetBase, IEService
};
}
+ [GrpcApiPerm(GuildPerm.Administrator)]
public override Task TestGreet(TestGreetRequest request, ServerCallContext context)
=> TestGreet(request.GuildId, request.ChannelId, request.UserId, request.Type);
diff --git a/src/EllieBot/Services/GrpcApi/OtherSvc.cs b/src/EllieBot/Services/GrpcApi/OtherSvc.cs
index f720803..12143ca 100644
--- a/src/EllieBot/Services/GrpcApi/OtherSvc.cs
+++ b/src/EllieBot/Services/GrpcApi/OtherSvc.cs
@@ -5,6 +5,12 @@ using EllieBot.Modules.Xp.Services;
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
{
private readonly IDiscordClient _client;
@@ -12,22 +18,54 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IEService
private readonly ICurrencyService _cur;
private readonly WaifuService _waifus;
private readonly ICoordinator _coord;
+ private readonly IStatsService _stats;
public OtherSvc(
DiscordSocketClient client,
XpService xp,
ICurrencyService cur,
WaifuService waifus,
- ICoordinator coord)
+ ICoordinator coord,
+ IStatsService stats)
{
_client = client;
_xp = xp;
_cur = cur;
_waifus = waifus;
_coord = coord;
+ _stats = stats;
}
- public override async Task GetTextChannels(GetTextChannelsRequest request, ServerCallContext context)
+ 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, 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 GetTextChannels(
+ GetTextChannelsRequest request,
+ ServerCallContext context)
{
var g = await _client.GetGuildAsync(request.GuildId);
var reply = new GetTextChannelsReply();
@@ -54,9 +92,9 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IEService
return new CurrencyLbEntryReply()
{
Amount = x.CurrencyAmount,
- User = user.ToString(),
+ User = user?.ToString() ?? x.Username,
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();
reply.Entries.AddRange(waifus.Select(x => new WaifuLbEntry()
{
- ClaimedBy = x.Claimer,
+ ClaimedBy = x.Claimer ?? string.Empty,
IsMutual = x.Claimer == x.Affinity,
Value = x.Price,
User = x.Username,
@@ -108,6 +146,7 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IEService
{
var reply = new GetShardStatusesReply();
+ // todo cache
var shards = _coord.GetAllShardStatuses();
reply.Shards.AddRange(shards.Select(x => new ShardStatusReply()
@@ -120,4 +159,41 @@ public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IEService
return Task.FromResult(reply);
}
-}
+
+ [GrpcApiPerm(GuildPerm.Administrator)]
+ public override async Task 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;
+ }
+}
\ No newline at end of file
diff --git a/src/EllieBot/Services/GrpcApiService.cs b/src/EllieBot/Services/GrpcApiService.cs
index fe1f1e1..f16321a 100644
--- a/src/EllieBot/Services/GrpcApiService.cs
+++ b/src/EllieBot/Services/GrpcApiService.cs
@@ -1,5 +1,5 @@
-using Grpc;
-using Grpc.Core;
+using Grpc.Core;
+using Grpc.Core.Interceptors;
using EllieBot.Common.ModuleBehaviors;
namespace EllieBot.GrpcApi;
@@ -9,51 +9,61 @@ public class GrpcApiService : IEService, IReadyExecutor
private Server? _app;
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 ExprsSvc _exprs;
private readonly GreetByeSvc _greet;
+ private readonly IBotCredsProvider _creds;
public GrpcApiService(
+ DiscordSocketClient client,
OtherSvc other,
ExprsSvc exprs,
- GreetByeSvc greet)
+ GreetByeSvc greet,
+ IBotCredsProvider creds)
{
+ _client = client;
_other = other;
_exprs = exprs;
_greet = greet;
+ _creds = creds;
}
public async Task OnReadyAsync()
{
- if (!_isEnabled)
+ var creds = _creds.GetCreds();
+ if (creds.GrpcApi is null || creds.GrpcApi.Enabled)
return;
try
{
- _app = new()
+ var host = creds.GrpcApi.Host;
+ var port = creds.GrpcApi.Port + _client.ShardId;
+
+ var interceptor = new PermsInterceptor(_client);
+
+ _app = new Server()
{
Services =
{
- GrpcOther.BindService(_other),
- GrpcExprs.BindService(_exprs),
- GrpcGreet.BindService(_greet)
+ GrpcOther.BindService(_other).Intercept(interceptor),
+ GrpcExprs.BindService(_exprs).Intercept(interceptor),
+ GrpcGreet.BindService(_greet).Intercept(interceptor),
},
Ports =
{
- new(_host, _port, _creds),
+ new(host, port, ServerCredentials.Insecure),
}
};
+
_app.Start();
+
+ Log.Information("Grpc Api Server started on port {Host}:{Port}", host, port);
}
- finally
+ catch
{
_app?.ShutdownAsync().GetAwaiter().GetResult();
}
-
- Log.Information("Grpc Api Server started on port {Host}:{Port}", _host, _port);
}
}
\ No newline at end of file
diff --git a/src/EllieBot/Services/PermsInterceptor.cs b/src/EllieBot/Services/PermsInterceptor.cs
new file mode 100644
index 0000000..c066ae7
--- /dev/null
+++ b/src/EllieBot/Services/PermsInterceptor.cs
@@ -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 UnaryServerHandler(
+ TRequest request,
+ ServerCallContext context,
+ UnaryServerMethod 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;
+ }
+ }
+}
diff --git a/src/EllieBot/_common/Abstractions/creds/IBotCredentials.cs b/src/EllieBot/_common/Abstractions/creds/IBotCredentials.cs
index faeae4f..7f98972 100644
--- a/src/EllieBot/_common/Abstractions/creds/IBotCredentials.cs
+++ b/src/EllieBot/_common/Abstractions/creds/IBotCredentials.cs
@@ -29,6 +29,7 @@ public interface IBotCredentials
string TwitchClientSecret { get; set; }
GoogleApiConfig Google { get; set; }
BotCacheImplemenation BotCache { get; set; }
+ Creds.GrpcApiConfig GrpcApi { get; set; }
}
public interface IVotesSettings
diff --git a/src/EllieBot/_common/Creds.cs b/src/EllieBot/_common/Creds.cs
index 30ad6b7..d9139c9 100644
--- a/src/EllieBot/_common/Creds.cs
+++ b/src/EllieBot/_common/Creds.cs
@@ -162,7 +162,7 @@ public sealed class Creds : IBotCredentials
We don't provide support for this.
If you leave certPath empty, the api will run on http.
""")]
- public ApiConfig Api { get; set; }
+ public GrpcApiConfig GrpcApi { get; set; }
public Creds()
{
@@ -189,7 +189,7 @@ public sealed class Creds : IBotCredentials
RestartCommand = new RestartConfig();
Google = new GoogleApiConfig();
- Api = new ApiConfig();
+ GrpcApi = new GrpcApiConfig();
}
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 string CertPath { get; set; } = string.Empty;
diff --git a/src/EllieBot/_common/Impl/RemoteGrpcCoordinator.cs b/src/EllieBot/_common/Impl/RemoteGrpcCoordinator.cs
index de56a39..1fb2867 100644
--- a/src/EllieBot/_common/Impl/RemoteGrpcCoordinator.cs
+++ b/src/EllieBot/_common/Impl/RemoteGrpcCoordinator.cs
@@ -90,8 +90,7 @@ public class RemoteGrpcCoordinator : ICoordinator, IReadyExecutor
{
if (!gracefulImminent)
{
- Log.Warning(ex,
- "Hearbeat failed and graceful shutdown was not expected: {Message}",
+ Log.Warning(ex, "Hearbeat failed and graceful shutdown was not expected: {Message}",
ex.Message);
break;
}
diff --git a/src/EllieBot/_common/Services/IStatsService.cs b/src/EllieBot/_common/Services/IStatsService.cs
index 3dee0a6..3f73495 100644
--- a/src/EllieBot/_common/Services/IStatsService.cs
+++ b/src/EllieBot/_common/Services/IStatsService.cs
@@ -49,8 +49,8 @@ public interface IStatsService
///
double GetPrivateMemoryMegabytes();
- GuildInfo GetGuildInfo(string name);
- GuildInfo GetGuildInfo(ulong id);
+ GuildInfo GetGuildInfoAsync(string name);
+ Task GetGuildInfoAsync(ulong id);
}
public record struct GuildInfo
diff --git a/src/EllieBot/_common/Services/Impl/StatsService.cs b/src/EllieBot/_common/Services/Impl/StatsService.cs
index 9387e51..363f8a5 100644
--- a/src/EllieBot/_common/Services/Impl/StatsService.cs
+++ b/src/EllieBot/_common/Services/Impl/StatsService.cs
@@ -180,19 +180,20 @@ public sealed class StatsService : IStatsService, IReadyExecutor, IEService
return _currentProcess.PrivateMemorySize64 / 1.Megabytes();
}
- public GuildInfo GetGuildInfo(string name)
+ public GuildInfo GetGuildInfoAsync(string name)
=> throw new NotImplementedException();
- public GuildInfo GetGuildInfo(ulong id)
+ public async Task GetGuildInfoAsync(ulong id)
{
var g = _client.GetGuild(id);
+ var ig = (IGuild)g;
return new GuildInfo()
{
Id = g.Id,
IconUrl = g.IconUrl,
Name = g.Name,
- Owner = g.Owner.Username,
+ Owner = (await ig.GetUserAsync(g.OwnerId))?.Username ?? "Unknown",
OwnerId = g.OwnerId,
CreatedAt = g.CreatedAt.UtcDateTime,
VoiceChannels = g.VoiceChannels.Count,