Как создать ChatGPT-бота в Telegram на Python

В этой статье

Чтобы сделать своего GPT-бота в Telegram, создайте бота через BotFather и запустите программу, которая передаёт ваши сообщения в OpenAI API, а полученный текст возвращает в чат. Ниже — пример личного бота на Python: он отвечает только указанному пользователю в личной переписке.

Это ваша интеграция с моделью OpenAI. Она не переносит в Telegram аккаунт ChatGPT, его историю и инструменты. Подписка ChatGPT Plus не оплачивает API: использование API рассчитывается отдельно. Разъяснение OpenAI.

Код проверен локально на Python 3.13 с подставными ответами сервисов. Настоящие запросы к Telegram и OpenAI в рамках проверки не отправлялись. При первом запуске проверьте доступ в своём окружении.

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

Подготовьте Python, аккаунт Telegram и доступ к OpenAI API с подходящим биллингом. Для этого примера не нужны пакеты openai и python-telegram-bot: запросы отправляет стандартная библиотека Python. Сохраните код в обычный текстовый файл bot.py, а не в документ Word.

Ключ OpenAI создаётся в API Dashboard. Выберите доступную вашему проекту текстовую модель с поддержкой Responses API и проверьте её цену перед запуском. Идентификатор модели вы введёте отдельно; название подписки ChatGPT сюда не подходит. Подготовка API.

В примере один обычный вопрос вызывает один запрос к OpenAI. Лимит входа в 4000 символов — наше ограничение программы, а не тариф OpenAI. Он не задаёт предельную стоимость: длина ответа и расчёт по выбранной модели тоже влияют на расход. Начните с одного короткого вопроса и проверьте его стоимость в своём API-проекте.

Создайте бота и получите токен

Откройте BotFather, отправьте /newbot и выполните его указания для имени и username. Сохраните выданный токен: он управляет ботом, поэтому не публикуйте его и не вставляйте в сообщение обычному боту. При утечке токен можно отозвать через BotFather. Официальная инструкция Telegram.

Если у вас уже есть собственный бот, его токен тоже подходит, но прежний обработчик нужно остановить. Этот пример использует получение обновлений через getUpdates. Оно не работает одновременно с webhook; программа обнаруживает настроенный webhook и останавливается. Для первого опыта проще создать отдельного бота, сохранив действующую интеграцию. Получение обновлений.

Сохраните код

Программа запрашивает секреты при запуске через скрытый ввод. Вставляйте их в локальном терминале: символы могут не отображаться, это нормально. Не вводите токены прямо в исходник или в переписку.

Ответ OpenAI собирается из всех текстовых элементов output, поскольку первый элемент может содержать сведения о рассуждении, а не сообщение. В HTTP-ответе нельзя рассчитывать на удобное поле SDK response.output_text. Формат результата Responses API.

import getpass
import json
import sys
import urllib.error
import urllib.request


def post(url, data, key=None):
    headers = {"Content-Type": "application/json"}
    if key:
        headers["Authorization"] = "Bearer " + key
    request = urllib.request.Request(
        url, json.dumps(data).encode("utf-8"), headers, method="POST"
    )
    try:
        with urllib.request.urlopen(request, timeout=120) as response:
            return json.load(response)
    except urllib.error.HTTPError as error:
        service = "OpenAI" if key else "Telegram"
        raise RuntimeError(f"{service}: HTTP {error.code}") from None
    except (urllib.error.URLError, TimeoutError):
        raise RuntimeError("Сбой сети или тайм-аут; проверьте ответ до повтора") from None


def telegram(token, method, data):
    result = post(f"https://api.telegram.org/bot{token}/{method}", data)
    if not result.get("ok"):
        raise RuntimeError("Telegram не подтвердил запрос")
    return result["result"]


def ask(key, model, text):
    result = post("https://api.openai.com/v1/responses", {
        "model": model,
        "instructions": "Отвечай по-русски, кратко, обычным текстом.",
        "input": text,
    }, key)
    if result.get("status") != "completed":
        raise RuntimeError("OpenAI не завершил ответ")
    parts = [part["text"] for item in result.get("output", [])
             if item.get("type") == "message"
             for part in item.get("content", [])
             if part.get("type") == "output_text"]
    answer = "\n".join(parts).strip()
    if not answer:
        raise RuntimeError("В ответе OpenAI нет текста")
    return answer


def handle(update, token, owner, key, model):
    message = update.get("message", {})
    if (message.get("chat", {}).get("type") != "private"
            or message.get("from", {}).get("id") != owner):
        return
    text = message.get("text", "").strip()
    if not text:
        answer = "Пришлите текстовое сообщение."
    elif text.startswith("/"):
        answer = "Отправьте один вопрос с полным контекстом. История не передаётся."
    elif len(text) > 4000:
        answer = "Сократите сообщение до 4000 символов."
    else:
        answer = ask(key, model, text)
    for start in range(0, len(answer), 2000):
        telegram(token, "sendMessage", {
            "chat_id": message["chat"]["id"], "text": answer[start:start + 2000]
        })


def main():
    token = getpass.getpass("Токен Telegram: ").strip()
    if telegram(token, "getWebhookInfo", {}).get("url"):
        raise RuntimeError("У бота настроен webhook. Используйте нового бота.")
    identify = "--identify" in sys.argv
    owner = key = model = None
    if not identify:
        owner = int(input("Ваш числовой Telegram ID: "))
        if owner <= 0:
            raise RuntimeError("Нужен положительный ID пользователя")
        key = getpass.getpass("Ключ OpenAI API: ").strip()
        model = input("ID доступной вам текстовой модели Responses API: ").strip()
        if not key or not model:
            raise RuntimeError("Ключ и ID модели обязательны")
    print("Жду /id в личном чате" if identify else "Бот запущен; остановка Ctrl+C")
    offset = 0
    while True:
        updates = telegram(token, "getUpdates", {
            "offset": offset, "timeout": 30, "allowed_updates": ["message"]
        })
        for update in updates:
            message = update.get("message", {})
            if identify:
                if (message.get("chat", {}).get("type") == "private"
                        and message.get("text") == "/id"):
                    user = message["from"]
                    print("ID:", user["id"], "username:", user.get("username", "нет"))
            else:
                handle(update, token, owner, key, model)
            offset = update["update_id"] + 1


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("Остановлено")
    except (RuntimeError, ValueError, KeyError) as error:
        print("Остановлено:", error)
        sys.exit(1)

Программа отправляет текст только при статусе completed; остальные состояния останавливают обработку. Статусы в официальной схеме OpenAI.

Узнайте свой числовой Telegram ID

В терминале из папки с файлом выполните:

python bot.py --identify

Введите токен Telegram. Затем откройте своего бота, нажмите Start и отправьте /id в личном чате. В терминале появятся числовой ID отправителя и его username, если он задан. Сверьте username со своим аккаунтом и запишите свой ID. Это ID пользователя, а не токен, телефон или username бота. Команда /id в этом режиме ничего не отправляет в OpenAI.

Если в выводе несколько пользователей, не выбирайте первый номер автоматически: найдите запись своего аккаунта. Для первого запуска не распространяйте адрес бота, пока не определили ID. Остановите режим определения через Ctrl+C. Этот режим подтверждает прочитанные обновления, но не отвечает на них: не запускайте его на действующем боте с очередью сообщений, которые нужно обработать.

Запустите ответы через OpenAI

Запустите обычный режим:

python bot.py

По очереди введите токен Telegram, свой числовой ID, ключ OpenAI API и точный ID выбранной модели. Например, актуальное руководство OpenAI использует gpt-6-astra; её наличие в примере не гарантирует доступность или подходящую вам цену. Пример запроса OpenAI.

Дождитесь надписи «Бот запущен». Отправьте в личный чат учебный запрос:

Подготовь короткое объявление для участников книжного обмена.
Встреча перенесена с субботы на воскресенье, начало в 15:00.
Место прежнее, но его адрес здесь не указан.
Попроси подтвердить участие. Не придумывай адрес и причину переноса.

Проверьте пять условий: воскресенье, 15:00, просьба подтвердить участие, отсутствие выдуманного адреса и причины переноса. Это критерии проверки, а не заранее полученный ответ модели. Пришедшее сообщение подтверждает связь с сервисами; содержание проверьте отдельно.

Затем отправьте /start: бот должен вернуть подсказку без обращения к OpenAI. Фото вместо текста тоже не вызывает генерацию — придёт просьба прислать текст. Сообщения от другого ID и сообщения в группе программа игнорирует ещё до вызова API.

Что умеет этот пример и где его пределы

Каждый вопрос отправляется отдельно. Фраза «сократи предыдущий ответ» не передаёт модели предыдущий ответ: вставьте его в новое сообщение вместе с заданием. Для диалога с историей потребуется отдельное хранение и передача контекста.

Скрипт обрабатывает сообщения последовательно и работает, пока запущен процесс. Закрытие терминала, остановка программы или сон компьютера прекращают обработку. Круглосуточная работа потребует постоянного запуска на вашей машине или сервере.

Длинный ответ делится на части по 2000 символов без Markdown-разметки. Это выбранный запас относительно ограничения sendMessage в 4096 символов. Параметры отправки сообщения.

Положение в очереди хранится только в памяти. После аварии или перезапуска последнее обновление может прийти повторно, в том числе привести к повторному платному вызову. Для постоянного использования сохраните update_id и состояние обработки на диск, продумайте восстановление после сбоев между генерацией и отправкой. Простого увеличения offset недостаточно для гарантии однократного выполнения при авариях.

Сообщение проходит через Telegram и OpenAI. Сам скрипт не записывает тексты в журнал, но это не означает, что данные нигде не хранятся у сервисов. Для первого опыта используйте учебные данные.

Если бот молчит или остановился

Наблюдение Следующий шаг
В терминале нет «Бот запущен» Завершите ввод всех параметров и прочитайте сообщение остановки
Режим --identify не показывает ID Отправьте именно /id своему боту в личном чате; проверьте токен и запущенный процесс
Бот принимает сообщения, но не отвечает вам Сверьте введённый числовой ID с результатом --identify; групповой чат здесь не поддерживается
Сообщение о webhook Используйте отдельного нового бота либо сначала разберите конфигурацию существующего обработчика
OpenAI: HTTP … Проверьте ключ, доступ выбранной модели и биллинг проекта; для расшифровки используйте официальный справочник ошибок
Telegram: HTTP … Проверьте токен, отсутствие второго обработчика и доступ к Bot API
Сбой сети или тайм-аут Сначала проверьте, пришёл ли ответ в Telegram и появился ли расход API; повтор может создать ещё один запрос
Нет текста или ответ не завершён Проверьте модель и результат отдельного запроса к Responses API; программа не выдаёт неполный ответ за успешный

При сетевой или API-ошибке программа останавливается, не повторяя платный запрос бесконечно. Для подробной диагностики OpenAI сверяйтесь со справочником ошибок API. Не публикуйте целиком URL Telegram-запроса: он содержит токен бота.

Проверить детали

Источники материала

Функции и условия сервисов меняются. Дата сверки указана в начале статьи. Как подготовлен материал

Есть следующая задача?Ещё по теме «Разработка и API» →