Неофициальный .NET SDK для MAX Bot API · Unofficial .NET SDK for MAX Bot API
- О проекте
- Установка
- Быстрый старт
- Конфигурация
- Dependency Injection
- Получение обновлений
- Справочник API методов
- Построитель сообщений
- Обработка ошибок
Max.BotClient — библиотека для разработки ботов на платформе MAX.
Мультитаргет: .NET Standard 2.0 (для совместимости с .NET Framework 4.6.1+ и .NET Core 2.0+) и .NET 10.0 (с расширениями для DI).
Ключевые возможности:
- Все методы MAX Bot API с полной типизацией
- Единый класс
Messageдля создания, чтения и редактирования сообщений (builder pattern) - Long Polling из коробки с автоматической пагинацией через маркеры
- Поддержка Webhook-подписок
- Автоматические повторные запросы при ошибках 429/5xx (exponential backoff)
- Встроенный rate limiting через семафор (отдельные лимиты для обычных и polling запросов)
- Расширение
AddMaxBotClient()дляIServiceCollection(на .NET 10.0+) - Поддержка вложений: фото, видео, аудио, файлы, стикеры, контакты, геолокация
- Inline-клавиатуры с fluent-builder
dotnet add package Max.BotClientИли через PackageReference в .csproj:
<PackageReference Include="Max.BotClient" Version="*" />using Max.BotClient;
using Max.BotClient.Types;
// Создание клиента
var bot = new BotClient("ВАШ_ТОКЕН");
// Получение информации о боте
var me = await bot.GetMe();
Console.WriteLine($"Бот: @{me.Username}");
// Отправка текстового сообщения пользователю
await bot.SendMessage(userId, new Message("Привет!"));
// Отправка сообщения в чат
await bot.SendMessage(chatId, new Message("Привет, чат!").ToChat());var options = new BotClientOptions("ВАШ_ТОКЕН")
{
RetryCount = 3, // Максимум повторных попыток при 429/5xx (по умолчанию: 3)
RetryDelaySeconds = 1, // Начальная задержка для exponential backoff в секундах (по умолчанию: 1)
// Фактические задержки: 1s, 2s, 4s...
RPS = 30, // Лимит обычных запросов в секунду (по умолчанию: 30; 0 — без ограничения)
PollingRPS = 2 // Лимит polling-запросов в секунду (по умолчанию: 2; 0 — без ограничения)
};
var bot = new BotClient(options);
// Можно передать собственный HttpClient
var httpClient = new HttpClient();
var bot2 = new BotClient(options, httpClient);О rate limiting. SDK сериализует исходящие запросы через семафор и гарантирует минимальный интервал между стартами запросов:
ceil(1000 / RPS)мс для обычных вызовов иceil(1000 / PollingRPS)мс для long polling. Обычный и polling клиенты имеют независимые счётчики. Установите0чтобы полностью отключить ограничение.
О сертификате Минцифры. Согласно changelog MAX, с 19 июля 2026 для корректной работы чат-ботов необходимо добавить сертификат Минцифры в список доверенных на машине, где запущен бот.
Доступно при таргете .NET 10.0+. Регистрирует
BotClientиIBotClientкак singleton и подключает их черезIHttpClientFactory(named client"MaxBotClient").
using Max.BotClient;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddMaxBotClient("ВАШ_ТОКЕН", options =>
{
options.RPS = 30;
options.PollingRPS = 2;
options.RetryCount = 3;
});
var provider = services.BuildServiceProvider();
var bot = provider.GetRequiredService<IBotClient>();AddHttpClient("MaxBotClient") регистрируется автоматически — при необходимости можно подключить дополнительные delegating handlers (Polly, метрики, логирование):
services.AddMaxBotClient("ВАШ_ТОКЕН");
services.AddHttpClient("MaxBotClient")
.AddPolicyHandler(/* ваша Polly-политика */);Метод StartReceiving запускает опрос в фоновом потоке и не блокирует выполнение:
var cts = new CancellationTokenSource();
bot.StartReceiving(
updateHandler: async (bot, update, ct) =>
{
Console.WriteLine($"Тип обновления: {update.UpdateType}");
if (update.Message != null)
{
var msg = update.Message;
Console.WriteLine($"Сообщение от {msg.Sender?.Name}: {msg.Text}");
// Ответить в тот же чат/диалог
await bot.SendMessage(
msg.RecipientId ?? 0,
new Message("Получил твоё сообщение!").ToChat()
);
}
},
errorHandler: async (bot, ex, ct) =>
{
Console.WriteLine($"Ошибка: {ex.Message}");
},
options: new ReceiverOptions
{
Timeout = 30, // Таймаут long polling в секундах (0–90)
Limit = 100, // Обновлений за запрос (1–1000)
DropPendingUpdates = true, // Пропустить накопившиеся обновления при старте
AllowedUpdates = new[] // Фильтр типов обновлений (null = все)
{
UpdateType.MessageCreated,
UpdateType.MessageCallback
}
},
cancellationToken: cts.Token
);
Console.WriteLine("Бот запущен. Нажмите Ctrl+C для остановки.");
Console.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); };
await Task.Delay(Timeout.Infinite, cts.Token).ContinueWith(_ => { });Метод ReceiveAsync блокирует текущий поток до отмены:
await bot.ReceiveAsync(updateHandler, errorHandler, options, cts.Token);// Подписаться на вебхук
var result = await bot.Subscribe(
url: "https://your-domain.com/webhook",
updateTypes: new[] { UpdateType.MessageCreated, UpdateType.MessageCallback },
secret: "your-secret-key" // Для заголовка X-Max-Bot-Api-Secret
);
// Получить список активных подписок
var subscriptions = await bot.GetSubscriptions();
// Отписаться
await bot.Unsubscribe("https://your-domain.com/webhook");| Метод | HTTP | Описание |
|---|---|---|
GetMessages(chatId, from?, to?, count?) |
GET | Получить сообщения из чата (по времени) |
GetMessages(messageIds[]) |
GET | Получить сообщения по их ID |
GetMessage(messageId) |
GET | Получить одно сообщение по ID |
SendMessage(id, message, disableLinkPreview?) |
POST | Отправить сообщение |
EditMessage(messageId, message) |
PUT | Редактировать сообщение (до 24 часов) |
DeleteMessage(messageId) |
DELETE | Удалить сообщение (до 24 часов) |
AnswerCallback(callbackId, message?, notification?) |
POST | Ответить на нажатие кнопки |
// Получить последние 10 сообщений из чата
var messages = await bot.GetMessages(chatId, count: 10);
// Отправить сообщение с отключённым превью ссылок
await bot.SendMessage(userId, new Message("https://example.com"), disableLinkPreview: true);
// Редактировать сообщение
await bot.EditMessage(message.Mid, new Message("Исправленный текст"));
// Удалить сообщение
await bot.DeleteMessage(message.Mid);
// Ответить на нажатие кнопки уведомлением
await bot.AnswerCallback(callbackId, notification: "Кнопка нажата!");
// Ответить на нажатие кнопки с обновлением сообщения
await bot.AnswerCallback(callbackId, message: new Message("Новый текст сообщения"));| Метод | HTTP | Описание |
|---|---|---|
GetChats(count?, marker?) |
GET | Список чатов бота (с пагинацией) |
GetChat(chatId) |
GET | Информация о чате |
UpdateChat(chatId, request) |
PATCH | Изменить название/иконку чата |
DeleteChat(chatId) |
DELETE | Удалить чат для всех участников |
SendAction(chatId, action) |
POST | Отправить действие (typing, sending_photo...) |
GetPinnedMessage(chatId) |
GET | Получить закреплённое сообщение |
PinMessage(chatId, messageId, notify?) |
PUT | Закрепить сообщение |
UnpinMessage(chatId) |
DELETE | Открепить сообщение |
// Получить все чаты (с пагинацией)
long? marker = null;
do
{
var response = await bot.GetChats(count: 50, marker: marker);
foreach (var chat in response.Chats)
Console.WriteLine($"{chat.Title} ({chat.ChatId})");
marker = response.Marker;
} while (marker != null);
// Показать "бот печатает..."
await bot.SendAction(chatId, SenderAction.TypingOn);
// Закрепить сообщение без уведомления
await bot.PinMessage(chatId, messageId, notify: false);
// Изменить название чата
await bot.UpdateChat(chatId, new UpdateChatRequest { Title = "Новое название" });| Метод | HTTP | Описание |
|---|---|---|
GetMyMembership(chatId) |
GET | Членство бота в чате |
LeaveChat(chatId) |
DELETE | Покинуть чат |
GetChatMembers(chatId, userIds?, count?, marker?) |
GET | Список участников |
AddChatMembers(chatId, userIds[]) |
POST | Добавить участников |
RemoveChatMember(chatId, userId, block?) |
DELETE | Удалить участника |
GetChatAdmins(chatId) |
GET | Список администраторов |
AddChatAdmins(chatId, admins[]) |
POST | Назначить администраторов |
RemoveChatAdmin(chatId, userId) |
DELETE | Снять права администратора |
// Получить участников чата
var members = await bot.GetChatMembers(chatId, count: 100);
foreach (var member in members.Members)
Console.WriteLine($"{member.User?.Name} — {member.Role}");
// Добавить пользователей в чат
await bot.AddChatMembers(chatId, new[] { userId1, userId2 });
// Удалить и заблокировать участника
await bot.RemoveChatMember(chatId, userId, block: true);
// Назначить администратора с правами
await bot.AddChatAdmins(chatId, new[]
{
new ChatAdmin { UserId = userId, Permissions = AdminPermissions.ReadMessages | AdminPermissions.AddMembers }
});| Метод | HTTP | Описание |
|---|---|---|
GetSubscriptions() |
GET | Список вебхук-подписок |
Subscribe(url, updateTypes?, secret?) |
POST | Подписаться на вебхук |
Unsubscribe(url) |
DELETE | Отписаться от вебхука |
GetUpdates(limit?, timeout?, marker?, types?) |
GET | Получить обновления (low-level) |
| Метод | HTTP | Описание |
|---|---|---|
GetMe() |
GET | Информация о боте |
GetUploadUrl(type) |
POST | Получить URL для загрузки файла |
GetVideo(videoToken) |
GET | Информация о видео |
// Получить URL для загрузки фото
var upload = await bot.GetUploadUrl(UploadType.Photo);
// Загрузите файл по upload.Url, затем используйте токен в сообщении
// Получить информацию о видео
var video = await bot.GetVideo(videoAttachment.Token);
Console.WriteLine($"Длительность: {video.Duration} сек, {video.Width}x{video.Height}");Класс Message используется одновременно для создания, получения и редактирования сообщений:
// Текст с форматированием
var msg = new Message("**Жирный** и _курсив_");
// Фото по URL
var msg = new Message("Подпись")
.WithPhoto("https://example.com/photo.jpg");
// Несколько фото
var msg = new Message()
.WithPhoto("https://example.com/photo1.jpg")
.AddPhoto("https://example.com/photo2.jpg");
// Фото по токену (уже загруженное)
var msg = new Message().WithPhoto(PhotoAttachment.FromToken("token123"));
// Видео
var msg = new Message("Видео").WithVideo("video_token");
// Видео + фото в одном сообщении
var msg = new Message("Медиа")
.WithVideo("video_token")
.WithPhoto("https://example.com/photo.jpg");
// Inline-клавиатура
var msg = new Message("Выберите действие:")
.WithKeyboard(kb => kb
.AddRow()
.AddCallbackButton("Кнопка 1", "payload_1")
.AddCallbackButton("Кнопка 2", "payload_2")
.AddRow()
.AddLinkButton("Сайт", "https://example.com")
);
// Отправить в чат (по умолчанию — пользователю)
var msg = new Message("Привет!").ToChat();
// Ссылка на другое сообщение (reply/forward)
var msg = new Message("Ответ")
.WithLink(MessageLinkRequestType.Reply, messageId, chatId);bot.StartReceiving(async (bot, update, ct) =>
{
var msg = update.Message;
if (msg == null) return;
Console.WriteLine($"Текст: {msg.Text}");
Console.WriteLine($"От: {msg.Sender?.Name} ({msg.Sender?.UserId})");
Console.WriteLine($"ID: {msg.Mid}");
Console.WriteLine($"Время: {DateTimeOffset.FromUnixTimeMilliseconds(msg.Timestamp)}");
// Получить вложения
var photos = msg.GetPhotos(); // PhotoAttachment[]
var videos = msg.GetVideos(); // VideoAttachment[]
var files = msg.GetFiles(); // FileAttachment[]
var audio = msg.GetAudios(); // AudioAttachment[]
// Проверить наличие вложений
if (msg.HasAttachments())
Console.WriteLine($"Вложений: {msg.GetAllAttachments().Length}");
// Inline-клавиатура
if (update.Callback != null)
{
Console.WriteLine($"Payload кнопки: {update.Callback.Payload}");
await bot.AnswerCallback(update.Callback.CallbackId, notification: "OK");
}
});try
{
await bot.SendMessage(userId, new Message("Текст"));
}
catch (MaxBotClientApiException ex)
{
Console.WriteLine($"Код ошибки: {(int)ex.StatusCode}");
Console.WriteLine($"Тело ответа: {ex.ResponseBody}");
Console.WriteLine($"Повторяема: {ex.IsRetryable}"); // true для 429/5xx
}
catch (OperationCanceledException)
{
Console.WriteLine("Запрос отменён");
}- About
- Installation
- Quick Start
- Configuration
- Dependency Injection
- Receiving Updates
- API Methods Reference
- Message Builder
- Error Handling
Max.BotClient is a library for building bots on the MAX platform.
Multi-targets .NET Standard 2.0 (compatible with .NET Framework 4.6.1+ and .NET Core 2.0+) and .NET 10.0 (with DI extensions).
Key features:
- Full coverage of MAX Bot API with strong typing
- Single unified
Messageclass for creating, reading, and editing messages (builder pattern) - Built-in Long Polling with automatic marker-based pagination
- Webhook subscription support
- Automatic retries on 429/5xx errors (exponential backoff)
- Built-in semaphore-based rate limiting (independent limits for regular and polling requests)
AddMaxBotClient()extension forIServiceCollection(on .NET 10.0+)- Attachment support: photos, videos, audio, files, stickers, contacts, locations
- Inline keyboards with fluent builder
dotnet add package Max.BotClientOr via PackageReference in your .csproj:
<PackageReference Include="Max.BotClient" Version="*" />using Max.BotClient;
using Max.BotClient.Types;
// Create the client
var bot = new BotClient("YOUR_TOKEN");
// Get bot info
var me = await bot.GetMe();
Console.WriteLine($"Bot: @{me.Username}");
// Send a text message to a user
await bot.SendMessage(userId, new Message("Hello!"));
// Send a message to a chat
await bot.SendMessage(chatId, new Message("Hello, chat!").ToChat());var options = new BotClientOptions("YOUR_TOKEN")
{
RetryCount = 3, // Max retries on 429/5xx (default: 3)
RetryDelaySeconds = 1, // Initial delay in seconds for exponential backoff (default: 1)
// Actual delays: 1s, 2s, 4s...
RPS = 30, // Regular requests per second cap (default: 30; 0 = no limit)
PollingRPS = 2 // Long-polling requests per second cap (default: 2; 0 = no limit)
};
var bot = new BotClient(options);
// You can also inject a custom HttpClient
var httpClient = new HttpClient();
var bot2 = new BotClient(options, httpClient);About rate limiting. The SDK serializes outgoing requests through a semaphore and guarantees a minimum interval between request starts:
ceil(1000 / RPS)ms for regular calls andceil(1000 / PollingRPS)ms for long polling. The regular and polling clients have independent counters. Set the value to0to disable throttling entirely.
About the Ministry of Digital Development certificate. According to the MAX changelog, starting July 19, 2026, bots require the Russian Ministry of Digital Development (Mintsifry) certificate to be added to the trusted certificates on the machine running the bot.
Available when targeting .NET 10.0+. Registers
BotClientandIBotClientas singletons and wires them throughIHttpClientFactory(named client"MaxBotClient").
using Max.BotClient;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddMaxBotClient("YOUR_TOKEN", options =>
{
options.RPS = 30;
options.PollingRPS = 2;
options.RetryCount = 3;
});
var provider = services.BuildServiceProvider();
var bot = provider.GetRequiredService<IBotClient>();AddHttpClient("MaxBotClient") is registered automatically — you can attach extra delegating handlers (Polly, metrics, logging) on top:
services.AddMaxBotClient("YOUR_TOKEN");
services.AddHttpClient("MaxBotClient")
.AddPolicyHandler(/* your Polly policy */);StartReceiving starts polling in a background task and does not block:
var cts = new CancellationTokenSource();
bot.StartReceiving(
updateHandler: async (bot, update, ct) =>
{
Console.WriteLine($"Update type: {update.UpdateType}");
if (update.Message != null)
{
var msg = update.Message;
Console.WriteLine($"Message from {msg.Sender?.Name}: {msg.Text}");
// Reply to the same chat/dialog
await bot.SendMessage(
msg.RecipientId ?? 0,
new Message("Got your message!").ToChat()
);
}
},
errorHandler: async (bot, ex, ct) =>
{
Console.WriteLine($"Error: {ex.Message}");
},
options: new ReceiverOptions
{
Timeout = 30, // Long polling timeout in seconds (0–90)
Limit = 100, // Updates per request (1–1000)
DropPendingUpdates = true, // Skip accumulated updates on startup
AllowedUpdates = new[] // Filter update types (null = all)
{
UpdateType.MessageCreated,
UpdateType.MessageCallback
}
},
cancellationToken: cts.Token
);
Console.WriteLine("Bot started. Press Ctrl+C to stop.");
Console.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); };
await Task.Delay(Timeout.Infinite, cts.Token).ContinueWith(_ => { });ReceiveAsync blocks the current thread until cancellation:
await bot.ReceiveAsync(updateHandler, errorHandler, options, cts.Token);// Subscribe to webhook
var result = await bot.Subscribe(
url: "https://your-domain.com/webhook",
updateTypes: new[] { UpdateType.MessageCreated, UpdateType.MessageCallback },
secret: "your-secret-key" // Used for X-Max-Bot-Api-Secret header
);
// List active subscriptions
var subscriptions = await bot.GetSubscriptions();
// Unsubscribe
await bot.Unsubscribe("https://your-domain.com/webhook");| Method | HTTP | Description |
|---|---|---|
GetMessages(chatId, from?, to?, count?) |
GET | Get messages from a chat by time range |
GetMessages(messageIds[]) |
GET | Get messages by their IDs |
GetMessage(messageId) |
GET | Get a single message by ID |
SendMessage(id, message, disableLinkPreview?) |
POST | Send a message |
EditMessage(messageId, message) |
PUT | Edit a message (within 24 hours) |
DeleteMessage(messageId) |
DELETE | Delete a message (within 24 hours) |
AnswerCallback(callbackId, message?, notification?) |
POST | Answer a button callback |
// Get last 10 messages from a chat
var messages = await bot.GetMessages(chatId, count: 10);
// Send a message with link preview disabled
await bot.SendMessage(userId, new Message("https://example.com"), disableLinkPreview: true);
// Edit a message
await bot.EditMessage(message.Mid, new Message("Updated text"));
// Delete a message
await bot.DeleteMessage(message.Mid);
// Answer a button callback with a notification
await bot.AnswerCallback(callbackId, notification: "Button clicked!");
// Answer a button callback by updating the message
await bot.AnswerCallback(callbackId, message: new Message("New message text"));| Method | HTTP | Description |
|---|---|---|
GetChats(count?, marker?) |
GET | List bot's chats (paginated) |
GetChat(chatId) |
GET | Get chat info |
UpdateChat(chatId, request) |
PATCH | Update chat title/icon |
DeleteChat(chatId) |
DELETE | Delete a chat for all members |
SendAction(chatId, action) |
POST | Send a chat action (typing, sending_photo...) |
GetPinnedMessage(chatId) |
GET | Get the pinned message |
PinMessage(chatId, messageId, notify?) |
PUT | Pin a message |
UnpinMessage(chatId) |
DELETE | Unpin the current message |
// Iterate all chats with pagination
long? marker = null;
do
{
var response = await bot.GetChats(count: 50, marker: marker);
foreach (var chat in response.Chats)
Console.WriteLine($"{chat.Title} ({chat.ChatId})");
marker = response.Marker;
} while (marker != null);
// Show "bot is typing..."
await bot.SendAction(chatId, SenderAction.TypingOn);
// Pin a message silently
await bot.PinMessage(chatId, messageId, notify: false);
// Rename a chat
await bot.UpdateChat(chatId, new UpdateChatRequest { Title = "New Chat Name" });| Method | HTTP | Description |
|---|---|---|
GetMyMembership(chatId) |
GET | Bot's membership in a chat |
LeaveChat(chatId) |
DELETE | Leave a chat |
GetChatMembers(chatId, userIds?, count?, marker?) |
GET | List chat members |
AddChatMembers(chatId, userIds[]) |
POST | Add members to a chat |
RemoveChatMember(chatId, userId, block?) |
DELETE | Remove a member |
GetChatAdmins(chatId) |
GET | List chat admins |
AddChatAdmins(chatId, admins[]) |
POST | Promote admins |
RemoveChatAdmin(chatId, userId) |
DELETE | Demote an admin |
// List chat members
var members = await bot.GetChatMembers(chatId, count: 100);
foreach (var member in members.Members)
Console.WriteLine($"{member.User?.Name} — {member.Role}");
// Add users to a chat
await bot.AddChatMembers(chatId, new[] { userId1, userId2 });
// Remove and block a member
await bot.RemoveChatMember(chatId, userId, block: true);
// Promote an admin with specific permissions
await bot.AddChatAdmins(chatId, new[]
{
new ChatAdmin { UserId = userId, Permissions = AdminPermissions.ReadMessages | AdminPermissions.AddMembers }
});| Method | HTTP | Description |
|---|---|---|
GetSubscriptions() |
GET | List webhook subscriptions |
Subscribe(url, updateTypes?, secret?) |
POST | Subscribe to webhook |
Unsubscribe(url) |
DELETE | Unsubscribe from webhook |
GetUpdates(limit?, timeout?, marker?, types?) |
GET | Get updates (low-level) |
| Method | HTTP | Description |
|---|---|---|
GetMe() |
GET | Get bot info |
GetUploadUrl(type) |
POST | Get a URL to upload a file |
GetVideo(videoToken) |
GET | Get video details |
// Get an upload URL for a photo
var upload = await bot.GetUploadUrl(UploadType.Photo);
// Upload your file to upload.Url, then use the token in a message
// Get video details
var video = await bot.GetVideo(videoAttachment.Token);
Console.WriteLine($"Duration: {video.Duration}s, {video.Width}x{video.Height}");The Message class is used for creating, receiving, and editing messages all in one:
// Plain text
var msg = new Message("Hello World!");
// Text with markdown formatting
var msg = new Message("**Bold** and _italic_");
// Photo from URL
var msg = new Message("Caption").WithPhoto("https://example.com/photo.jpg");
// Multiple photos
var msg = new Message()
.WithPhoto("https://example.com/photo1.jpg")
.AddPhoto("https://example.com/photo2.jpg");
// Photo from token (already uploaded)
var msg = new Message().WithPhoto(PhotoAttachment.FromToken("token123"));
// Video
var msg = new Message("Video").WithVideo("video_token");
// Combined video + photo
var msg = new Message("Media")
.WithVideo("video_token")
.WithPhoto("https://example.com/photo.jpg");
// Inline keyboard
var msg = new Message("Choose an action:")
.WithKeyboard(kb => kb
.AddRow()
.AddCallbackButton("Button 1", "payload_1")
.AddCallbackButton("Button 2", "payload_2")
.AddRow()
.AddLinkButton("Website", "https://example.com")
);
// Send to a chat (default recipient is user)
var msg = new Message("Hello!").ToChat();
// Reply / forward
var msg = new Message("Reply text")
.WithLink(MessageLinkRequestType.Reply, messageId, chatId);bot.StartReceiving(async (bot, update, ct) =>
{
var msg = update.Message;
if (msg == null) return;
Console.WriteLine($"Text: {msg.Text}");
Console.WriteLine($"From: {msg.Sender?.Name} ({msg.Sender?.UserId})");
Console.WriteLine($"ID: {msg.Mid}");
Console.WriteLine($"Time: {DateTimeOffset.FromUnixTimeMilliseconds(msg.Timestamp)}");
// Access attachments
var photos = msg.GetPhotos(); // PhotoAttachment[]
var videos = msg.GetVideos(); // VideoAttachment[]
var files = msg.GetFiles(); // FileAttachment[]
var audio = msg.GetAudios(); // AudioAttachment[]
// Check for attachments
if (msg.HasAttachments())
Console.WriteLine($"Attachments: {msg.GetAllAttachments().Length}");
// Handle button callbacks
if (update.Callback != null)
{
Console.WriteLine($"Button payload: {update.Callback.Payload}");
await bot.AnswerCallback(update.Callback.CallbackId, notification: "OK");
}
});try
{
await bot.SendMessage(userId, new Message("Text"));
}
catch (MaxBotClientApiException ex)
{
Console.WriteLine($"Status code: {(int)ex.StatusCode}");
Console.WriteLine($"Response body: {ex.ResponseBody}");
Console.WriteLine($"Is retryable: {ex.IsRetryable}"); // true for 429/5xx
}
catch (OperationCanceledException)
{
Console.WriteLine("Request was cancelled");
}