forked from EllieBotDevs/elliebot
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
src/EllieBot/Services
|
@ -14,6 +14,7 @@ public class ExprsSvc : GrpcExprs.GrpcExprsBase, IEService
|
|||
_svc = svc;
|
||||
}
|
||||
|
||||
[GrpcApiPerm(GuildPerm.Administrator)]
|
||||
public override async Task<AddExprReply> AddExpr(AddExprRequest request, ServerCallContext context)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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<Empty> DeleteExpr(DeleteExprRequest request, ServerCallContext context)
|
||||
{
|
||||
await _svc.DeleteAsync(request.GuildId, new kwum(request.Id));
|
||||
|
|
|
@ -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<GetGreetReply> 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<UpdateGreetReply> 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<TestGreetReply> TestGreet(TestGreetRequest request, ServerCallContext context)
|
||||
=> TestGreet(request.GuildId, request.ChannelId, request.UserId, request.Type);
|
||||
|
||||
|
|
|
@ -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<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 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<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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
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;
|
||||
}
|
||||
}
|
||||
}
|
Reference in a new issue