Add subscribe command

This commit is contained in:
Sergey Nazarov 2024-03-26 15:42:59 +03:00
parent 33074e99a0
commit 658b28799a
16 changed files with 287 additions and 42 deletions

View File

@ -1,31 +1,34 @@
<Project>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<MicrosoftVersion>8.0.0</MicrosoftVersion>
<InsightTelegramBotVersion>0.16.0</InsightTelegramBotVersion>
</PropertyGroup>
<ItemGroup Label="Nocr">
<PackageVersion Include="Nocr.TextMatcher.Async.Api.Contracts" Version="0.4.7" />
</ItemGroup>
<ItemGroup Label="Rebus">
<PackageVersion Include="Rebus" Version="8.2.2"/>
<PackageVersion Include="Rebus.ServiceProvider" Version="10.1.0"/>
<PackageVersion Include="Rebus.RabbitMq" Version="9.0.1"/>
<PackageVersion Include="Rebus.Serilog" Version="8.0.0"/>
</ItemGroup>
<ItemGroup Label="Insight.TelegramBot">
<PackageVersion Include="Insight.TelegramBot" Version="$(InsightTelegramBotVersion)"/>
<PackageVersion Include="Insight.TelegramBot.Hosting.DependencyInjection" Version="$(InsightTelegramBotVersion)"/>
<PackageVersion Include="Insight.TelegramBot.Handling" Version="$(InsightTelegramBotVersion)"/>
</ItemGroup>
<ItemGroup Label="Serilog">
<PackageVersion Include="Serilog" Version="3.1.1"/>
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.1"/>
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.0"/>
</ItemGroup>
<ItemGroup Label="Microsoft">
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftVersion)"/>
</ItemGroup>
</Project>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<MicrosoftVersion>8.0.0</MicrosoftVersion>
<InsightTelegramBotVersion>0.16.0</InsightTelegramBotVersion>
</PropertyGroup>
<ItemGroup Label="Nocr">
<PackageVersion Include="Nocr.TextMatcher.Api.Contracts" Version="0.4.13" />
<PackageVersion Include="Nocr.TextMatcher.Async.Api.Contracts" Version="0.4.13" />
<PackageVersion Include="Nocr.Users.Api.Contracts" Version="0.4.11" />
</ItemGroup>
<ItemGroup Label="Rebus">
<PackageVersion Include="Rebus" Version="8.2.2" />
<PackageVersion Include="Rebus.ServiceProvider" Version="10.1.0" />
<PackageVersion Include="Rebus.RabbitMq" Version="9.0.1" />
<PackageVersion Include="Rebus.Serilog" Version="8.0.0" />
</ItemGroup>
<ItemGroup Label="Insight.TelegramBot">
<PackageVersion Include="Insight.TelegramBot" Version="$(InsightTelegramBotVersion)" />
<PackageVersion Include="Insight.TelegramBot.Hosting.DependencyInjection" Version="$(InsightTelegramBotVersion)" />
<PackageVersion Include="Insight.TelegramBot.Handling" Version="$(InsightTelegramBotVersion)" />
</ItemGroup>
<ItemGroup Label="Serilog">
<PackageVersion Include="Serilog" Version="3.1.1" />
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.0" />
</ItemGroup>
<ItemGroup Label="Microsoft">
<PackageVersion Include="Microsoft.Extensions.Http" Version="$(MicrosoftVersion)" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftVersion)" />
</ItemGroup>
</Project>

View File

@ -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<StartMessageMatcher>
{
@ -14,10 +14,11 @@ public class StartMessageHandler : IMatchingUpdateHandler<StartMessageMatcher>
{
_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

View File

@ -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
{

View File

@ -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<SubscribeMessageMatcher>
{
private readonly IBot _bot;
private readonly IUsersService _usersService;
private readonly ITextMatchesController _textMatchesController;
/// <summary>
/// Regex to match command "/subscribe @username match_type keywords". <br/>
/// For instance, "/subscribe @baraholka 1 обувь ботинки сапоги" will create match for a current user with type "All" and pattern "обувь ботинки сапоги".
/// </summary>
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);
}
}

View File

@ -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<TextMatchMatched>
{
private readonly ILogger<TextMatchMatchedHandler> _logger;
private readonly IBot _bot;
private readonly ITextMatchesController _textMatchesController;
private readonly IUsersService _usersService;
public TextMatchMatchedHandler(IBot bot)
public TextMatchMatchedHandler(ILogger<TextMatchMatchedHandler> 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));
}
}
}

View File

@ -6,10 +6,16 @@
<ItemGroup>
<PackageReference Include="Insight.TelegramBot" />
<PackageReference Include="Insight.TelegramBot.Handling" />
<PackageReference Include="Microsoft.Extensions.Http" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Nocr.Users.Api.Contracts" />
<PackageReference Include="Rebus" />
<PackageReference Include="Rebus.ServiceProvider" />
<PackageReference Include="Nocr.TextMatcher.Async.Api.Contracts" />
<PackageReference Include="Nocr.TextMatcher.Api.Contracts" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Nocr.TelegramClient.Core\Nocr.TelegramClient.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,7 @@
using Nocr.TelegramClient.Core.Options;
namespace Nocr.TelegramClient.AppServices.Options;
public class TextMatcherRestEaseOptions : RestEaseOptions
{
}

View File

@ -0,0 +1,7 @@
using Nocr.TelegramClient.Core.Options;
namespace Nocr.TelegramClient.AppServices.Options;
public class UsersRestEaseOptions : RestEaseOptions
{
}

View File

@ -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<TextMatchMatchedHandler>();
services.AddHttpClient();
services.Configure<UsersRestEaseOptions>(configuration.GetSection(nameof(UsersRestEaseOptions)));
services.AddScoped<IUsersController>(ctx =>
{
var options = ctx.GetRequiredService<IOptions<UsersRestEaseOptions>>().Value;
var httpClientFactory = ctx.GetRequiredService<IHttpClientFactory>();
var client = httpClientFactory.CreateClient(nameof(IUsersController));
client.BaseAddress = new Uri(options.BasePath);
return RestClient.For<IUsersController>(client);
});
services.Configure<TextMatcherRestEaseOptions>(configuration.GetSection(nameof(TextMatcherRestEaseOptions)));
services.AddScoped<ITextMatchesController>(ctx =>
{
var options = ctx.GetRequiredService<IOptions<TextMatcherRestEaseOptions>>().Value;
var httpClientFactory = ctx.GetRequiredService<IHttpClientFactory>();
var client = httpClientFactory.CreateClient(nameof(ITextMatchesController));
client.BaseAddress = new Uri(options.BasePath);
return RestClient.For<ITextMatchesController>(client);
});
services.AddScoped<IUsersService, UsersService>();
return services;
}

View File

@ -0,0 +1,10 @@
using Nocr.Users.Api.Contracts.Users.Dto;
namespace Nocr.TelegramClient.AppServices.Users;
public interface IUsersService
{
public Task<UserData?> GetOrCreate(long telegramId, string? username, CancellationToken cancellationToken = default);
public Task<UserData?> GetById(long id, CancellationToken cancellationToken = default);
}

View File

@ -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<UserData?> 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<UserData?> GetById(long id, CancellationToken cancellationToken = default)
{
return _usersController.GetById(id, cancellationToken);
}
}

View File

@ -0,0 +1,6 @@
namespace Nocr.TelegramClient.Core.Options;
public class RestEaseOptions
{
public string BasePath { get; set; }
}

View File

@ -25,7 +25,6 @@ public class Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ICurrentDateProvider, DefaultCurrentDateProvider>();
services.AddTelegramBot(bot =>
bot.WithBot<BotClient>(ServiceLifetime.Transient)
.WithTelegramBotClient(client => client
@ -34,7 +33,8 @@ public class Startup
.WithOptions(opt => opt.FromConfiguration(Configuration))
.WithPolling(polling => polling.WithExceptionHandler<LoggingPollingExceptionHandler>()));
services.AddTelegramBotHandling(typeof(BotClient).Assembly);
services.AddAppServices();
services.AddAppServices(Configuration);
services.Configure<RebusRabbitMqOptions>(Configuration.GetSection(nameof(RebusRabbitMqOptions)));
services.AddRebus((builder, ctx) =>

View File

@ -16,7 +16,6 @@
<ItemGroup>
<ProjectReference Include="..\Nocr.TelegramClient.AppServices\Nocr.TelegramClient.AppServices.csproj" />
<ProjectReference Include="..\Nocr.TelegramClient.Core\Nocr.TelegramClient.Core.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -11,5 +11,11 @@
},
"RebusRabbitMqOptions": {
"ConnectionString": "amqp://admin:admin@localhost:5672/"
},
"UsersRestEaseOptions": {
"BasePath": "http://localhost:4998"
},
"TextMatcherRestEaseOptions": {
"BasePath": "http://localhost:5001"
}
}

View File

@ -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"
}
}