Added grpc api, perm system
grpc api config in creds
This commit is contained in:
parent
de97213046
commit
391d2e43e8
14 changed files with 410 additions and 42 deletions
|
@ -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>
|
||||||
|
|
148
src/EllieBot.Generators/GrpcApiPermGenerator.cs
Normal file
148
src/EllieBot.Generators/GrpcApiPermGenerator.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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) =>
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
67
src/EllieBot/Services/PermsInterceptor.cs
Normal file
67
src/EllieBot/Services/PermsInterceptor.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue