Webhook
Пример будет на основе ASP.NET приложения. nuget пакеты установленные в примере

Program.cs
Для корректной работы webhook ботов, требуется добавить секретный токен. Если в билдере не указать секретный токен для webhook бота, он будет сгенерирован автоматически.
...
builder.Services.AddControllers().AddNewtonsoftJson();
...
new PRBotBuilder("5623652365:Token")
.UseFactory(new PRBotWebHookFactory())
.SetUrlWebHook("https://domain.ru/botendpoint")
.SetClearUpdatesOnStart(true)
.Build();
// Найти экземпляр бота можно через класс BotCollection
...
// Сервис который запустит ботов после запуска приложения
builder.Services.AddHostedService<BotHostedService>();
...
// Регистрация маршрута для получения данных через WebHook.
// В данном примере будет https://domain.ru/botendpoint
// Обратите внимание на метод SetUrlWebHook, который используется выше.
app.MapBotWebhookRoute<BotController>("/botendpoint");
...
app.Run();
WebHookExtensions.cs
using Microsoft.AspNetCore.Mvc;
namespace AspNetWebHook
{
/// <summary>
/// Статический класс, содержащий методы расширения для маршрутизации webhook'ов.
/// </summary>
public static class WebHookExtensions
{
/// <summary>
/// Сопоставляет маршрут webhook с указанным действием контроллера.
/// </summary>
/// <typeparam name="TContoller">Тип контроллера.</typeparam>
/// <param name="endpoints">Объект для добавления маршрута.</param>
/// <param name="route">Шаблон маршрута.</param>
/// <returns>Строитель для настройки конечной точки действия контроллера.</returns>
public static ControllerActionEndpointConventionBuilder MapBotWebhookRoute<TContoller>(this IEndpointRouteBuilder endpoints, string route)
where TContoller : Controller
{
// Название контроллера без Controller.
var controllerName = typeof(TContoller).Name.Replace("Controller", "", StringComparison.Ordinal);
// Метод, который будет обрабатывать маршрут.
var actionName = typeof(TContoller).GetMethods()[0].Name;
return endpoints.MapControllerRoute(
name: "bot_webhook",
pattern: route,
defaults: new { controller = controllerName, action = actionName });
}
}
}
Constants.cs
public class Constants
{
/// <summary>
/// Заголовок запроса с секретным токеном.
/// </summary>
public const string TELEGRAM_SECRET_TOKEN_HEADER = "X-Telegram-Bot-Api-Secret-Token";
}
BotHostedService.cs
Сервис который запускает ботов, после запуска приложения.
public class BotHostedService : IHostedService
{
private readonly IServiceProvider serviceProvider;
public BotHostedService(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
StartBots();
}
private async Task StartBots()
{
// На всякий случай задержка перед запуском.
await Task.Delay(2000);
var bots = BotCollection.Instance.GetBots();
foreach (var bot in bots)
{
// Проброс serviceProvider через DI
bot.Options.ServiceProvider = serviceProvider;
// На всякий случай обновление обработчиков.
bot.ReloadHandlers();
await bot.Start();
if (bot.DataRetrieval == DataRetrievalMethod.WebHook)
{
// Если бот запускается в формате webhook оповещаем в логах в случае ошибки.
var webHookResult = await((PRBotWebHook)bot).GetWebHookInfo();
if (!string.IsNullOrEmpty(webHookResult.LastErrorMessage))
bot.Events.OnErrorLogInvoke(new Exception(webHookResult.LastErrorMessage));
}
}
}
public async Task StopAsync(CancellationToken cancellationToken)
{
var bots = BotCollection.Instance.GetBots();
foreach (var bot in bots)
{
await bot.Stop();
}
}
}
ValidateTelegramBotAttribute.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using PRTelegramBot.Configs;
using PRTelegramBot.Core;
using PRTelegramBot.Models.Enums;
namespace AspNetWebHook.Filter
{
/// <summary>
/// Проверка заголовка "X-Telegram-Bot-Api-Secret-Token" при обработке webook.
/// Подробнее: <see href="https://core.telegram.org/bots/api#setwebhook"/> "secret_token"
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public sealed class ValidateTelegramBotAttribute : TypeFilterAttribute
{
public ValidateTelegramBotAttribute() : base(typeof(ValidateTelegramBotFilter)) { }
private class ValidateTelegramBotFilter : IActionFilter
{
public ValidateTelegramBotFilter() { }
public void OnActionExecuted(ActionExecutedContext context) { }
public void OnActionExecuting(ActionExecutingContext context)
{
if (!IsValidRequest(context.HttpContext.Request))
{
context.Result = new ObjectResult($"\"{Constants.TELEGRAM_SECRET_TOKEN_HEADER}\" is invalid")
{
StatusCode = 403
};
}
}
/// <summary>
/// Проверка секретного токена при обработке webhook запроса.
/// </summary>
/// <param name="request">Запрос.</param>
/// <returns>True - запрос валидный, False - невалидный.</returns>
private bool IsValidRequest(HttpRequest request)
{
var bots = BotCollection.Instance.GetBots().Where(x => x.DataRetrieval == DataRetrievalMethod.WebHook);
if (!bots.Any())
return false;
var isSecretTokenProvided = request.Headers.TryGetValue(Constants.TELEGRAM_SECRET_TOKEN_HEADER, out var secretTokenHeader);
if (!isSecretTokenProvided) return false;
foreach (var bot in bots)
{
var secretToken = ((WebHookTelegramOptions)bot.Options).SecretToken;
if (string.Equals(secretTokenHeader, secretToken, StringComparison.Ordinal));
return true;
}
return false;
}
}
}
}
BotController.cs
public class BotController : Controller
{
[HttpPost]
[ValidateTelegramBot]
public async Task<IActionResult> Post([FromBody] Update update)
{
// Получение секретного токена если есть.
if (Request.Headers.TryGetValue(Constants.TELEGRAM_SECRET_TOKEN_HEADER, out var secretTokenHeader))
{
//Выгрузка только webHook ботов.
var webHookbots = BotCollection.Instance.GetBots().Where(x => x.DataRetrieval == DataRetrievalMethod.WebHook);
foreach (var bot in webHookbots)
{
// Сравнение секретных токенов, если идентичны, выполняем обработку.
var secretToken = ((WebHookTelegramOptions)bot.Options).SecretToken;
if (string.Equals(secretTokenHeader, secretToken, StringComparison.Ordinal))
{
await bot.Handler.HandleUpdateAsync(bot.botClient, update, bot.Options.CancellationToken.Token);
return Ok();
}
}
}
return BadRequest();
}
}
Last updated