Как создать Telegram-бота с ChatGPT: Node.js и OpenAI API

В этой статье

Telegram-бот с ChatGPT — это не специальная функция Telegram и не подключение подписки ChatGPT. Вы создаёте собственного бота, принимаете его сообщения через Telegram Bot API, передаёте текст на свой сервер, а сервер делает запрос в OpenAI Responses API. Затем ответ возвращается в тот же чат.

Схема короткая:

пользователь → Telegram → getUpdates → Node.js → OpenAI Responses API
                                                ↓
пользователь ← Telegram ← sendMessage ← ответ модели

Ниже — минимальный пример без базы данных, webhook, голосовых сообщений и отдельного Telegram-фреймворка. Он подходит, чтобы проверить маршрут на локальном компьютере или небольшом сервере. Для API понадобится собственный ключ OpenAI; постоянная бесплатность, фиксированная цена и конкретная доступность модели не гарантируются — перед запуском проверьте актуальные условия в документации OpenAI.

Что понадобится

Нужны Node.js 20 или новее, аккаунт Telegram, созданный через @BotFather, и ключ OpenAI API. Node.js 20 удобно взять как ориентир: в нём есть встроенный fetch, а официальный JavaScript SDK OpenAI устанавливается обычным npm-пакетом.

Создайте каталог проекта и установите зависимости:

mkdir telegram-chatgpt-bot
cd telegram-chatgpt-bot
npm init -y
npm install openai dotenv

В package.json добавьте режим ES-модулей:

{
  "type": "module",
  "scripts": {
    "start": "node bot.mjs"
  }
}

Если в файле уже есть другие поля, не заменяйте весь package.json: достаточно добавить "type": "module" и скрипт start.

Создайте Telegram-бота и сохраните токены

Откройте в Telegram @BotFather, выполните /newbot, задайте отображаемое имя и username, который заканчивается на bot. В ответ BotFather выдаст токен вида 123456:ABC.... Это пароль к вашему боту: не вставляйте его в Git, скриншоты, клиентский JavaScript или сообщения в чате.

Ключ OpenAI создайте в панели API. Он также должен оставаться на сервере. В проекте создайте файл .env:

TELEGRAM_BOT_TOKEN=сюда_токен_от_BotFather
OPENAI_API_KEY=сюда_ключ_OpenAI
OPENAI_MODEL=gpt-5

Добавьте .env в .gitignore:

.env
node_modules/

Если ключ уже попал в репозиторий или переписку, считайте его скомпрометированным: отзовите его и выпустите новый. Маскировка строки в интерфейсе не заменяет отзыв ключа.

Минимальный бот на Node.js

Создайте bot.mjs:

import "dotenv/config";
import OpenAI from "openai";

const telegramToken = process.env.TELEGRAM_BOT_TOKEN;
const openaiKey = process.env.OPENAI_API_KEY;
const model = process.env.OPENAI_MODEL || "gpt-5";

if (!telegramToken || !openaiKey) {
  throw new Error(
    "Set TELEGRAM_BOT_TOKEN and OPENAI_API_KEY in the environment"
  );
}

const telegramBaseUrl = `https://api.telegram.org/bot${telegramToken}`;
const openai = new OpenAI({ apiKey: openaiKey });

async function telegram(method, body = {}) {
  const response = await fetch(`${telegramBaseUrl}/${method}`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify(body),
  });

  const data = await response.json();
  if (!response.ok || !data.ok) {
    throw new Error(`Telegram ${method}: ${data.description || response.status}`);
  }
  return data.result;
}

function splitMessage(text, maxLength = 4096) {
  const chunks = [];
  for (let start = 0; start < text.length; start += maxLength) {
    chunks.push(text.slice(start, start + maxLength));
  }
  return chunks.length ? chunks : ["Модель не вернула текстовый ответ."];
}

async function answerMessage(message) {
  if (!message?.chat?.id || typeof message.text !== "string") {
    return;
  }

  const response = await openai.responses.create({
    model,
    input: message.text,
  });

  const answer = response.output_text?.trim() ||
    "Модель не вернула текстовый ответ.";

  for (const chunk of splitMessage(answer)) {
    await telegram("sendMessage", {
      chat_id: message.chat.id,
      text: chunk,
    });
  }
}

async function main() {
  const bot = await telegram("getMe");
  console.log(`Started @${bot.username}`);

  let offset = 0;

  while (true) {
    try {
      const updates = await telegram("getUpdates", {
        offset,
        timeout: 30,
        allowed_updates: ["message"],
      });

      for (const update of updates) {
        offset = update.update_id + 1;

        try {
          await answerMessage(update.message);
        } catch (error) {
          console.error("Message handling failed:", error);
          if (update.message?.chat?.id) {
            await telegram("sendMessage", {
              chat_id: update.message.chat.id,
              text: "Не удалось получить ответ. Проверьте журнал процесса и настройки API.",
            });
          }
        }
      }
    } catch (error) {
      console.error("Polling failed:", error);
      await new Promise((resolve) => setTimeout(resolve, 3000));
    }
  }
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Здесь нет скрытого состояния диалога: каждый текст пользователя становится отдельным входом в Responses API. Поэтому бот отвечает на вопросы, но не помнит предыдущие сообщения. Историю можно добавить позже через previous_response_id, Conversations API или собственное хранилище — это уже другая граница ответственности.

Вызов responses.create и свойство output_text соответствуют примеру официального OpenAI JavaScript SDK. Модель вынесена в переменную окружения: это позволяет заменить её без редактирования исходного кода после проверки доступных моделей и условий аккаунта.

Запустите polling и проверьте ответ

Запустите процесс из каталога проекта:

npm start

Сначала код вызывает getMe. При успехе в консоли появится username бота. Затем процесс ждёт обновления через getUpdates с long polling: Telegram может держать запрос до 30 секунд, а не возвращать пустой ответ каждую миллисекунду. Подробнее о методах и формате обновлений — в Telegram Bot API.

Откройте личный чат с ботом, нажмите Start и отправьте обезличенное сообщение, например Объясни разницу между REST и RPC в трёх предложениях. В ответ бот должен вернуть текст модели. Не отправляйте в первую проверку пароли, персональные данные или рабочие секреты: сообщение проходит через Telegram и OpenAI.

offset увеличивается после каждого полученного обновления. Это подтверждает обработанное событие и не даёт одному и тому же сообщению бесконечно попадать в цикл при следующем запросе. message.text проверяется до вызова OpenAI, поэтому стикеры, фото и служебные события не превращаются в пустой API-запрос. Длинный ответ режется на части: у sendMessage есть ограничение длины текста Telegram.

Если кроме собственного API-ключа вам понадобится отдельно оплачиваемый цифровой сервис, после этой технической проверки можно посмотреть каталог Amber Market с подписками и цифровыми товарами. Актуальные условия заказа нужно проверить перед покупкой; оплатить заказ можно через СБП. Каталог не заменяет регистрацию OpenAI API, ключ и оплату запросов к нему.

Если polling не получает сообщения

getUpdates и webhook — взаимоисключающие способы доставки обновлений. Если для бота ранее настраивался webhook, long polling не заработает. Проверьте состояние тем же токеном, который загружает ваш процесс:

curl "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getWebhookInfo"

Если поле url непустое, удалите webhook и перезапустите процесс:

curl -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/deleteWebhook"

В PowerShell переменная задаётся иначе — $env:TELEGRAM_BOT_TOKEN. Команды выше рассчитаны на оболочку, где переменная уже экспортирована; файл .env сам по себе не экспортирует её в curl. Для диагностики проще выполнить встроенный getMe через тот же Node.js-процесс или использовать корректный синтаксис вашей оболочки. Сам токен не вставляйте в публичную команду и не сохраняйте в истории общего сервера.

Типичная ошибка Telegram 401 Unauthorized означает неверный или отозванный токен. Выпустите новый токен в BotFather и обновите .env.

Если Telegram отвечает, но OpenAI возвращает ошибку

Ошибки 401 со стороны OpenAI обычно указывают на неверный, отозванный или неправильно загруженный OPENAI_API_KEY. Перезапустите процесс после изменения .env: значения окружения читаются при старте.

429, rate limit и insufficient_quota — разные варианты проблемы с лимитом или доступной квотой. Повторять запрос без задержки бессмысленно: можно усилить ограничение. Сначала проверьте модель, квоту, настройки проекта и актуальные лимиты в панели OpenAI. Для разбора таких случаев пригодится инструкция про ошибки 429, rate limit и insufficient_quota.

Если нужен отдельный разбор первого запроса к OpenAI, полезно свериться с инструкцией по первому API-запросу, а хранение ключа — с инструкцией по получению и безопасному использованию ключа OpenAI API.

Что добавить после минимальной проверки

Этот пример намеренно не решает задачи production-сервиса. Следующий слой обычно включает ограничение частоты запросов на пользователя, журналирование без токенов и содержания секретных сообщений, обработку остановки процесса, повтор с backoff для временных сетевых ошибок, хранение истории, команды /start и /help, а затем webhook за HTTPS.

В группах понадобится отдельно учитывать privacy mode и права бота. Для голоса, изображений и файлов меняется как вход Telegram, так и формат запроса к модели. Платежи, база данных и масштабирование не являются частью маршрута «получить текст и вернуть текст», поэтому их не стоит незаметно добавлять в минимальный пример.

После успешного локального теста перенесите проект на сервер, задайте секреты в окружении процесса и оставьте наружу только нужный сетевой доступ. Бот, созданный через BotFather по этой схеме, остаётся вашей интеграцией Telegram Bot API и OpenAI API — он не становится официальным Telegram-ботом OpenAI или ChatGPT.

Источники

Есть следующая задача?Ещё по теме «Боты и автоматизация» →