Как стать автором
Обновить
3130.16
RUVDS.com
VDS/VPS-хостинг. Скидка 15% по коду HABR15

Как я создавал Telegram-бота с помощью ChatGPT

Уровень сложностиПростой
Время на прочтение11 мин
Количество просмотров17K

Не так давно мне пришла мысль попробовать создать собственного Телеграм-бота (просто из любопытства). Никаких знаний в программировании у меня нет, поэтому первого бота я создал с помощью специального конструктора для Телеграм-ботов. Довольно удобная штука, но зачастую обладает ограниченным функционалом или требуют оформления подписки. И тут мне в голову пришла идея воспользоваться популярным ChatGPT и попробовать создать бота с нуля, во всём следуя инструкциям нейронки. Устанавливать дополнительный софт на своём основном ПК мне не очень хотелось, поэтому разместить бота я решил на VDS-сервере.

▍ Подготовка


Нет принципиальной разницы, какую операционную систему выбирать для размещения бота на сервере, но из-за личных предпочтений и удобства настройки я остановился на Windows Server. Благодаря тому, что в этой ОС есть графическая оболочка, а расположение сервера я выбрал за рубежом (в моём случае в Казахстане), я смог прямо с сервера, без какого-либо дополнительного софта пользоваться ChatGPT и напрямую копировать полученный код в файл бота. Конфигурации VDS сервера для небольшого бота хватило бы самой минимальной, но для возможности использовать версию Windows Server поновее, я взял конфиг: CPU 2, RAM 2, SSD 20.


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

Хочу написать Телеграм бота и разместить его на своем VDS сервере (ОС Windows). Я полный 0 в этом и не знаю, с чего начать. Опиши пошагово.

В ответ получил довольно подробную инструкцию, включающую себя следующие шаги:

  1. Регистрация бота в Telegram и получение токена с помощью BotFather.
  2. Настройка VDS сервера (установка Python и нужных библиотек).
  3. Создание папки и файла bot.py, в котором будет храниться код бота.


▍ Написание кода


Теперь начинается самое интересное — написание кода. Недолго думая, начать решил с кода для показа курса популярных валют (доллар, евро и юань). Отправил запрос:

Напиши мне код бота для Телеграм, который будет по запросу присылать курс валют: доллара, евро и юаня

Скопировал полученный код, вставил в файл bot.py и при запуске бота командой python3 bot.py ожидаемо получил ошибку:

«SyntaxError: unterminated f-string literal (detected at line 26) (, line 26)»

Так как написанием кода занимается ChatGPT, то и исправлением ошибок пусть занимается он. Копируем текст ошибки, отправляем его нейронке и получаем описание ошибки и исправление в коде.


Вновь пробуем запустить код и снова получаю ошибку. На этот раз текст ошибки следующий:

«Traceback (most recent call last): File „C:\telegram_bot\bot.py“, line 5, in from aiogram.utils import executor ImportError: cannot import name 'executor' from 'aiogram.utils' (C:\telegram_bot\venv\Lib\site-packages\aiogram\utils\__init__.py)»

Снова получаем описание ошибки и исправленный код. Сделав ещё несколько попыток (во время которых GPT указал на необходимость добавления токена из Telegram в код бота) наконец получилось запустить бота без ошибок. Теперь отправляемся в Телеграм, находим бота, запускаем его командой /start и, следуя инструкциям, пробуем получить курс валют командой /rate:


Отлично, всё работает. Однако постоянно пользоваться командой /rate не очень удобно, а в случае когда команда не одна, можно просто в них запутаться. Намного удобнее, если бы вместо ввода команд можно было бы просто кликнуть на кнопку. Отправляем новый запрос:

Бот работает, но постоянно отправлять команды не очень удобно. Хочу, чтобы у бота была кнопка, при нажатии на которую он присылал курс валют.

Как и в прошлый раз, полученный код не удалось запустить с первого раза и возникли новые ошибки, но после нескольких исправлений бот корректно запустился. Теперь при его запуске в Telegram появилось дополнительное меню, в котором отображается кнопка, при нажатии на которую получается тот же курс валют, что выводился при вводе команды /rate.


Другой разговор, с кнопкой намного удобнее. В целом, не без ошибок, но вполне ожидаемо, что ChatGPT справился с такой простой задачей. Но мне стало интересно, как он себя покажет при добавлении нового функционала (как схожего, так и отличающегося) и не сломает ли уже работающий код. Начать решил аккуратно и отправил следующий запрос:

Хочу, чтобы была ещё одна кнопка для вывода последних новостей. Пусть новости будут браться с ria.ru

Бот успешно запустился с обновлённым кодом, и в Telegram появилась новая кнопка «Последние новости», вот только при нажатии по ней бот выдаёт ошибку: «Ошибка при получении новостей. Попробуйте позже».


В консоли на сервере также появилась ошибка:

«ERROR:root: Ошибка получения новостей: No module named 'bs4' INFO:aiogram.event:Update id=644057290 is handled. Duration 316 ms by bot id=7290802386»

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


По этому же примеру, без особого труда удалось добавить ещё одну кнопку с выводом прогноза погоды в нужном городе. В отличии от новостей и курса валют, для вывода погоды боту потребовался API-ключ сервиса OpenWeatherMap, и GPT любезно предоставил инструкцию, как этот ключ получить:

  1. Перейди на сайт: home.openweathermap.org/users/sign_up
  2. Зарегистрируйся или войди в аккаунт.
  3. Перейди в раздел API keys (или API ключи).
  4. Там будет твой стандартный API-ключ (или создай новый, если нужно).
  5. Скопируй этот ключ и замени your_openweathermap_api_key в коде на свой API-ключ.

При нажатии на кнопку «Погода» бот запрашивает город. При вводе города выводится погода


Весь вышеописанный функционал бота работает по схожему принципу и при запросе без проблем выводит нужную информацию из определённых источников. Проблемы начались при добавлении нового функционала. В голову мне пришло добавить возможность создавать напоминания. Идея заключалась в том, чтобы добавить в уже существующее меню новую кнопку, при нажатии на которую будет открываться новое меню с новыми кнопками, отвечающими за управление напоминаниями. Для начала я отправил следующий запрос:

Хочу добавить ещё одну кнопку, при нажатии на которую будет открываться новое меню. В этом меню нужно сделать 2 кнопки. Одна — для создания напоминания: Сначала отправляем текст, а затем дату. Вторая — для показа всех напоминаний.

К моему удивлению, бот запустился с первого раза. Как я и хотел, в меню появилась новая кнопка «Напоминания».


При нажатии на кнопку успешно открылось новое меню с двумя новыми кнопками. При нажатии на кнопку «добавить напоминание» бот сначала запрашивает текст для напоминания, затем дату. И если текст напоминания бот принял без проблем, то как бы я ни пытался указать дату, он постоянно запрашивал её повторно:


Эту ошибку нейронке удалось исправить с первого раза, слегка изменив логику обработки сообщения с датой, и при повторном запуске напоминание успешно сохранилось.


При нажатии на кнопку «Показать напоминания» также успешно выводятся сохранённые напоминания.


В таком виде напоминания хранятся в памяти бота и автоматически удаляются при его выключении / перезагрузке. Чтобы этого не происходило, я попросил переписать код, чтобы все созданные напоминания хранились в отдельном файле.

Помимо создания напоминаний, необходима была возможность и удалять их. Также не очень удобно постоянно указывать дату напоминания вручную (особенно когда необходимо создать напоминание на сегодня / завтра). Поэтому я отправил следующий запрос:

Нужно добавить возможность удалять напоминания. Также нужно добавить возможность быстро оставить напоминание на сегодня / завтра с помощью специальных кнопок, появляющихся после ввода текста напоминания.

Потребовалось несколько раз исправлять код, но в итоге удалось получить тот результат, на который я рассчитывал. При вводе текста напоминания теперь появляется 4 кнопки, для добавления напоминания на сегодня / завтра, напоминания «без даты» и напоминания на любую другую дату.


▍ Возникшие проблемы


Казалось бы, всё, бот работает, напоминания создаются, что может пойти не так? А то, что пока я увлечённо занимался напоминаниями, исправляя возникающие с ними ошибки, я совсем забыл про тестирование уже ранее добавленного функционала (Курс валют, новости и прогноз погоды). Вернувшись к главному меню бота, я выяснил, что кроме кнопки «Напоминания», остальные кнопки просто не работают. Кроме того, в консоли на сервере также не было никаких ошибок. Я написал про это нейронке, и она начала вносить изменения в код, не сильно помогающие в исправлении проблем с нерабочими кнопками, но при этом ломающие оставшийся рабочий функционал. Сначала бот перестал показывать созданные напоминания, затем перестал создавать новые. В итоге перестали работать все кнопки, и бот превратился в тыкву. Устав биться с нейронкой, я решил вернуться к последней версии кода, где работали напоминания, и посмотреть, в чём же дело. Как выяснилось, ChatGPT, пока писал код для напоминаний, беспощадно удалил весь код с остальных кнопок, оставив только сами кнопки.

В итоге я решил немного изменить подход. Я запустил новый чат, чтобы сломанный код не влиял на ответ нейронки, взял две рабочие версии кода (версию до создания кнопки “напоминания” и версию с “напоминаниями”) и отправил следующий запрос:

У меня есть два кода. Первый код с рабочими кнопками «Курс валют», «Последние новости» и «Погода» (сам код целиком) и код с рабочими «Напоминаниями» (сам код целиком). Нужно объединить их в один рабочий код.

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

import asyncio
import requests
import logging
import json
from aiogram import Bot, Dispatcher, types
from aiogram.types import Message, ReplyKeyboardMarkup, KeyboardButton
from bs4 import BeautifulSoup
from datetime import datetime, timedelta
 
# Токен бота
TOKEN = "Ваш Telegram токен"
WEATHER_API_KEY = "Ваш API ключ"
REMINDERS_FILE = "reminders.json"
 
logging.basicConfig(level=logging.INFO)
 
bot = Bot(token=TOKEN)
dp = Dispatcher()
 
reminders = {}
pending_reminders = {}
waiting_for_city = {}
 
def load_reminders():
    global reminders
    try:
        with open(REMINDERS_FILE, "r", encoding="utf-8") as file:
            reminders = json.load(file)
    except (FileNotFoundError, json.JSONDecodeError):
        reminders = {}
 
def save_reminders():
    with open(REMINDERS_FILE, "w", encoding="utf-8") as file:
        json.dump(reminders, file, ensure_ascii=False, indent=4)
 
async def set_reminder(user_id, text, date=None):
    if user_id not in reminders:
        reminders[user_id] = []
    reminders[user_id].append((text, date))
    save_reminders()
    return "Напоминание сохранено!"
 
async def get_reminders(user_id):
    if user_id in reminders and reminders[user_id]:
        return "\n".join([f"{idx + 1}. 📅 {r[1] if r[1] else 'Без даты'} - {r[0]}" for idx, r in enumerate(reminders[user_id])]).encode("utf-8", "ignore").decode("utf-8")
    return "У вас нет напоминаний."
 
async def delete_reminder(user_id, index):
    if user_id in reminders and 0 <= index < len(reminders[user_id]):
        del reminders[user_id][index]
        save_reminders()
        return "✅ Напоминание удалено!"
    return "❌ Неверный номер напоминания."
 
def get_currency_rates():
    url = "https://www.cbr-xml-daily.ru/daily_json.js"
    try:
        response = requests.get(url)
        data = response.json()
        usd, eur, cny = data["Valute"]["USD"]["Value"], data["Valute"]["EUR"]["Value"], data["Valute"]["CNY"]["Value"]
        return f"Курс валют:\n💵 Доллар: {usd:.2f} ₽\n💶 Евро: {eur:.2f} ₽\n🇨🇳 Юань: {cny:.2f} ₽"
    except Exception as e:
        logging.error(f"Ошибка получения курса валют: {e}")
        return "Ошибка при получении курса валют. Попробуйте позже."
 
def get_latest_news():
    url = "https://ria.ru/"
    try:
        response = requests.get(url, headers={"User-Agent": "Mozilla/5.0"})
        soup = BeautifulSoup(response.text, "html.parser")
        headlines = soup.find_all("a", class_="list-item__title") or soup.find_all("a", class_="cell-list__item-link")
        news = "\n".join([f"🔹 {h.text.strip()}" for h in headlines[:5]])
        return f"Последние новости:\n{news}" if news else "Не удалось получить новости."
    except Exception as e:
        logging.error(f"Ошибка получения новостей: {e}")
        return "Ошибка при получении новостей. Попробуйте позже."
 
def get_weather(city):
    url = f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={WEATHER_API_KEY}&units=metric&lang=ru"
    try:
        response = requests.get(url)
        data = response.json()
        if "main" in data:
            temp, description = data["main"]["temp"], data["weather"][0]["description"].capitalize()
            return f"🌤 Погода в {city}: {temp}°C, {description}"
        return "Не удалось получить погоду. Проверьте название города."
    except Exception as e:
        logging.error(f"Ошибка получения погоды: {e}")
        return "Ошибка при получении погоды. Попробуйте позже."
 
keyboard = ReplyKeyboardMarkup(
    keyboard=[
        [KeyboardButton(text="💰 Курс валют"), KeyboardButton(text="📰 Последние новости")],
        [KeyboardButton(text="🌤 Погода"), KeyboardButton(text="⏰ Напоминания")]
    ],
    resize_keyboard=True
)
 
 
reminder_keyboard = ReplyKeyboardMarkup(
    keyboard=[
        [KeyboardButton(text="➕ Добавить напоминание"), KeyboardButton(text="📋 Показать напоминания")],
        [KeyboardButton(text="🗑 Удалить напоминание"), KeyboardButton(text="🔙 Назад")]
    ],
    resize_keyboard=True
)
 
 
quick_date_keyboard = ReplyKeyboardMarkup(
    keyboard=[
        [KeyboardButton(text="📅 Сегодня"), KeyboardButton(text="📅 Завтра")],
        [KeyboardButton(text="📌 Без даты"), KeyboardButton(text="✏ Ввести дату вручную")]
    ],
    resize_keyboard=True
)
 
 
@dp.message()
async def handle_message(message: Message):
    user_id = str(message.from_user.id)
    text = message.text
 
    if user_id in waiting_for_city:
        weather_info = get_weather(text)
        del waiting_for_city[user_id]
        await message.answer(weather_info)
        return
 
    if text == "/start":
        await message.answer("Привет! Выбери действие ниже:", reply_markup=keyboard)
    elif text == "💰 Курс валют":
        await message.answer(get_currency_rates())
    elif text == "📰 Последние новости":
        await message.answer(get_latest_news())
    elif text == "🌤 Погода":
        waiting_for_city[user_id] = True
        await message.answer("Введите название города:")
    elif text == "⏰ Напоминания":
        await message.answer("Выберите действие:", reply_markup=reminder_keyboard)
    elif text == "➕ Добавить напоминание":
        pending_reminders[user_id] = {"stage": "text"}
        await message.answer("Введите текст напоминания:")
    elif text == "📋 Показать напоминания":
        reminders_text = await get_reminders(user_id)
        await message.answer(reminders_text)
    elif text == "🗑 Удалить напоминание":
        reminders_text = await get_reminders(user_id)
        if "нет напоминаний" in reminders_text.lower():
            await message.answer(reminders_text)
        else:
            await message.answer(f"Ваши напоминания:\n{reminders_text}\n\nВведите номер напоминания для удаления:")
            pending_reminders[user_id] = {"stage": "delete"}
    elif text == "🔙 Назад":
        await message.answer("Главное меню:", reply_markup=keyboard)
    elif user_id in pending_reminders:
        stage = pending_reminders[user_id].get("stage")
 
        if stage == "text":
            pending_reminders[user_id]["text"] = text
            pending_reminders[user_id]["stage"] = "date_choice"
            await message.answer("Выберите дату:", reply_markup=quick_date_keyboard)
        elif stage == "date_choice":
            date = None
            if text == "📅 Сегодня":
                date = datetime.now().strftime("%d.%m.%Y")
            elif text == "📅 Завтра":
                date = (datetime.now() + timedelta(days=1)).strftime("%d.%m.%Y")
            elif text == "✏ Ввести дату вручную":
                pending_reminders[user_id]["stage"] = "date"
                await message.answer("Введите дату в формате ДД.ММ.ГГГГ ЧЧ:ММ:")
                return
            await set_reminder(user_id, pending_reminders[user_id]["text"], date)
            del pending_reminders[user_id]
            await message.answer("✅ Напоминание сохранено!", reply_markup=reminder_keyboard)
 
async def main():
    load_reminders()
    await dp.start_polling(bot)
 
if __name__ == "__main__":
    asyncio.run(main())


▍ Вывод


В целом, я остался доволен получившимся результатом. Да, при написании кода не один раз возникали ошибки, но ChatGPT по большей части с ними справлялся и давал корректный код. Увы, он не осилил добавление функционала напоминаний к уже имеющемуся коду и заставил изрядно попотеть, чтобы разобраться в причине, но при написании кода с использованием нескольких чатов (один чат для одного функционала, второй чат для другого) подобных проблем возникать не должно.

© 2025 ООО «МТ ФИНАНС»

Telegram-канал со скидками, розыгрышами призов и новостями IT 💻
Теги:
Хабы:
Всего голосов 34: ↑31 и ↓3+51
Комментарии17

Публикации

Информация

Сайт
ruvds.com
Дата регистрации
Дата основания
Численность
11–30 человек
Местоположение
Россия
Представитель
ruvds