diff --git a/src/EllieBot.GrpcApiBase/protos/exprs.proto b/src/EllieBot.GrpcApiBase/protos/exprs.proto index 2d4528e..056d4a5 100644 --- a/src/EllieBot.GrpcApiBase/protos/exprs.proto +++ b/src/EllieBot.GrpcApiBase/protos/exprs.proto @@ -2,7 +2,7 @@ option csharp_namespace = "EllieBot.GrpcApi"; -import "google/protobuf/empty.proto"; +import "google/protobuf/empty.proto"; package exprs; @@ -10,6 +10,10 @@ service GrpcExprs { rpc GetExprs(GetExprsRequest) returns (GetExprsReply); rpc AddExpr(AddExprRequest) returns (AddExprReply); rpc DeleteExpr(DeleteExprRequest) returns (google.protobuf.Empty); + + rpc GetQuotes(GetQuotesRequest) returns (GetQuotesReply); + rpc AddQuote(AddQuoteRequest) returns (AddQuoteReply); + rpc DeleteQuote(DeleteQuoteRequest) returns (google.protobuf.Empty); } message DeleteExprRequest { @@ -48,3 +52,38 @@ message AddExprReply { string id = 1; bool success = 2; } + +message GetQuotesRequest { + uint64 guildId = 1; + string query = 2; + int32 page = 3; +} + +message GetQuotesReply { + repeated QuoteDto quotes = 1; + int32 totalCount = 2; +} + +message QuoteDto { + string id = 1; + string trigger = 2; + string response = 3; + + uint64 authorId = 4; + string authorName = 5; +} + +message AddQuoteRequest { + uint64 guildId = 1; + QuoteDto quote = 2; +} + +message AddQuoteReply { + string id = 1; + bool success = 2; +} + +message DeleteQuoteRequest { + string id = 1; + uint64 guildId = 2; +} \ No newline at end of file diff --git a/src/EllieBot/Modules/Administration/AutoPublishService.cs b/src/EllieBot/Modules/Administration/AutoPublishService.cs index 65658cf..2e877bb 100644 --- a/src/EllieBot/Modules/Administration/AutoPublishService.cs +++ b/src/EllieBot/Modules/Administration/AutoPublishService.cs @@ -1,4 +1,3 @@ -#nullable disable using LinqToDB; using LinqToDB.EntityFrameworkCore; using EllieBot.Common.ModuleBehaviors; @@ -11,7 +10,7 @@ public class AutoPublishService : IExecNoCommand, IReadyExecutor, IEService private readonly DbService _db; private readonly DiscordSocketClient _client; private readonly IBotCredsProvider _creds; - private ConcurrentDictionary _enabled; + private ConcurrentDictionary _enabled = new(); public AutoPublishService(DbService db, DiscordSocketClient client, IBotCredsProvider creds) { @@ -20,7 +19,7 @@ public class AutoPublishService : IExecNoCommand, IReadyExecutor, IEService _creds = creds; } - public async Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg) + public async Task ExecOnNoCommandAsync(IGuild? guild, IUserMessage msg) { if (guild is null) return; diff --git a/src/EllieBot/Modules/Expressions/EllieExpressionsService.cs b/src/EllieBot/Modules/Expressions/EllieExpressionsService.cs index 9101ee0..5e3561d 100644 --- a/src/EllieBot/Modules/Expressions/EllieExpressionsService.cs +++ b/src/EllieBot/Modules/Expressions/EllieExpressionsService.cs @@ -67,7 +67,6 @@ public sealed class EllieExpressionsService : IExecOnMessage, IReadyExecutor // private readonly GlobalPermissionService _gperm; // private readonly CmdCdService _cmdCds; private readonly IPermissionChecker _permChecker; - private readonly ICommandHandler _cmd; private readonly IBotStrings _strings; private readonly IBot _bot; private readonly IPubSub _pubSub; @@ -84,7 +83,6 @@ public sealed class EllieExpressionsService : IExecOnMessage, IReadyExecutor IBotStrings strings, IBot bot, DiscordSocketClient client, - ICommandHandler cmd, IPubSub pubSub, IMessageSenderService sender, IReplacementService repSvc, @@ -93,7 +91,6 @@ public sealed class EllieExpressionsService : IExecOnMessage, IReadyExecutor { _db = db; _client = client; - _cmd = cmd; _strings = strings; _bot = bot; _pubSub = pubSub; diff --git a/src/EllieBot/Modules/Utility/Quote/IQuoteService.cs b/src/EllieBot/Modules/Utility/Quote/IQuoteService.cs index acd6b19..87f9675 100644 --- a/src/EllieBot/Modules/Utility/Quote/IQuoteService.cs +++ b/src/EllieBot/Modules/Utility/Quote/IQuoteService.cs @@ -27,6 +27,8 @@ public interface IQuoteService string? keyword, string text); + Task<(IReadOnlyCollection quotes, int totalCount)> FindQuotesAsync(ulong guildId, string query, int page); + Task> GetGuildQuotesAsync(ulong guildId); Task RemoveAllByKeyword(ulong guildId, string keyword); Task GetQuoteByIdAsync(ulong guildId, int quoteId); @@ -39,6 +41,7 @@ public interface IQuoteService string text); Task EditQuoteAsync(ulong authorId, int quoteId, string text); + Task EditQuoteAsync(ulong guildId, int quoteId, string keyword, string text); Task DeleteQuoteAsync( ulong guildId, diff --git a/src/EllieBot/Modules/Utility/Quote/QuoteService.cs b/src/EllieBot/Modules/Utility/Quote/QuoteService.cs index a518a01..429387a 100644 --- a/src/EllieBot/Modules/Utility/Quote/QuoteService.cs +++ b/src/EllieBot/Modules/Utility/Quote/QuoteService.cs @@ -169,6 +169,23 @@ public sealed class QuoteService : IQuoteService, IEService return q; } + public async Task EditQuoteAsync( + ulong guildId, + int quoteId, + string keyword, + string text) + { + await using var uow = _db.GetDbContext(); + var result = await uow.GetTable() + .Where(x => x.Id == quoteId && x.GuildId == guildId) + .Set(x => x.Keyword, keyword) + .Set(x => x.Text, text) + .UpdateWithOutputAsync((del, ins) => ins); + + var q = result.FirstOrDefault(); + return q; + } + public async Task DeleteQuoteAsync( ulong guildId, ulong authorId, @@ -219,4 +236,24 @@ public sealed class QuoteService : IQuoteService, IEService return true; } + + public async Task<(IReadOnlyCollection quotes, int totalCount)> FindQuotesAsync( + ulong guildId, + string query, + int page) + { + await using var uow = _db.GetDbContext(); + + var baseQuery = uow.GetTable() + .Where(x => x.GuildId == guildId) + .Where(x => x.Keyword.Contains(query) || x.Text.Contains(query)); + + var quotes = await baseQuery + .OrderBy(x => x.Id) + .Skip((page - 1) * 10) + .Take(10) + .ToListAsyncLinqToDB(); + + return (quotes, await baseQuery.CountAsyncLinqToDB()); + } } \ No newline at end of file diff --git a/src/EllieBot/Services/GrpcApi/ExprsSvc.cs b/src/EllieBot/Services/GrpcApi/ExprsSvc.cs index 57c49d9..761d4f6 100644 --- a/src/EllieBot/Services/GrpcApi/ExprsSvc.cs +++ b/src/EllieBot/Services/GrpcApi/ExprsSvc.cs @@ -2,20 +2,31 @@ using Grpc.Core; using EllieBot.Db.Models; using EllieBot.Modules.EllieExpressions; +using EllieBot.Modules.Utility; namespace EllieBot.GrpcApi; public class ExprsSvc : GrpcExprs.GrpcExprsBase, IEService { private readonly EllieExpressionsService _svc; + private readonly IQuoteService _qs; + private readonly DiscordSocketClient _client; - public ExprsSvc(EllieExpressionsService svc) + public ExprsSvc(EllieExpressionsService svc, IQuoteService qs, DiscordSocketClient client) { _svc = svc; + _qs = qs; + _client = client; } + private ulong GetUserId(Metadata meta) + => ulong.Parse(meta.FirstOrDefault(x => x.Key == "userid")!.Value); + public override async Task AddExpr(AddExprRequest request, ServerCallContext context) { + if (string.IsNullOrWhiteSpace(request.Expr.Trigger) || string.IsNullOrWhiteSpace(request.Expr.Response)) + throw new RpcException(new Status(StatusCode.InvalidArgument, "Trigger and response are required")); + EllieExpression expr; if (!string.IsNullOrWhiteSpace(request.Expr.Id)) { @@ -66,8 +77,73 @@ public class ExprsSvc : GrpcExprs.GrpcExprsBase, IEService public override async Task DeleteExpr(DeleteExprRequest request, ServerCallContext context) { - await _svc.DeleteAsync(request.GuildId, new kwum(request.Id)); + if (kwum.TryParse(request.Id, out var id)) + await _svc.DeleteAsync(request.GuildId, id); return new Empty(); } -} + + public override async Task GetQuotes(GetQuotesRequest request, ServerCallContext context) + { + if (request.Page < 0) + throw new RpcException(new Status(StatusCode.InvalidArgument, "Page must be >= 0")); + + var (quotes, totalCount) = await _qs.FindQuotesAsync(request.GuildId, request.Query, request.Page); + + var reply = new GetQuotesReply(); + reply.TotalCount = totalCount; + reply.Quotes.AddRange(quotes.Select(x => new QuoteDto() + { + Id = new kwum(x.Id).ToString(), + Trigger = x.Keyword, + Response = x.Text, + AuthorId = x.AuthorId, + AuthorName = x.AuthorName + })); + + return reply; + } + + public override async Task AddQuote(AddQuoteRequest request, ServerCallContext context) + { + var userId = GetUserId(context.RequestHeaders); + + if (string.IsNullOrWhiteSpace(request.Quote.Trigger) || string.IsNullOrWhiteSpace(request.Quote.Response)) + throw new RpcException(new Status(StatusCode.InvalidArgument, "Trigger and response are required")); + + if (string.IsNullOrWhiteSpace(request.Quote.Id)) + { + var q = await _qs.AddQuoteAsync(request.GuildId, + userId, + (await _client.GetUserAsync(userId))?.Username ?? userId.ToString(), + request.Quote.Trigger, + request.Quote.Response); + + return new() + { + Id = new kwum(q.Id).ToString() + }; + } + + if (!kwum.TryParse(request.Quote.Id, out var qid)) + throw new RpcException(new Status(StatusCode.InvalidArgument, "Invalid quote id")); + + await _qs.EditQuoteAsync( + request.GuildId, + new kwum(request.Quote.Id), + request.Quote.Trigger, + request.Quote.Response); + + return new() + { + Id = new kwum(qid).ToString() + }; + } + + + public override async Task DeleteQuote(DeleteQuoteRequest request, ServerCallContext context) + { + await _qs.DeleteQuoteAsync(request.GuildId, GetUserId(context.RequestHeaders), true, new kwum(request.Id)); + return new Empty(); + } +} \ No newline at end of file