diff --git a/Console.Advanced/Console.Advanced.csproj b/Console.Advanced/Console.Advanced.csproj index 5a0e1334..33ee6dde 100644 --- a/Console.Advanced/Console.Advanced.csproj +++ b/Console.Advanced/Console.Advanced.csproj @@ -14,8 +14,8 @@ - - Always + + PreserveNewest diff --git a/Console.Advanced/Files/bot.gif b/Console.Advanced/Files/bot.gif new file mode 100644 index 00000000..1dfad4e7 Binary files /dev/null and b/Console.Advanced/Files/bot.gif differ diff --git a/Console.Advanced/Files/tux.png b/Console.Advanced/Files/tux.png deleted file mode 100644 index efc8f175..00000000 Binary files a/Console.Advanced/Files/tux.png and /dev/null differ diff --git a/Console.Advanced/Services/UpdateHandler.cs b/Console.Advanced/Services/UpdateHandler.cs index 3e415435..b6691cc4 100644 --- a/Console.Advanced/Services/UpdateHandler.cs +++ b/Console.Advanced/Services/UpdateHandler.cs @@ -90,7 +90,7 @@ async Task SendPhoto(Message msg) { await _bot.SendChatActionAsync(msg.Chat, ChatAction.UploadPhoto); await Task.Delay(2000); // simulate a long task - await using var fileStream = new FileStream("Files/tux.png", FileMode.Open, FileAccess.Read); + await using var fileStream = new FileStream("Files/bot.gif", FileMode.Open, FileAccess.Read); return await _bot.SendPhotoAsync(msg.Chat, fileStream, caption: "Read https://telegrambots.github.io/book/"); } diff --git a/Console.Advanced/appsettings.json b/Console.Advanced/appsettings.json index 6203dc3d..fbc2f844 100644 --- a/Console.Advanced/appsettings.json +++ b/Console.Advanced/appsettings.json @@ -6,6 +6,6 @@ } }, "BotConfiguration": { - "BotToken": "{BOT_TOKEN}" + "BotToken": "YOUR_BOT_TOKEN", } } diff --git a/FSharp.Example/FSharp.Example.fsproj b/FSharp.Example/FSharp.Example.fsproj index d6a4e249..b4495081 100644 --- a/FSharp.Example/FSharp.Example.fsproj +++ b/FSharp.Example/FSharp.Example.fsproj @@ -13,8 +13,8 @@ - - Always + + PreserveNewest diff --git a/FSharp.Example/Files/bot.gif b/FSharp.Example/Files/bot.gif new file mode 100644 index 00000000..1dfad4e7 Binary files /dev/null and b/FSharp.Example/Files/bot.gif differ diff --git a/FSharp.Example/Files/tux.png b/FSharp.Example/Files/tux.png deleted file mode 100644 index efc8f175..00000000 Binary files a/FSharp.Example/Files/tux.png and /dev/null differ diff --git a/FSharp.Example/Services/Internal/UpdateHandlerFuncs.fs b/FSharp.Example/Services/Internal/UpdateHandlerFuncs.fs index 2fd0b762..74c79147 100644 --- a/FSharp.Example/Services/Internal/UpdateHandlerFuncs.fs +++ b/FSharp.Example/Services/Internal/UpdateHandlerFuncs.fs @@ -86,7 +86,7 @@ module UpdateHandlerFuncs = cancellationToken = cts) |> Async.AwaitTask |> ignore - let filePath = @"Files/tux.png" + let filePath = @"Files/bot.gif" let fileName = filePath.Split(Path.DirectorySeparatorChar) |> Array.last diff --git a/Serverless/AwsLambda.Webhook/lambda-bot/Properties/launchSettings.json b/Serverless/AwsLambda.Webhook/lambda-bot/Properties/launchSettings.json new file mode 100644 index 00000000..60631d12 --- /dev/null +++ b/Serverless/AwsLambda.Webhook/lambda-bot/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "Mock Lambda Test Tool": { + "commandName": "Executable", + "commandLineArgs": "--port 5050", + "workingDirectory": ".\\bin\\$(Configuration)\\netcoreapp3.1", + "executablePath": "C:\\Users\\%USERNAME%\\.dotnet\\tools\\dotnet-lambda-test-tool-3.1.exe" + } + } +} \ No newline at end of file diff --git a/Serverless/AzureFunctions.IsolatedProcess.Webhook/Properties/launchSettings.json b/Serverless/AzureFunctions.IsolatedProcess.Webhook/Properties/launchSettings.json new file mode 100644 index 00000000..ab5547aa --- /dev/null +++ b/Serverless/AzureFunctions.IsolatedProcess.Webhook/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "profiles": { + "AzureFunctions_IsolatedProcess_Webhook": { + "commandName": "Project", + "commandLineArgs": "--port 7071", + "launchBrowser": false + } + } +} diff --git a/Webhook.Controllers/BotConfiguration.cs b/Webhook.Controllers/BotConfiguration.cs new file mode 100644 index 00000000..8ecb54a0 --- /dev/null +++ b/Webhook.Controllers/BotConfiguration.cs @@ -0,0 +1,6 @@ +public class BotConfiguration +{ + public string BotToken { get; init; } = default!; + public Uri BotWebhookUrl { get; init; } = default!; + public string SecretToken { get; init; } = default!; +} diff --git a/Webhook.Controllers/Controllers/BotController.cs b/Webhook.Controllers/Controllers/BotController.cs index 01f5dc3e..96ccea10 100644 --- a/Webhook.Controllers/Controllers/BotController.cs +++ b/Webhook.Controllers/Controllers/BotController.cs @@ -1,26 +1,36 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Telegram.Bot; using Telegram.Bot.Filters; -using Telegram.Bot.Services; using Telegram.Bot.Types; +using Webhook.Controllers.Services; -namespace Telegram.Bot.Controllers; +namespace Webhook.Controllers.Controllers; -public class BotController : ControllerBase +[ApiController] +[Route("[controller]")] +public class BotController(IOptions Config) : ControllerBase { + [HttpGet("setWebhook")] + public async Task SetWebHook([FromServices] TelegramBotClient bot, CancellationToken ct) + { + var webhookUrl = Config.Value.BotWebhookUrl.AbsoluteUri; + await bot.SetWebhookAsync(webhookUrl, allowedUpdates: [], secretToken: Config.Value.SecretToken, cancellationToken: ct); + return $"Webhook set to {webhookUrl}"; + } + [HttpPost] - [ValidateTelegramBot] - public async Task Post( - [FromBody] Update update, - [FromServices] UpdateHandler handleUpdateService, - CancellationToken cancellationToken) + public async Task Post([FromBody] Update update, [FromServices] UpdateHandler handleUpdateService, CancellationToken ct) { + if (Request.Headers["X-Telegram-Bot-Api-Secret-Token"] != Config.Value.SecretToken) + return Forbid(); try { - await handleUpdateService.HandleUpdateAsync(update, cancellationToken); + await handleUpdateService.HandleUpdateAsync(update, ct); } catch (Exception exception) { - await handleUpdateService.HandleErrorAsync(exception, Polling.HandleErrorSource.HandleUpdateError, cancellationToken); + await handleUpdateService.HandleErrorAsync(exception, ct); } return Ok(); } diff --git a/Webhook.Controllers/Extensions.cs b/Webhook.Controllers/Extensions.cs deleted file mode 100644 index 100eda15..00000000 --- a/Webhook.Controllers/Extensions.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.Extensions.Options; - -#pragma warning disable CA1050 // Declare types in namespaces -#pragma warning disable RCS1110 // Declare types in namespaces -public static class WebhookExtensions -#pragma warning restore RCS1110 // Declare types in namespaces -#pragma warning restore CA1050 // Declare types in namespaces -{ - public static T GetConfiguration(this IServiceProvider serviceProvider) - where T : class - { - var o = serviceProvider.GetService>(); - if (o is null) - throw new ArgumentNullException(nameof(T)); - - return o.Value; - } - - public static ControllerActionEndpointConventionBuilder MapBotWebhookRoute( - this IEndpointRouteBuilder endpoints, - string route) - { - var controllerName = typeof(T).Name.Replace("Controller", "", StringComparison.Ordinal); - var actionName = typeof(T).GetMethods()[0].Name; - - return endpoints.MapControllerRoute( - name: "bot_webhook", - pattern: route, - defaults: new { controller = controllerName, action = actionName }); - } -} diff --git a/Webhook.Controllers/Files/bot.gif b/Webhook.Controllers/Files/bot.gif new file mode 100644 index 00000000..1dfad4e7 Binary files /dev/null and b/Webhook.Controllers/Files/bot.gif differ diff --git a/Webhook.Controllers/Files/tux.png b/Webhook.Controllers/Files/tux.png deleted file mode 100644 index efc8f175..00000000 Binary files a/Webhook.Controllers/Files/tux.png and /dev/null differ diff --git a/Webhook.Controllers/Filters/ValidateTelegramBotRequestAttribute.cs b/Webhook.Controllers/Filters/ValidateTelegramBotRequestAttribute.cs deleted file mode 100644 index c337b95a..00000000 --- a/Webhook.Controllers/Filters/ValidateTelegramBotRequestAttribute.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.Extensions.Options; - -namespace Telegram.Bot.Filters; - -/// -/// Check for "X-Telegram-Bot-Api-Secret-Token" -/// Read more: "secret_token" -/// -[AttributeUsage(AttributeTargets.Method)] -public sealed class ValidateTelegramBotAttribute : TypeFilterAttribute -{ - public ValidateTelegramBotAttribute() - : base(typeof(ValidateTelegramBotFilter)) - { - } - - private class ValidateTelegramBotFilter : IActionFilter - { - private readonly string _secretToken; - - public ValidateTelegramBotFilter(IOptions options) - { - var botConfiguration = options.Value; - _secretToken = botConfiguration.SecretToken; - } - - public void OnActionExecuted(ActionExecutedContext context) - { - } - - public void OnActionExecuting(ActionExecutingContext context) - { - if (!IsValidRequest(context.HttpContext.Request)) - { - context.Result = new ObjectResult("\"X-Telegram-Bot-Api-Secret-Token\" is invalid") - { - StatusCode = 403 - }; - } - } - - private bool IsValidRequest(HttpRequest request) - { - var isSecretTokenProvided = request.Headers.TryGetValue("X-Telegram-Bot-Api-Secret-Token", out var secretTokenHeader); - if (!isSecretTokenProvided) return false; - - return string.Equals(secretTokenHeader, _secretToken, StringComparison.Ordinal); - } - } -} diff --git a/Webhook.Controllers/Program.cs b/Webhook.Controllers/Program.cs index f1fd7203..5c5744ce 100644 --- a/Webhook.Controllers/Program.cs +++ b/Webhook.Controllers/Program.cs @@ -1,59 +1,25 @@ using Telegram.Bot; -using Telegram.Bot.Controllers; -using Telegram.Bot.Services; var builder = WebApplication.CreateBuilder(args); -// Setup Bot configuration -var botConfigurationSection = builder.Configuration.GetSection(BotConfiguration.Configuration); -builder.Services.Configure(botConfigurationSection); - -var botConfiguration = botConfigurationSection.Get(); - -// Register named HttpClient to get benefits of IHttpClientFactory -// and consume it with ITelegramBotClient typed client. -// More read: -// https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests#typed-clients -// https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests -builder.Services.AddHttpClient("telegram_bot_client") - .AddTypedClient((httpClient, sp) => - { - BotConfiguration? botConfig = sp.GetConfiguration(); - TelegramBotClientOptions options = new(botConfig.BotToken); - return new TelegramBotClient(options, httpClient); - }); - -// Dummy business-logic service -builder.Services.AddScoped(); - -// There are several strategies for completing asynchronous tasks during startup. -// Some of them could be found in this article https://andrewlock.net/running-async-tasks-on-app-startup-in-asp-net-core-part-1/ -// We are going to use IHostedService to add and later remove Webhook -builder.Services.AddHostedService(); +// Setup bot configuration +var botConfigSection = builder.Configuration.GetSection("BotConfiguration"); +builder.Services.Configure(botConfigSection); +builder.Services.AddHttpClient("tgwebhook").RemoveAllLoggers().AddTypedClient( + httpClient => new TelegramBotClient(botConfigSection.Get()!.BotToken, httpClient)); +builder.Services.AddScoped(); +builder.Services.ConfigureTelegramBotMvc(); builder.Services.AddControllers(); -// The Telegram.Bot library heavily depends on System.Text.Json library with special Json -// settings to deserialize incoming webhook updates and send serialized responses back. -builder.Services.ConfigureTelegramBotMvc(); - var app = builder.Build(); -// Construct webhook route from the Route configuration parameter -// It is expected that BotController has single method accepting Update -app.MapBotWebhookRoute(route: botConfiguration.Route); + +// Configure the HTTP request pipeline. + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + app.MapControllers(); -app.Run(); -#pragma warning disable CA1050 // Declare types in namespaces -#pragma warning disable RCS1110 // Declare type inside namespace. -public class BotConfiguration -#pragma warning restore RCS1110 // Declare type inside namespace. -#pragma warning restore CA1050 // Declare types in namespaces -{ - public static readonly string Configuration = "BotConfiguration"; - - public string BotToken { get; init; } = default!; - public string HostAddress { get; init; } = default!; - public string Route { get; init; } = default!; - public string SecretToken { get; init; } = default!; -} +app.Run(); diff --git a/Webhook.Controllers/Properties/launchSettings.json b/Webhook.Controllers/Properties/launchSettings.json new file mode 100644 index 00000000..454bd278 --- /dev/null +++ b/Webhook.Controllers/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:5131", + "sslPort": 44315 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "bot/setWebhook", + "applicationUrl": "http://localhost:5227", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "bot/setWebhook", + "applicationUrl": "https://localhost:7057;http://localhost:5227", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "bot/setWebhook", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Webhook.Controllers/Services/ConfigureWebhook.cs b/Webhook.Controllers/Services/ConfigureWebhook.cs deleted file mode 100644 index f9fffca5..00000000 --- a/Webhook.Controllers/Services/ConfigureWebhook.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Microsoft.Extensions.Options; -using Telegram.Bot.Types.Enums; - -namespace Telegram.Bot.Services; - -public class ConfigureWebhook : IHostedService -{ - private readonly ILogger _logger; - private readonly IServiceProvider _serviceProvider; - private readonly BotConfiguration _botConfig; - - public ConfigureWebhook( - ILogger logger, - IServiceProvider serviceProvider, - IOptions botOptions) - { - _logger = logger; - _serviceProvider = serviceProvider; - _botConfig = botOptions.Value; - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - using var scope = _serviceProvider.CreateScope(); - var botClient = scope.ServiceProvider.GetRequiredService(); - - // Configure custom endpoint per Telegram API recommendations: - // https://core.telegram.org/bots/api#setwebhook - // If you'd like to make sure that the webhook was set by you, you can specify secret data - // in the parameter secret_token. If specified, the request will contain a header - // "X-Telegram-Bot-Api-Secret-Token" with the secret token as content. - var webhookAddress = $"{_botConfig.HostAddress}{_botConfig.Route}"; - _logger.LogInformation("Setting webhook: {WebhookAddress}", webhookAddress); - await botClient.SetWebhookAsync( - url: webhookAddress, - allowedUpdates: Array.Empty(), - secretToken: _botConfig.SecretToken, - cancellationToken: cancellationToken); - } - - public async Task StopAsync(CancellationToken cancellationToken) - { - /* You shouldn't delete webhook on app stop (typically when recycled by your host provider) - otherwise your app will not be restarted by the next incoming Update - - using var scope = _serviceProvider.CreateScope(); - var botClient = scope.ServiceProvider.GetRequiredService(); - - // Remove webhook on app shutdown - _logger.LogInformation("Removing webhook"); - await botClient.DeleteWebhookAsync(cancellationToken: cancellationToken); - */ - } -} diff --git a/Webhook.Controllers/Services/UpdateHandler.cs b/Webhook.Controllers/Services/UpdateHandler.cs index 32add61d..ff4791de 100644 --- a/Webhook.Controllers/Services/UpdateHandler.cs +++ b/Webhook.Controllers/Services/UpdateHandler.cs @@ -1,25 +1,25 @@ +using Telegram.Bot; using Telegram.Bot.Exceptions; -using Telegram.Bot.Polling; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.InlineQueryResults; using Telegram.Bot.Types.ReplyMarkups; -namespace Telegram.Bot.Services; +namespace Webhook.Controllers.Services; public class UpdateHandler { - private readonly ITelegramBotClient _bot; + private readonly TelegramBotClient _bot; private readonly ILogger _logger; private static readonly InputPollOption[] PollOptions = ["Hello", "World!"]; - public UpdateHandler(ITelegramBotClient bot, ILogger logger) + public UpdateHandler(TelegramBotClient bot, ILogger logger) { _bot = bot; _logger = logger; } - public async Task HandleErrorAsync(Exception exception, HandleErrorSource source, CancellationToken cancellationToken) + public async Task HandleErrorAsync(Exception exception, CancellationToken cancellationToken) { _logger.LogInformation("HandleError: {exception}", exception); // Cooldown in case of network connection error @@ -90,7 +90,7 @@ async Task SendPhoto(Message msg) { await _bot.SendChatActionAsync(msg.Chat, ChatAction.UploadPhoto); await Task.Delay(2000); // simulate a long task - await using var fileStream = new FileStream("Files/tux.png", FileMode.Open, FileAccess.Read); + await using var fileStream = new FileStream("Files/bot.gif", FileMode.Open, FileAccess.Read); return await _bot.SendPhotoAsync(msg.Chat, fileStream, caption: "Read https://telegrambots.github.io/book/"); } diff --git a/Webhook.Controllers/Webhook.Controllers.csproj b/Webhook.Controllers/Webhook.Controllers.csproj index d05c1249..eff7b23c 100644 --- a/Webhook.Controllers/Webhook.Controllers.csproj +++ b/Webhook.Controllers/Webhook.Controllers.csproj @@ -11,12 +11,8 @@ - - - - - - Always + + PreserveNewest diff --git a/Webhook.Controllers/appsettings.Development.json b/Webhook.Controllers/appsettings.Development.json index 8c9190bd..0c208ae9 100644 --- a/Webhook.Controllers/appsettings.Development.json +++ b/Webhook.Controllers/appsettings.Development.json @@ -1,16 +1,8 @@ { "Logging": { - "IncludeScopes": false, "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" + "Default": "Information", + "Microsoft.AspNetCore": "Warning" } - }, - "BotConfiguration": { - "BotToken": "{BOT_TOKEN}", - "HostAddress": "https://mydomain.com", - "Route": "/bot", - "SecretToken": "SOME-SECRET-STRING" } } diff --git a/Webhook.Controllers/appsettings.json b/Webhook.Controllers/appsettings.json index af3ff015..55bd7658 100644 --- a/Webhook.Controllers/appsettings.json +++ b/Webhook.Controllers/appsettings.json @@ -1,14 +1,14 @@ { "Logging": { - "IncludeScopes": false, "LogLevel": { - "Default": "Warning" + "Default": "Information", + "Microsoft.AspNetCore": "Warning" } }, + "AllowedHosts": "*", "BotConfiguration": { - "BotToken": "{BOT_TOKEN}", - "HostAddress": "https://mydomain.com", - "Route": "/bot", + "BotToken": "YOUR_BOT_TOKEN", + "BotWebhookUrl": "https://your.public.host/bot", "SecretToken": "SOME-SECRET-STRING" } } diff --git a/Webhook.MinimalAPIs/Program.cs b/Webhook.MinimalAPIs/Program.cs index 591b0552..decf9834 100644 --- a/Webhook.MinimalAPIs/Program.cs +++ b/Webhook.MinimalAPIs/Program.cs @@ -10,7 +10,7 @@ var app = builder.Build(); app.UseHttpsRedirection(); -app.MapGet("/setWebhook", async (TelegramBotClient bot) => { await bot.SetWebhookAsync(webhookUrl); return $"Webhook set to {webhookUrl}"; }); +app.MapGet("/bot/setWebhook", async (TelegramBotClient bot) => { await bot.SetWebhookAsync(webhookUrl); return $"Webhook set to {webhookUrl}"; }); app.MapPost("/bot", OnUpdate); app.Run(); diff --git a/Webhook.MinimalAPIs/Properties/launchSettings.json b/Webhook.MinimalAPIs/Properties/launchSettings.json index 0226867f..d3c9414c 100644 --- a/Webhook.MinimalAPIs/Properties/launchSettings.json +++ b/Webhook.MinimalAPIs/Properties/launchSettings.json @@ -13,7 +13,7 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "launchUrl": "setWebhook", + "launchUrl": "bot/setWebhook", "applicationUrl": "http://localhost:5225", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" @@ -23,7 +23,7 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "launchUrl": "setWebhook", + "launchUrl": "bot/setWebhook", "applicationUrl": "https://localhost:7262;http://localhost:5225", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" @@ -32,7 +32,7 @@ "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, - "launchUrl": "setWebhook", + "launchUrl": "bot/setWebhook", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }