Migration guide for version 21.x

Important notes:

Renamed parameter replyToMessageId:replyParameters:

That parameter was renamed and you can still pass a messageId for simple replies.

Or you can pass a ReplyParameters structure for more advanced reply configuration.

Renamed parameter disableWebPagePreview:linkPreviewOptions:

That parameter was renamed and you can still pass true to disable web preview.

Or you can pass a LinkPreviewOptions structure for more precise preview configuration.

Changed bool?bool

Many boolean parameters or fields are now simply of type bool.

In most cases, it shouldn't impact your existing code, or rather simplify it. Previously null values are now just false.

Changed ParseMode?ParseMode

When you don't need to specify a ParseMode, just pass default or ParseMode.None.

Better backward-compatibility and simplification of code

We added/restored features & implicit conversions that make your code simpler:

  • InputFile: just pass a string/Stream for file_id/url/stream content (as was possible in previous versions of Telegram.Bot)
  • InputMedia*: just pass an InputFile when you don't need to associate caption or such
  • MessageId: auto-converts to/from int (and also from Message)
  • ReactionType: just pass a string when you want to send an emoji
  • ReactionType: just pass a long when you want to send a custom emoji (id)
  • Some other obvious implicit conversion operators for structures containing a single property
  • No more enforcing init; properties, so you can adjust the content of fields as you wish or modify a structure returned by the API (before passing it back to the API if you want)
  • No more JSON "required properties" during deserialization, so your old saved JSON files won't break if a field is added/renamed.
  • Restored some MessageType enum values that were removed (renamed) recently (easier compatibility)

MaybeInaccessibleMessage

This class hierarchy was introduced in Bot API 7.0 and broke existing code and added unnecessary complexity.

This was removed in our library v21 and you will just receive directly a Message (as before).

To identify an "inaccessible message", you can just check message.Type == MessageType.Unknown or message.Date == default.

Chat and ChatFullInfo

In previous versions, the big Chat structure contained many fields that were filled only after a call to GetChatAsync.

This structure is now split into Chat and ChatFullInfo structures.

The new Chat structure contains only common fields that are always filled. The new ChatFullInfo structure inherits from Chat and is returned only by GetChatAsync method, with all the extra fields.

Request structures

Request structures (types ending with Request) are NOT the recommended way to use the library in your projects.

They are to be considered as low-level raw access to Bot API structures for advanced programmers, and might change/break at any time in the future.

If you have existing code using them, you can use the MakeRequestAsync method to send those requests. (Other methods based on those requests will be removed soon)

Payments with Telegram Stars

To make a payment in Telegram Stars with SendInvoiceAsync, set the following parameters:

  • providerToken: null or ""
  • currency: "XTR"
  • prices: with a single price
  • no tip amounts

Webhooks with System.Text.Json

The library now uses System.Text.Json instead of NewtonsoftJson.

To make it work in your ASP.NET projects, you should now:

  • Remove package Microsoft.AspNetCore.Mvc.NewtonsoftJson from your project dependencies
  • Follow our Webhook page to configure your web app correctly

InputPollOption in SendPollAsync

SendPollAsync now expect an array of InputPollOption instead of string.

But we added an implicit conversion from string to InputPollOption, so the change is minimal:

// before:
await bot.SendPollAsync(chatId, "question", new[] { "answer1", "answer2" });
// after:
await bot.SendPollAsync(chatId, "question", new InputPollOption[] { "answer1", "answer2" });

Global cancellation token (v21.2)

You can now specify a global CancellationToken directly in TelegramBotClient constructor.

This way, you won't need to pass a cancellationToken to every method call after that (if you just need one single cancellation token for stopping your bot)

Polling system now catch exceptions in your HandleUpdate code (v21.3)

warning

That's a change of behaviour, but most of you will probably welcome this change

If you forgot to wrap your HandleUpdateAsync code in a big try..catch, and your code happen to throw an exception, this would previously stop the polling completely.

Now the Polling system will catch your exceptions, pass them to your HandleErrorAsync method and continue the polling.

In previous versions of the library:

  • ReceiveAsync would throw out the exception (therefore stopping the polling)
  • StartReceiving would pass the exception to HandlePollingErrorAsync and silently stop the polling

If you still want the previous behaviour, have your HandleErrorAsync start like this:

Task HandleErrorAsync(ITelegramBotClient bot, Exception ex, HandleErrorSource source, CancellationToken ct)
{
   if (source is HandleErrorSource.HandleUpdateError) throw ex;
   ...

New helpers/extensions to simplify your code (v21.5)

  • When replying to a message, you can now simply pass a Message for replyParameters: rather than a Message.MessageId
  • Update.AllTypes is a constant array containing all UpdateTypes. You can pass it for the allowedUpdates: parameter (GetUpdatesAsync/SetWebhookAsync)
  • Message has now 2 extensions methods: .ToHtml() and .ToMarkdown() to convert the message text/caption and their entities into a simple Html or Markdown string.
  • You can also use methods Markdown.Escape() and HtmlText.Escape() to sanitize reserved characters from strings
  • Reply/Inline Keyboard Markup now have construction methods to simplify building keyboards dynamically:
var replyMarkup = new InlineKeyboardMarkup()
    .AddButton(InlineKeyboardButton.WithUrl("Link to Repository", "https://github.com/TelegramBots/Telegram.Bot"))
    .AddNewRow().AddButton("callback").AddButton("caption", "data")
    .AddNewRow("with", "three", "buttons")
    .AddNewRow().AddButtons("A", "B", InlineKeyboardButton.WithSwitchInlineQueryCurrentChat("switch"));
  • Same for ReplyKeyboardMarkup (and you can use new ReplyKeyboardMarkup(true) to resize keyboard)

As previously announced, the Request-typed methods are gone. But you can still send Request structures via the MakeRequestAsync method.

Simplified polling with events (v21.7)

Instead of StartReceiving/ReceiveAsync system, you can now simply set 2 or 3 events on the botClient:

  • bot.OnMessage += ... to receive Message updates
  • bot.OnUpdate += ... to receive other updates (or all updates if you don't set OnMessage)
  • bot.OnError += ... to handle errors/exceptions during polling or your handlers

Note: Second argument to OnMessage event specifies which kind of update it was (edited, channel or business message?)

When you assign those events, polling starts automatically, you don't have anything to do.
Polling will stop when you remove (-=) your events, or when you cancel the global cancellation token

You can also use await bot.DropPendingUpdatesAsync() before setting those events to ignore past updates.
The Console example project has been updated to demonstrate these events.

Automatic retrying API calls in case of "Too Many Requests" (v21.9)

If Telegram servers fail on your API call with this error and ask you to to retry in less than 60 seconds, TelegramBotClient will now automatically wait for the requested delay and retry sending the same request up to 3 times.

This is configurable using Options on the constructor:

using var cts = new CancellationTokenSource();
var options = new TelegramBotClientOptions(token) { RetryThreshold = 120, RetryCount = 2 };
var bot = new TelegramBotClient(options, cancellationToken: cts.Token);

*️⃣ This is a change of behavior compared to previous versions, but probably a welcome one.
To disable this system and keep the same behavior as previous versions, use RetryThreshold = 0

Notes for advanced users:

  • If this happens while uploading files (InputFile streams), the streams will be reset to their start position in order to be sent again
  • If your streams are non-seekable (no problem with MemoryStream/FileStream), the full HTTP request to Bot API will be buffered before the first sending (so it can lead to a temporary use of memory if you're sending big files)