diff --git a/src/EllieBot.GrpcApiBase/protos/xp.proto b/src/EllieBot.GrpcApiBase/protos/xp.proto new file mode 100644 index 0000000..84c4daa --- /dev/null +++ b/src/EllieBot.GrpcApiBase/protos/xp.proto @@ -0,0 +1,108 @@ +syntax = "proto3"; + +option csharp_namespace = "EllieBot.GrpcApi"; + +package xp; + +service GrpcXp { + rpc GetXpLb(GetXpLbRequest) returns (GetXpLbReply); + rpc ResetUserXp(ResetUserXpRequest) returns (ResetUserXpReply); + + rpc GetXpSettings(GetXpSettingsRequest) returns (GetXpSettingsReply); + + rpc AddExclusion(AddExclusionRequest) returns (AddExclusionReply); + rpc DeleteExclusion(DeleteExclusionRequest) returns (DeleteExclusionReply); + + rpc AddReward(AddRewardRequest) returns (AddRewardReply); + rpc DeleteReward(DeleteRewardRequest) returns (DeleteRewardReply); +} + +message GetXpLbRequest { + uint64 guildId = 1; + int32 page = 2; +} + +message GetXpLbReply { + repeated XpLbUserReply users = 1; + int32 total = 2; +} + +message XpLbUserReply { + uint64 userId = 1; + string username = 2; + int64 xp = 3; + int64 level = 4; + string avatar = 5; +} + +message ResetUserXpRequest { + uint64 guildId = 1; + uint64 userId = 2; +} + +message ResetUserXpReply { + bool success = 1; +} + +message GetXpSettingsReply { + repeated ExclItemReply exclusions = 1; + repeated RewItemReply rewards = 2; + bool serverExcluded = 3; +} + +message GetXpSettingsRequest { + uint64 guildId = 1; +} + +message ExclItemReply { + string type = 1; + uint64 id = 2; + string name = 3; +} + +message RewItemReply { + int32 level = 1; + string type = 2; + string value = 3; +} + +message AddExclusionRequest { + uint64 guildId = 1; + string type = 2; + uint64 id = 3; +} + +message AddExclusionReply { + bool success = 1; +} + +message DeleteExclusionRequest { + uint64 guildId = 1; + string type = 2; + uint64 id = 3; +} + +message DeleteExclusionReply { + bool success = 1; +} + +message AddRewardRequest { + uint64 guildId = 1; + int32 level = 2; + string type = 3; + string value = 4; +} + +message AddRewardReply { + bool success = 1; +} + +message DeleteRewardRequest { + uint64 guildId = 1; + int32 level = 2; + string type = 3; +} + +message DeleteRewardReply { + bool success = 1; +} \ No newline at end of file diff --git a/src/EllieBot/Services/GrpcApi/XpSvc.cs b/src/EllieBot/Services/GrpcApi/XpSvc.cs new file mode 100644 index 0000000..287c71e --- /dev/null +++ b/src/EllieBot/Services/GrpcApi/XpSvc.cs @@ -0,0 +1,245 @@ +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using EllieBot.Db.Models; +using EllieBot.Modules.Gambling.Bank; +using EllieBot.Modules.EllieExpressions; +using EllieBot.Modules.Utility; +using EllieBot.Modules.Xp.Services; + +namespace EllieBot.GrpcApi; + +public class XpSvc : GrpcXp.GrpcXpBase, IGrpcSvc, IEService +{ + private readonly XpService _xp; + private readonly DiscordSocketClient _client; + private readonly IUserService _duSvc; + + public XpSvc(XpService xp, DiscordSocketClient client, IUserService duSvc) + { + _xp = xp; + _client = client; + _duSvc = duSvc; + } + + public ServerServiceDefinition Bind() + => GrpcXp.BindService(this); + + public override async Task GetXpSettings( + GetXpSettingsRequest request, + ServerCallContext context) + { + var guild = _client.GetGuild(request.GuildId); + + if (guild is null) + throw new RpcException(new Status(StatusCode.NotFound, "Guild not found")); + + var excludedChannels = _xp.GetExcludedChannels(request.GuildId); + var excludedRoles = _xp.GetExcludedRoles(request.GuildId); + var isServerExcluded = _xp.IsServerExcluded(request.GuildId); + + var reply = new GetXpSettingsReply(); + + reply.Exclusions.AddRange(excludedChannels + .Select(x => new ExclItemReply() + { + Id = x, + Type = "Channel", + Name = guild.GetChannel(x)?.Name ?? "????" + }) + .Concat(excludedRoles + .Select(x => new ExclItemReply() + { + Id = x, + Type = "Role", + Name = guild.GetRole(x)?.Name ?? "????" + }))); + + reply.ServerExcluded = isServerExcluded; + + return reply; + } + + public override async Task AddExclusion(AddExclusionRequest request, ServerCallContext context) + { + await Task.Yield(); + + var success = false; + var guild = _client.GetGuild(request.GuildId); + + if (guild is null) + throw new RpcException(new Status(StatusCode.NotFound, "Guild not found")); + + if (request.Type == "Role") + { + if (guild.GetRole(request.Id) is null) + return new() + { + Success = false + }; + + success = _xp.ToggleExcludeRole(request.GuildId, request.Id); + } + else if (request.Type == "Channel") + { + if (guild.GetTextChannel(request.Id) is null) + return new() + { + Success = false + }; + + success = _xp.ToggleExcludeChannel(request.GuildId, request.Id); + } + + return new() + { + Success = success + }; + } + + public override Task DeleteExclusion( + DeleteExclusionRequest request, + ServerCallContext context) + { + var success = false; + if (request.Type == "Role") + success = _xp.ToggleExcludeRole(request.GuildId, request.Id); + else + success = _xp.ToggleExcludeChannel(request.GuildId, request.Id); + + return Task.FromResult(new DeleteExclusionReply + { + Success = success + }); + } + + public override async Task AddReward(AddRewardRequest request, ServerCallContext context) + { + await Task.Yield(); + + var success = false; + var guild = _client.GetGuild(request.GuildId); + + if (guild is null) + throw new RpcException(new Status(StatusCode.NotFound, "Guild not found")); + + if (request.Type == "AddRole" || request.Type == "RemoveRole") + { + if (!ulong.TryParse(request.Value, out var rid)) + throw new RpcException(new Status(StatusCode.InvalidArgument, "Invalid role id")); + + var role = guild.GetRole(rid); + if (role is null) + return new() + { + Success = false + }; + + _xp.SetRoleReward(request.GuildId, request.Level, rid, request.Type == "RemoveRole"); + success = true; + } + else if (request.Type == "Currency") + { + if (!int.TryParse(request.Value, out var amount)) + throw new RpcException(new Status(StatusCode.InvalidArgument, "Invalid amount")); + + _xp.SetCurrencyReward(request.GuildId, request.Level, amount); + success = true; + } + + return new() + { + Success = success + }; + } + + public override Task DeleteReward(DeleteRewardRequest request, ServerCallContext context) + { + var success = false; + + if (request.Type == "AddRole" || request.Type == "RemoveRole") + { + _xp.ResetRoleReward(request.GuildId, request.Level); + success = true; + } + else if (request.Type == "Currency") + { + _xp.SetCurrencyReward(request.GuildId, request.Level, 0); + success = true; + } + + return Task.FromResult(new DeleteRewardReply + { + Success = success + }); + } + + public override async Task ResetUserXp(ResetUserXpRequest request, ServerCallContext context) + { + await _xp.XpReset(request.GuildId, request.UserId); + + return new ResetUserXpReply + { + Success = true + }; + } + + public override async Task GetXpLb(GetXpLbRequest request, ServerCallContext context) + { + if (request.Page < 0) + throw new RpcException(new Status(StatusCode.InvalidArgument, "Page must be greater than or equal to 0")); + + var guild = _client.GetGuild(request.GuildId); + + if (guild is null) + throw new RpcException(new Status(StatusCode.NotFound, "Guild not found")); + + var data = await _xp.GetGuildUserXps(request.GuildId, request.Page); + var total = await _xp.GetTotalGuildUsers(request.GuildId); + + var reply = new GetXpLbReply + { + Total = total + }; + + reply.Users.AddRange(await data + .Select(async x => + { + var user = guild.GetUser(x.UserId); + + if (user is null) + { + var du = await _duSvc.GetUserAsync(x.UserId); + if (du is null) + return new XpLbUserReply + { + UserId = x.UserId, + Avatar = string.Empty, + Username = string.Empty, + Xp = x.Xp, + Level = new LevelStats(x.Xp).Level + }; + + return new XpLbUserReply() + { + UserId = x.UserId, + Avatar = du.RealAvatarUrl()?.ToString() ?? string.Empty, + Username = du.ToString() ?? string.Empty, + Xp = x.Xp, + Level = new LevelStats(x.Xp).Level + }; + } + + return new XpLbUserReply + { + UserId = x.UserId, + Avatar = user?.GetAvatarUrl() ?? string.Empty, + Username = user?.ToString() ?? string.Empty, + Xp = x.Xp, + Level = new LevelStats(x.Xp).Level + }; + }) + .WhenAll()); + + return reply; + } +} \ No newline at end of file