using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using System.Text.Json; using Telegram.Bot; using Telegram.Bot.Types; using Telegrator.Core; using Telegrator.Hosting.Web; namespace Telegrator.Mediation; /// /// Service for receiving updates for Hosted telegram bots via Webhooks /// public class HostedUpdateWebhooker : IHostedService { private const string SecretTokenHeader = "X-Telegram-Bot-Api-Secret-Token"; private readonly ITelegramBotClient _botClient; private readonly IUpdateRouter _updateRouter; private readonly WebhookerOptions _options; /// /// Initiallizes new instance of /// /// /// /// /// /// public HostedUpdateWebhooker(ITelegramBotClient botClient, IUpdateRouter updateRouter, IOptions options) { if (string.IsNullOrEmpty(options.Value.WebhookUri)) throw new ArgumentNullException(nameof(options), "Option \"WebhookUrl\" must be set to subscribe for update recieving"); _botClient = botClient; _updateRouter = updateRouter; _options = options.Value; } /// public Task StartAsync(CancellationToken cancellationToken) { StartInternal(cancellationToken); return Task.CompletedTask; } private async void StartInternal(CancellationToken cancellationToken) { await _botClient.SetWebhook( url: _options.WebhookUri, maxConnections: _options.MaxConnections, allowedUpdates: _updateRouter.HandlersProvider.AllowedTypes, dropPendingUpdates: _options.DropPendingUpdates, secretToken: _options.SecretToken, cancellationToken: cancellationToken); } /// public Task StopAsync(CancellationToken cancellationToken) { _botClient.DeleteWebhook(_options.DropPendingUpdates, cancellationToken); return Task.CompletedTask; } /// /// Maps bot webhook to application builder /// /// public void MapWebhook(IEndpointRouteBuilder routeBuilder) { string pattern = new UriBuilder(_options.WebhookUri).Path; routeBuilder.MapPost(pattern, (Delegate)ReceiveUpdate); } private async Task ReceiveUpdate(HttpContext ctx) { if (_options.SecretToken != null) { if (!ctx.Request.Headers.TryGetValue(SecretTokenHeader, out StringValues strings)) return Results.BadRequest(); string? secret = strings.SingleOrDefault(); if (secret == null) return Results.BadRequest(); if (_options.SecretToken != secret) return Results.StatusCode(401); } Update? update = await JsonSerializer.DeserializeAsync(ctx.Request.Body, JsonBotAPI.Options, ctx.RequestAborted); if (update is not { Id: > 0 }) return Results.BadRequest(); await _updateRouter.HandleUpdateAsync(_botClient, update, ctx.RequestAborted); return Results.Ok(); } }