From 658b28799a696f248ab6e4cf2d147043b7cb0d66 Mon Sep 17 00:00:00 2001 From: Sergey Nazarov Date: Tue, 26 Mar 2024 15:42:59 +0300 Subject: [PATCH] Add subscribe command --- Directory.Packages.props | 63 ++++++++-------- .../{ => StartMessage}/StartMessageHandler.cs | 7 +- .../{ => StartMessage}/StartMessageMatcher.cs | 2 +- .../SubscribeMessageHandler.cs | 74 +++++++++++++++++++ .../Matches/TextMatchMatchedHandler.cs | 56 +++++++++++++- .../Nocr.TelegramClient.AppServices.csproj | 6 ++ .../Options/TextMatcherRestEaseOptions.cs | 7 ++ .../Options/UsersRestEaseOptions.cs | 7 ++ .../ServiceCollectionExtensions.cs | 34 ++++++++- .../Users/IUsersService.cs | 10 +++ .../Users/UsersService.cs | 40 ++++++++++ .../Options/RestEaseOptions.cs | 6 ++ .../Infrastructure/Startup.cs | 4 +- .../Nocr.TelegramClient.Host.csproj | 1 - .../appsettings.Development.json | 6 ++ .../appsettings.DockerCompose.json | 6 ++ 16 files changed, 287 insertions(+), 42 deletions(-) rename src/Nocr.TelegramClient.AppServices/Handlers/Messages/{ => StartMessage}/StartMessageHandler.cs (79%) rename src/Nocr.TelegramClient.AppServices/Handlers/Messages/{ => StartMessage}/StartMessageMatcher.cs (73%) create mode 100644 src/Nocr.TelegramClient.AppServices/Handlers/Messages/SubscribeMessage/SubscribeMessageHandler.cs create mode 100644 src/Nocr.TelegramClient.AppServices/Options/TextMatcherRestEaseOptions.cs create mode 100644 src/Nocr.TelegramClient.AppServices/Options/UsersRestEaseOptions.cs create mode 100644 src/Nocr.TelegramClient.AppServices/Users/IUsersService.cs create mode 100644 src/Nocr.TelegramClient.AppServices/Users/UsersService.cs create mode 100644 src/Nocr.TelegramClient.Core/Options/RestEaseOptions.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 7ab0600..d758c13 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,31 +1,34 @@ - - net8.0 - enable - enable - 8.0.0 - 0.16.0 - - - - - - - - - - - - - - - - - - - - - - - - + + net8.0 + enable + enable + 8.0.0 + 0.16.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Nocr.TelegramClient.AppServices/Handlers/Messages/StartMessageHandler.cs b/src/Nocr.TelegramClient.AppServices/Handlers/Messages/StartMessage/StartMessageHandler.cs similarity index 79% rename from src/Nocr.TelegramClient.AppServices/Handlers/Messages/StartMessageHandler.cs rename to src/Nocr.TelegramClient.AppServices/Handlers/Messages/StartMessage/StartMessageHandler.cs index 7c6c4b7..dabad00 100644 --- a/src/Nocr.TelegramClient.AppServices/Handlers/Messages/StartMessageHandler.cs +++ b/src/Nocr.TelegramClient.AppServices/Handlers/Messages/StartMessage/StartMessageHandler.cs @@ -4,7 +4,7 @@ using Insight.TelegramBot.Models; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; -namespace Nocr.TelegramClient.AppServices.Handlers.Messages; +namespace Nocr.TelegramClient.AppServices.Handlers.Messages.StartMesage; public class StartMessageHandler : IMatchingUpdateHandler { @@ -14,10 +14,11 @@ public class StartMessageHandler : IMatchingUpdateHandler { _bot = bot ?? throw new ArgumentNullException(nameof(bot)); } - + public Task Handle(Update update, CancellationToken cancellationToken = default) { - var message = new TextMessage(update.Message.Chat) + var telegramId = update.Message.From.Id; + var message = new TextMessage(telegramId) { Text = "Привет! Я _bot_name_", ParseMode = ParseMode.Html diff --git a/src/Nocr.TelegramClient.AppServices/Handlers/Messages/StartMessageMatcher.cs b/src/Nocr.TelegramClient.AppServices/Handlers/Messages/StartMessage/StartMessageMatcher.cs similarity index 73% rename from src/Nocr.TelegramClient.AppServices/Handlers/Messages/StartMessageMatcher.cs rename to src/Nocr.TelegramClient.AppServices/Handlers/Messages/StartMessage/StartMessageMatcher.cs index da696d1..72ba13e 100644 --- a/src/Nocr.TelegramClient.AppServices/Handlers/Messages/StartMessageMatcher.cs +++ b/src/Nocr.TelegramClient.AppServices/Handlers/Messages/StartMessage/StartMessageMatcher.cs @@ -1,6 +1,6 @@ using Insight.TelegramBot.Handling.Matchers.TextMatchers; -namespace Nocr.TelegramClient.AppServices.Handlers.Messages; +namespace Nocr.TelegramClient.AppServices.Handlers.Messages.StartMesage; public sealed class StartMessageMatcher : TextStartWithUpdateMatcher { diff --git a/src/Nocr.TelegramClient.AppServices/Handlers/Messages/SubscribeMessage/SubscribeMessageHandler.cs b/src/Nocr.TelegramClient.AppServices/Handlers/Messages/SubscribeMessage/SubscribeMessageHandler.cs new file mode 100644 index 0000000..2021218 --- /dev/null +++ b/src/Nocr.TelegramClient.AppServices/Handlers/Messages/SubscribeMessage/SubscribeMessageHandler.cs @@ -0,0 +1,74 @@ +using System.Text.RegularExpressions; +using Insight.TelegramBot; +using Insight.TelegramBot.Handling.Handlers; +using Insight.TelegramBot.Handling.Matchers.TextMatchers; +using Insight.TelegramBot.Models; +using Nocr.TelegramClient.AppServices.Users; +using Nocr.TextMatcher.Api.Contracts.TextMatches; +using Nocr.TextMatcher.Api.Contracts.TextMatches.Requests; +using Telegram.Bot.Types; + +namespace Nocr.TelegramClient.AppServices.Handlers.Messages.SubscribeMessage; + +public sealed class SubscribeMessageMatcher : TextStartWithUpdateMatcher +{ + public SubscribeMessageMatcher() + { + Template = "/subscribe"; + } +} + +public class SubscribeMessageHandler : IMatchingUpdateHandler +{ + private readonly IBot _bot; + private readonly IUsersService _usersService; + private readonly ITextMatchesController _textMatchesController; + + /// + /// Regex to match command "/subscribe @username match_type keywords".
+ /// For instance, "/subscribe @baraholka 1 обувь ботинки сапоги" will create match for a current user with type "All" and pattern "обувь ботинки сапоги". + ///
+ private static Regex _commandRegex = + new Regex(@"/subscribe (.*\B@(?=\w{5,32}\b)[a-zA-Z0-9]+(?:_[a-zA-Z0-9]+)*.*) (\d{1}) (.*)", + RegexOptions.Compiled); + + public SubscribeMessageHandler(IBot bot, IUsersService usersService, ITextMatchesController textMatchesController) + { + _bot = bot ?? throw new ArgumentNullException(nameof(bot)); + _usersService = usersService ?? throw new ArgumentNullException(nameof(usersService)); + _textMatchesController = + textMatchesController ?? throw new ArgumentNullException(nameof(textMatchesController)); + } + + public async Task Handle(Update update, CancellationToken cancellationToken = default) + { + var telegramId = update.Message.From.Id; + + var match = _commandRegex.Match(update.Message.Text); + if (!match.Success) + { + await _bot.SendMessageAsync(new TextMessage(telegramId) + { + Text = "Команда не удовлетворяет формату" + }, CancellationToken.None); + } + + var username = match.Groups[1].Value.TrimStart('@'); + var rule = match.Groups[2].Value; + var template = match.Groups[3].Value; + + var user = await _usersService.GetOrCreate(telegramId, update.Message.From.Username, cancellationToken); + var matchId = await _textMatchesController.Create(new CreateTextMatchRequest + { + UserId = user.Id, + ChatUsername = username, + Rule = (TextMatchRule)Convert.ToInt32(rule), + Template = template, + }, cancellationToken); + + await _bot.SendMessageAsync(new TextMessage(telegramId) + { + Text = $"Подписка создана: {matchId}" + }, CancellationToken.None); + } +} \ No newline at end of file diff --git a/src/Nocr.TelegramClient.AppServices/Matches/TextMatchMatchedHandler.cs b/src/Nocr.TelegramClient.AppServices/Matches/TextMatchMatchedHandler.cs index 41e4924..fcffbe6 100644 --- a/src/Nocr.TelegramClient.AppServices/Matches/TextMatchMatchedHandler.cs +++ b/src/Nocr.TelegramClient.AppServices/Matches/TextMatchMatchedHandler.cs @@ -1,26 +1,74 @@ using Insight.TelegramBot; using Insight.TelegramBot.Models; +using Microsoft.Extensions.Logging; +using Nocr.TelegramClient.AppServices.Users; +using Nocr.TextMatcher.Api.Contracts.TextMatches; using Nocr.TextMatcher.Async.Api.Contracts; +using Nocr.Users.AppServices.Contracts.Users; using Rebus.Handlers; namespace Nocr.TelegramClient.AppServices.Matches; public class TextMatchMatchedHandler : IHandleMessages { + private readonly ILogger _logger; private readonly IBot _bot; + private readonly ITextMatchesController _textMatchesController; + private readonly IUsersService _usersService; - public TextMatchMatchedHandler(IBot bot) + public TextMatchMatchedHandler(ILogger logger, IBot bot, + ITextMatchesController textMatchesController, IUsersService usersService) { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _bot = bot ?? throw new ArgumentNullException(nameof(bot)); + _textMatchesController = + textMatchesController ?? throw new ArgumentNullException(nameof(textMatchesController)); + _usersService = usersService ?? throw new ArgumentNullException(nameof(usersService)); } - + public async Task Handle(TextMatchMatched message) { - var textMessage = new TextMessage(58874635) + var match = await _textMatchesController.GetById(message.MatchId); + if (match == null) { - Text = $"[{message.PublishedDateTime:MM.dd.yyyy HH:mm:ss}] Найдено совпадение.\n @{message.ChatUsername} > {message.Text}" + _logger.LogWarning("Match [{MatchId}] from message {MessageId} not found", message.MatchId, message.Id); + return; + } + + var user = await _usersService.GetById(message.MatchUserId); + if (user == null) + { + _logger.LogWarning("User [{UserId}] of [{MatchId}] from message {MessageId} not found", message.MatchUserId, message.MatchId, message.Id); + return; + } + + var identity = user.Identities.FirstOrDefault(x => x.Type == UserIdentityType.TelegramId); + if (identity == null) + { + _logger.LogWarning("There is no identity with type '{IdentityType}' for user '{UserId}'", UserIdentityType.TelegramId, user.Id); + return; + } + + var textMessage = new TextMessage(long.Parse(identity.Identity)) + { + Text = $"[{message.PublishedDateTime:MM.dd.yyyy HH:mm:ss}] Найдено совпадение.\n @{match.ChatUsername}. Тип совпадения: '{GetTextMatchRule(match.Rule)}', шаблон: '{match.Template}'" }; await _bot.SendMessageAsync(textMessage); } + + private string GetTextMatchRule(TextMatchRule rule) + { + switch (rule) + { + case TextMatchRule.Full: + return "Полное"; + case TextMatchRule.AllWords: + return "Все слова из списка"; + case TextMatchRule.AnyWord: + return "Одно слово из списка"; + default: + throw new IndexOutOfRangeException(nameof(rule)); + } + } } \ No newline at end of file diff --git a/src/Nocr.TelegramClient.AppServices/Nocr.TelegramClient.AppServices.csproj b/src/Nocr.TelegramClient.AppServices/Nocr.TelegramClient.AppServices.csproj index d349003..784c41e 100644 --- a/src/Nocr.TelegramClient.AppServices/Nocr.TelegramClient.AppServices.csproj +++ b/src/Nocr.TelegramClient.AppServices/Nocr.TelegramClient.AppServices.csproj @@ -6,10 +6,16 @@ + + + + + + diff --git a/src/Nocr.TelegramClient.AppServices/Options/TextMatcherRestEaseOptions.cs b/src/Nocr.TelegramClient.AppServices/Options/TextMatcherRestEaseOptions.cs new file mode 100644 index 0000000..e8b3089 --- /dev/null +++ b/src/Nocr.TelegramClient.AppServices/Options/TextMatcherRestEaseOptions.cs @@ -0,0 +1,7 @@ +using Nocr.TelegramClient.Core.Options; + +namespace Nocr.TelegramClient.AppServices.Options; + +public class TextMatcherRestEaseOptions : RestEaseOptions +{ +} \ No newline at end of file diff --git a/src/Nocr.TelegramClient.AppServices/Options/UsersRestEaseOptions.cs b/src/Nocr.TelegramClient.AppServices/Options/UsersRestEaseOptions.cs new file mode 100644 index 0000000..84c4e1d --- /dev/null +++ b/src/Nocr.TelegramClient.AppServices/Options/UsersRestEaseOptions.cs @@ -0,0 +1,7 @@ +using Nocr.TelegramClient.Core.Options; + +namespace Nocr.TelegramClient.AppServices.Options; + +public class UsersRestEaseOptions : RestEaseOptions +{ +} \ No newline at end of file diff --git a/src/Nocr.TelegramClient.AppServices/ServiceCollectionExtensions.cs b/src/Nocr.TelegramClient.AppServices/ServiceCollectionExtensions.cs index aef691c..ab520f3 100644 --- a/src/Nocr.TelegramClient.AppServices/ServiceCollectionExtensions.cs +++ b/src/Nocr.TelegramClient.AppServices/ServiceCollectionExtensions.cs @@ -1,18 +1,50 @@ +using System.Resources; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Nocr.TelegramClient.AppServices.Matches; +using Nocr.TelegramClient.AppServices.Options; +using Nocr.TelegramClient.AppServices.Users; +using Nocr.TelegramClient.Core.Options; +using Nocr.TextMatcher.Api.Contracts.TextMatches; +using Nocr.Users.Api.Contracts.Users; using Rebus.Config; +using RestEase; namespace Nocr.TelegramClient.AppServices; public static class ServiceCollectionExtensions { - public static IServiceCollection AddAppServices(this IServiceCollection services) + public static IServiceCollection AddAppServices(this IServiceCollection services, IConfiguration configuration) { if (services == null) throw new ArgumentNullException(nameof(services)); // Add registrations here services.AddRebusHandler(); + services.AddHttpClient(); + + services.Configure(configuration.GetSection(nameof(UsersRestEaseOptions))); + services.AddScoped(ctx => + { + var options = ctx.GetRequiredService>().Value; + var httpClientFactory = ctx.GetRequiredService(); + var client = httpClientFactory.CreateClient(nameof(IUsersController)); + client.BaseAddress = new Uri(options.BasePath); + return RestClient.For(client); + }); + + services.Configure(configuration.GetSection(nameof(TextMatcherRestEaseOptions))); + services.AddScoped(ctx => + { + var options = ctx.GetRequiredService>().Value; + var httpClientFactory = ctx.GetRequiredService(); + var client = httpClientFactory.CreateClient(nameof(ITextMatchesController)); + client.BaseAddress = new Uri(options.BasePath); + return RestClient.For(client); + }); + + services.AddScoped(); return services; } diff --git a/src/Nocr.TelegramClient.AppServices/Users/IUsersService.cs b/src/Nocr.TelegramClient.AppServices/Users/IUsersService.cs new file mode 100644 index 0000000..4005d00 --- /dev/null +++ b/src/Nocr.TelegramClient.AppServices/Users/IUsersService.cs @@ -0,0 +1,10 @@ +using Nocr.Users.Api.Contracts.Users.Dto; + +namespace Nocr.TelegramClient.AppServices.Users; + +public interface IUsersService +{ + public Task GetOrCreate(long telegramId, string? username, CancellationToken cancellationToken = default); + + public Task GetById(long id, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/Nocr.TelegramClient.AppServices/Users/UsersService.cs b/src/Nocr.TelegramClient.AppServices/Users/UsersService.cs new file mode 100644 index 0000000..175359b --- /dev/null +++ b/src/Nocr.TelegramClient.AppServices/Users/UsersService.cs @@ -0,0 +1,40 @@ +using Nocr.Users.Api.Contracts.Users; +using Nocr.Users.Api.Contracts.Users.Dto; +using Nocr.Users.Api.Contracts.Users.Dto.Requests; +using Nocr.Users.AppServices.Contracts.Users; + +namespace Nocr.TelegramClient.AppServices.Users; + +public sealed class UsersService : IUsersService +{ + private readonly IUsersController _usersController; + + public UsersService(IUsersController usersController) + { + _usersController = usersController ?? throw new ArgumentNullException(nameof(usersController)); + } + + public async Task GetOrCreate(long telegramId, string? username, CancellationToken cancellationToken = default) + { + var user = await _usersController.GetByIdentity(UserIdentityType.TelegramId, telegramId.ToString(), cancellationToken); + if (user == null) + { + await _usersController.Create(new CreateUserRequest + { + Username = username ?? $"User: {telegramId.ToString()}", + TelegramId = telegramId, + TelegramUsername = username + }, cancellationToken); + + user = await _usersController.GetByIdentity(UserIdentityType.TelegramId, telegramId.ToString(), + cancellationToken); + } + + return user; + } + + public Task GetById(long id, CancellationToken cancellationToken = default) + { + return _usersController.GetById(id, cancellationToken); + } +} \ No newline at end of file diff --git a/src/Nocr.TelegramClient.Core/Options/RestEaseOptions.cs b/src/Nocr.TelegramClient.Core/Options/RestEaseOptions.cs new file mode 100644 index 0000000..9b8676e --- /dev/null +++ b/src/Nocr.TelegramClient.Core/Options/RestEaseOptions.cs @@ -0,0 +1,6 @@ +namespace Nocr.TelegramClient.Core.Options; + +public class RestEaseOptions +{ + public string BasePath { get; set; } +} \ No newline at end of file diff --git a/src/Nocr.TelegramClient.Host/Infrastructure/Startup.cs b/src/Nocr.TelegramClient.Host/Infrastructure/Startup.cs index 4eb38e4..329c1b2 100644 --- a/src/Nocr.TelegramClient.Host/Infrastructure/Startup.cs +++ b/src/Nocr.TelegramClient.Host/Infrastructure/Startup.cs @@ -25,7 +25,6 @@ public class Startup public void ConfigureServices(IServiceCollection services) { services.AddSingleton(); - services.AddTelegramBot(bot => bot.WithBot(ServiceLifetime.Transient) .WithTelegramBotClient(client => client @@ -34,7 +33,8 @@ public class Startup .WithOptions(opt => opt.FromConfiguration(Configuration)) .WithPolling(polling => polling.WithExceptionHandler())); services.AddTelegramBotHandling(typeof(BotClient).Assembly); - services.AddAppServices(); + + services.AddAppServices(Configuration); services.Configure(Configuration.GetSection(nameof(RebusRabbitMqOptions))); services.AddRebus((builder, ctx) => diff --git a/src/Nocr.TelegramClient.Host/Nocr.TelegramClient.Host.csproj b/src/Nocr.TelegramClient.Host/Nocr.TelegramClient.Host.csproj index 3e5111d..65deee9 100644 --- a/src/Nocr.TelegramClient.Host/Nocr.TelegramClient.Host.csproj +++ b/src/Nocr.TelegramClient.Host/Nocr.TelegramClient.Host.csproj @@ -16,7 +16,6 @@ - diff --git a/src/Nocr.TelegramClient.Host/appsettings.Development.json b/src/Nocr.TelegramClient.Host/appsettings.Development.json index 2d26fac..f354630 100644 --- a/src/Nocr.TelegramClient.Host/appsettings.Development.json +++ b/src/Nocr.TelegramClient.Host/appsettings.Development.json @@ -11,5 +11,11 @@ }, "RebusRabbitMqOptions": { "ConnectionString": "amqp://admin:admin@localhost:5672/" + }, + "UsersRestEaseOptions": { + "BasePath": "http://localhost:4998" + }, + "TextMatcherRestEaseOptions": { + "BasePath": "http://localhost:5001" } } diff --git a/src/Nocr.TelegramClient.Host/appsettings.DockerCompose.json b/src/Nocr.TelegramClient.Host/appsettings.DockerCompose.json index c6ccd78..221c4c2 100644 --- a/src/Nocr.TelegramClient.Host/appsettings.DockerCompose.json +++ b/src/Nocr.TelegramClient.Host/appsettings.DockerCompose.json @@ -1,5 +1,11 @@ { "RebusRabbitMqOptions": { "ConnectionString": "amqp://admin:admin@nocr-rabbitmq:5672/" + }, + "UsersRestEaseOptions": { + "BasePath": "http://nocr-users:80" + }, + "TextMatcherRestEaseOptions": { + "BasePath": "http://nocr-text-matcher:80" } } \ No newline at end of file