Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
- Event handlers are still invoked without an await, so that a slow subscriber cannot hold up other updates — but a failure inside one is now logged instead of being lost with the unobserved task.
- The `FastBotTemplate` console template held the process open with an empty `while(true) { }`, which spins a CPU core at 100% for as long as the bot runs. It now awaits `Task.Delay(Timeout.Infinite)`. In the same template `bot.StartAsync()` was fire-and-forget (`_ = ...`), so a failure while the bot was starting vanished without a trace; it is awaited now.
- The confirmation example in `ConsoleExample` wrapped a button whose header was `ExampleThree`, but the handler for that header reads `EntityTCommand<string>` while the button carried `EntityTCommand<long>`. Pressing "Yes" therefore threw a `JsonException` inside the converter, which logged it and returned null, so the handler quietly did nothing. The example now uses `ExampleTwo`, whose handler reads the type the button actually carries.
- `ValidateTelegramBotAttribute` in the ASP.NET webhook example accepted every request. A stray semicolon after the `if` turned the secret-token comparison into an empty statement, so `return true` ran unconditionally on the first bot in the list — any request carrying the header passed the filter, whatever the header held. `BotController` compares the token again before handling an update, so the shipped example was not itself exploitable, but the filter is what people copy into their own projects, and on its own it protected nothing. The same code was printed on the webhook page of the documentation.
- `FileInlineConverter(string path)` ignored the folder name it was given and always used a folder literally called `path`. It now uses the name passed in, and rejects an empty one. Anyone who used this constructor was writing to the wrong folder; after upgrading, the inline payloads move to the folder they asked for, so any confirmation still pending at the moment of the upgrade will not be found.
- `InlineConfirmation.ActionWithConfirmation` dereferenced the parsed command without checking it. When the callback data could not be parsed — for example when a handler reads a different `EntityTCommand<T>` than the button carries — the converter returned null and the handler threw a `NullReferenceException` into the log instead of telling the user anything. It now falls through to the same "something went wrong" message as an unknown confirmation.
- Removed a duplicated byte order mark from 32 source files. It was introduced by the tooling used for the comment translation; the files compiled, but each one began with a stray zero-width character.
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
- Обработчики событий по-прежнему вызываются без `await`, чтобы медленный подписчик не задерживал остальные update, — но сбой внутри такого обработчика теперь логируется, а не теряется вместе с необслуженной задачей.
- Консольный шаблон `FastBotTemplate` удерживал процесс пустым `while(true) { }`, который всё время работы бота крутит ядро процессора на 100%. Теперь используется `await Task.Delay(Timeout.Infinite)`. Там же `bot.StartAsync()` вызывался без ожидания (`_ = ...`), поэтому сбой при запуске бота пропадал бесследно; теперь вызов ожидается.
- Пример подтверждения в `ConsoleExample` оборачивал кнопку с заголовком `ExampleThree`, но обработчик этого заголовка читает `EntityTCommand<string>`, тогда как кнопка несла `EntityTCommand<long>`. Нажатие «Yes» из-за этого приводило к `JsonException` внутри конвертера, тот его логировал и возвращал null, и обработчик молча ничего не делал. Теперь пример использует `ExampleTwo`, чей обработчик читает тот тип, который кнопка и передаёт.
- `ValidateTelegramBotAttribute` в примере с webhook на ASP.NET пропускал любой запрос. Лишняя точка с запятой после `if` превращала сравнение секретного токена в пустой оператор, поэтому `return true` выполнялся безусловно на первом же боте из списка — фильтр проходил любой запрос с этим заголовком, каким бы ни было его значение. `BotController` сверяет токен ещё раз перед обработкой update, поэтому сам пример проэксплуатировать было нельзя, но именно фильтр люди копируют к себе в проекты, а сам по себе он не защищал ни от чего. Тот же код был напечатан на странице документации про webhook.
- `FileInlineConverter(string path)` игнорировал переданное имя папки и всегда использовал папку с буквальным именем `path`. Теперь используется переданное имя, а пустое отвергается. Кто пользовался этим конструктором, писал не в ту папку; после обновления данные inline-кнопок переедут в запрошенную папку, поэтому подтверждения, ожидавшие ответа на момент обновления, найдены не будут.
- `InlineConfirmation.ActionWithConfirmation` обращался к разобранной команде без проверки. Если callback_data разобрать не удавалось — например, когда обработчик читает не тот `EntityTCommand<T>`, который несёт кнопка, — конвертер возвращал null, а обработчик писал в лог `NullReferenceException` вместо того, чтобы что-то сказать пользователю. Теперь это приводит к тому же сообщению «что-то пошло не так», что и неизвестное подтверждение.
- Из 32 файлов исходников убран продублированный BOM. Он появился из-за инструмента, которым переводились комментарии; файлы компилировались, но каждый начинался с лишнего символа нулевой ширины.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ private bool IsValidRequest(HttpRequest request)
foreach (var bot in bots)
{
var secretToken = bot.Options.WebHookOptions.SecretToken;
if (string.Equals(secretTokenHeader, secretToken, StringComparison.Ordinal));
if (string.Equals(secretTokenHeader, secretToken, StringComparison.Ordinal))
return true;
}
return false;
Expand Down
4 changes: 4 additions & 0 deletions docs/en/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# Table of contents

* [PRTelegramBot](README.md)
* [F.A.Q.](faq.md)
* [Migrating to 1.0](migrating-to-1.0.md)
* [Getting started](getting-started/README.md)
* [Webhook](getting-started/webhook/README.md)
* [Debugging a webhook](getting-started/webhook/debugging-webhook.md)
43 changes: 43 additions & 0 deletions docs/en/faq.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
description: Problems people run into most often, and what fixes them.
---

# F.A.Q.

## The bot fails to start with "404 not found"

The token is not valid. Check that it was copied from BotFather in full, with no stray whitespace, and that it still belongs to an existing bot.

## The bot ignores inline commands, or does not respond at all

Several people have hit this and the fix each time was to regenerate the bot's token in BotFather with **/revoke**, then use the new one.

If only *some* commands are ignored, check the **BotId** instead: a handler attribute with a bot id that does not match the one on the builder is simply never called.

## "Unable to find package Telegram.Bot with version xxx"

From version 20 the Telegram.Bot team published to their own feed at `https://nuget.voids.site/`, and NuGet could not find the package on nuget.org.

This is no longer relevant — from version 22 the package is back on nuget.org, and PRTelegramBot 1.0.0 uses Telegram.Bot 22.10.2.1.

If you are pinned to an older version and hit this, add the extra source once:

```sh
dotnet nuget add source https://nuget.voids.site/v3/index.json -n voids
```

In Visual Studio the same setting lives under **Tools → Options → NuGet Package Manager → Package Sources**.

## A callback button does nothing, and the log shows a JsonException

Something is reading the callback data as a different type than the button carries. `EntityTCommand<long>` and `EntityTCommand<string>` serialise the identifier differently, so a handler expecting one and a button sending the other cannot be parsed:

```
System.Text.Json.JsonException: The JSON value could not be converted to System.String. Path: $.d.1
```

The converter catches that, logs it and returns `null`, so the handler quietly does nothing. Check that the type in `context.GetCommandByCallbackOrNull<T>()` matches the type used when the button was built.

## Where do I report a bug or ask a question?

Questions go to the [Telegram chat](https://t.me/prethinkdev). Bugs and feature requests go to [GitHub issues](https://github.com/prethink/PRTelegramBot/issues).
107 changes: 107 additions & 0 deletions docs/en/getting-started/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---
description: Everything you need to do to get your first bot running.
---

# Getting started

## Create a bot in BotFather

Every Telegram bot is registered through [@BotFather](https://t.me/botfather), the official Telegram service for that.

1. Open Telegram and find **BotFather**.
2. Start the conversation with **/start**.
3. Send **/newbot** to create a new bot.
4. Give the bot a name and a username when asked.
5. BotFather replies with an access token, something like `1234567890:ABCDEFGHIJKLMNOPQRSTUVXYZ`.
6. Copy it. That token is unique to your bot and is what authenticates every call to the Telegram API.

{% hint style="warning" %}
Keep the token out of version control. Use [user secrets](https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets), environment variables, or a configuration file that is excluded from the repository.
{% endhint %}

## Install the package

The library targets .NET 6.0 and runs on any newer version.

```sh
dotnet new console -o MyBot
cd MyBot
dotnet add package PRTelegramBot
```

Or install **PRTelegramBot** from the NuGet package manager in your IDE. The package page is [here](https://www.nuget.org/packages/PRTelegramBot), and the source is on [GitHub](https://github.com/prethink/PRTelegramBot).

## Start the bot

```csharp
using PRTelegramBot.Builders;
using PRTelegramBot.Models.EventsArgs;

// A PRTelegramBot instance.
var telegram = new PRBotBuilder("Token").Build();

// Ordinary log messages.
telegram.Events.OnCommonLog += Telegram_OnLogCommon;
// Errors.
telegram.Events.OnErrorLog += Telegram_OnLogError;

// Start the bot.
await telegram.StartAsync();

async Task Telegram_OnLogError(ErrorLogEventArgs e)
{
// Handle errors.
}

async Task Telegram_OnLogCommon(CommonLogEventArgs e)
{
// Handle log messages.
}
```

Everything the builder can configure — admins, white lists, middleware, converters, background tasks, webhook settings — is passed through `PRBotBuilder`. That page has not been translated yet; until it is, see the Russian [PRBotBuilder](https://prethink.gitbook.io/prtelegrambot/prbotbuilder-sozdanie-botov) page, where the code is language-neutral.

## Add a command

A handler is an ordinary method marked with an attribute. Nothing registers it by hand — the framework finds it by reflection when the bot starts, so adding a command means adding a method.

```csharp
using PRTelegramBot.Attributes;
using PRTelegramBot.Interfaces;
using PRTelegramBot.Services.Messages;

public static class Commands
{
// Runs when the user sends /start.
[SlashHandler("/start")]
public static async Task Start(IBotContext context)
{
await MessageSender.Send(context, "Hello, World!");
}

// Runs when the message text is exactly "Ping", ignoring case.
[ReplyMenuHandler("Ping")]
public static async Task Ping(IBotContext context)
{
await MessageSender.Send(context, "Pong");
}
}
```

Run the project, send `/start` to your bot, and it answers.

By default updates arrive through [polling](https://core.telegram.org/bots/faq#how-do-i-get-updates), which needs no public address and is the quickest way to start developing. Running behind a public URL instead is described under [Webhook](webhook/).

## Several bots in one project

One project can run any number of bots. They are told apart by **BotId**, which you set on the builder and repeat on the handler attributes.

You might run five bots that all do the same thing, or five that each do something different — both work.

## Examples

| Example | What it shows |
| --- | --- |
| [Console](https://github.com/prethink/PRTelegramBot/tree/master/Examples/ConsoleExample) | Most of the framework in one place: commands of every kind, menus, events, middleware, background tasks. Start here. |
| [ASP.NET](https://github.com/prethink/PRTelegramBot/tree/master/Examples/AspNetExample) | A bot inside ASP.NET Core with everything resolved through dependency injection. Polling. |
| [ASP.NET webhook](https://github.com/prethink/PRTelegramBot/tree/master/Examples/AspNetWebHookExample) | Two bots on a single webhook endpoint, told apart by their secret token. |
Loading
Loading