Веб

Как решить ошибку 403 в Telegraf.js при callback_query

Telegram разрешает сообщение в chat_join_request, но запрещает в callback_query, если пользователь не открыл чат с ботом. Как решить 403 в Telegraf.js через NGINX.

Как администратор группы в Telegram может взаимодействовать с новым пользователем, который отправил запрос на вступление, используя бота? После того как бот получает обновление chat_join_request, он отправляет сообщение с кнопками, но при обработке callback_query попытка отправить новое сообщение приводит к ошибке 403: Forbidden: bot can’t initiate conversation with a user. Почему Telegram позволяет отправлять сообщение в обработчике chat_join_request, но запрещает в обработчике callback_query, хотя chat_id одинаковый? Как решить эту проблему в Telegraf.js с webhook через NGINX?

Краткий ответ
Telegram позволяет отправлять сообщение в обработчике chat_join_request, потому что это «специальное» обновление, в котором бот уже считается «запрошенным» пользователем. В обработчике callback_query бот пытается инициировать приватный диалог с пользователем, которого пользователь ещё не начал в боте, и Telegram блокирует это как 403 Forbidden: bot can’t initiate conversation with a user. Чтобы решить проблему в Telegraf.js при работе через webhook, нужно убедиться, что пользователь уже открыл приватный чат с ботом (или заставить его открыть его через switch_pm) и использовать ctx.callbackQuery.from.id как chat_id при отправке сообщения.


1. Почему Telegram ведёт себя так

Обработчик Что делает Telegram Почему сообщение разрешено / запрещено
chat_join_request При поступлении запроса на вступление Telegram отправляет обновление с полем chatJoinRequest. Бот уже получает идентификатор пользователя (from.id) и может немедленно отправить ему сообщение в приватном чате. Telegram рассматривает это как «собственное» обновление от пользователя, поэтому разрешает инициировать диалог.
callback_query Кнопка нажата в сообщении группы. Внутри callback_query содержится from.id, но это не приватный чат бота. Бот пытается отправить сообщение в приватный чат, который пользователь ещё не открыл. Telegram запрещает «инициировать» приватный чат без явного согласия пользователя. Поэтому вы получаете 403.

Ключевой момент – в обоих случаях chat_id одинаковый (идентификатор пользователя), но в первом случае Telegram позволяет, а во втором – нет, потому что в процессе callback_query пользователь ещё не начал разговор с ботом.


2. Как заставить пользователя открыть чат с ботом

2.1. switch_pm – открыть приватный чат из inline‑клавиатуры

js
bot.command('start', ctx => ctx.reply('Привет! Я бот.'));

bot.on('chat_join_request', ctx => {
  const userId = ctx.chatJoinRequest.from.id;

  // Отправляем пользователю сообщение с кнопкой «Открыть чат»
  ctx.telegram.sendMessage(
    userId,
    'Присоединяйтесь к нам! Нажмите кнопку, чтобы открыть чат с ботом.',
    {
      reply_markup: {
        inline_keyboard: [
          [
            {
              text: 'Открыть чат',
              switch_pm_text: 'Начать',
              switch_pm_parameter: 'start',
            },
          ],
        ],
      },
    }
  );
});

switch_pm_parameter – произвольный параметр, который будет передан в /start при открытии чата.

2.2. Проверка на наличие приватного чата

js
bot.on('callback_query', async ctx => {
  const userId = ctx.callbackQuery.from.id;

  try {
    await ctx.telegram.sendMessage(userId, 'Ваше сообщение');
  } catch (err) {
    if (err.response && err.response.statusCode === 403) {
      // Бот не может начать диалог
      await ctx.reply(
        'Пожалуйста, сначала откройте чат с ботом. Нажмите кнопку ниже.',
        {
          reply_markup: {
            inline_keyboard: [
              [
                {
                  text: 'Открыть чат',
                  switch_pm_text: 'Начать',
                  switch_pm_parameter: 'start',
                },
              ],
            ],
          },
        }
      );
    } else {
      throw err;
    }
  }
});

Важно – использовать ctx.telegram.sendMessage(userId, …) вместо ctx.reply, потому что ctx.reply пытается ответить в том же чате, где возник callback_query (группе), а нам нужен приватный чат.


3. Настройка Telegraf.js + webhook через NGINX

3.1. Основной скрипт бота

js
const { Telegraf } = require('telegraf');
const bot = new Telegraf(process.env.BOT_TOKEN);

// обработчики, как в примерах выше
// ...

// Устанавливаем webhook
bot.telegram.setWebhook(`https://your.domain.com/${process.env.BOT_TOKEN}`).catch(console.error);

// Запускаем локальный HTTP‑сервер (порт 3000 по умолчанию)
bot.startWebhook(`/${process.env.BOT_TOKEN}`, null, 3000);

3.2. Конфигурация NGINX

nginx
server {
    listen 80;
    server_name your.domain.com;

    location /${BOT_TOKEN} {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_cache_bypass $http_upgrade;
    }

    # При желании можно добавить HTTPS
    # ...
}

Пояснение – путь /${BOT_TOKEN} должен совпадать с тем, что вы передаёте в setWebhook. Если вы используете HTTPS, убедитесь, что сертификат валиден (Let’s Encrypt или аналог).


4. Итоги

  1. Почему различиеchat_join_request считается «специальным» обновлением, где Telegram разрешает отправку приватного сообщения, тогда как callback_query требует, чтобы пользователь уже начал чат с ботом.
  2. Как решить – заставить пользователя открыть чат через switch_pm (или switch_inline_query), проверять наличие диалога и обрабатывать 403‑ошибку.
  3. Как реализовать в Telegraf.js – использовать ctx.telegram.sendMessage(userId, …) в обработчике callback_query, обрабатывать исключения и отвечать пользователю кнопкой switch_pm.
  4. Webhook через NGINX – настроить проксирование location /<token> к локальному порту бота и установить webhook через bot.telegram.setWebhook.

Эти шаги позволят администратору группы беспрепятственно взаимодействовать с новым пользователем, даже если он не инициировал диалог с ботом с самого начала.

Авторы
Проверено модерацией
Модерация