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

от автора


Не так давно мне пришла мысль попробовать создать собственного Телеграм-бота (просто из любопытства). Никаких знаний в программировании у меня нет, поэтому первого бота я создал с помощью специального конструктора для Телеграм-ботов. Довольно удобная штука, но зачастую обладает ограниченным функционалом или требуют оформления подписки. И тут мне в голову пришла идея воспользоваться популярным 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 💻


ссылка на оригинал статьи https://habr.com/ru/articles/887236/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *