Обучение #2
Язык программирования - Далее ЯП
Операционная система - Далее ОС
Редактор кода - Далее IDE
CMD - Далее консоль
Первый урок - *Кликабельно*
В этом уроке мы научимся:
Напишем telegram бота с логикой анкетирования
- Сделаем 2 кнопки (Заполнить анкету\Информация)
- Сделаем текстовое меню
- Сделаем выбор пола через текстовое меню
Начало
Для начала давайте создадим новый проект, создаём папку "bot_ankets"
В папке создадим 4 файла:
1. main.py - в него будем писать основной код бота
2. config.py - в него запишем токен бота, id админа, данные для подключения к базе данных
3. class_db.py - в него напишем вспомогательный класс для работы с базой данных
4. test.py - в нём я записываю все клавиатуры, если такое название вам не нравиться, вы можете назвать файл "keyboards.py", от названия ничего не зависит
Давайте заполним файл config.py:
token = 'Токен телеграм бота' admin = 132 #ID админа (позже научу его получать) host = 'localhost' #IP для подключения к базе данных (Ничего не менять, это локальный хостинг) port = 8080 #Порт для подключения к базе данных (Ничего не меняем, мы его меняли при создании из первого урока) user = 'root' #Пользователь для подключения к базе данных (Стандартный - root) password = 'Password' #Пароль для подключения к базе данных (Замените на свой который вписывали на первом уроке) database = 'test_basa' #Имя базы данных которую мы создали в первом уроке
И так мы заполнили конфиг, нажимаем сочетание клавиш ctrl+s для сохранения изменений
Далее давайте напишем вспомогательный класс для работы с базой данных в файл class_db.py:
import pymysql #Импортируем модуль pymysql который установили в первом уроке from config import host, port, user, password, database #Импортируем из файла config.py данные для подключения к базе данных class work_db: def __init__(self): #Функция для иницилизирования переменных в класс, запускать её не надо self.host = host self.port = port self.user = user self.password = password self.database = database def connect_db(self): #Функция для конекта к базе данных try: connection = pymysql.connect( host = self.host, port = self.port, user = self.user, password = self.password, database = self.database, cursorclass = pymysql.cursors.DictCursor ) return connection #Если конект прошёл успешно, возвращаем переменную connection, для дальнейшей работы except Exception as eror: print(eror) #Если произошла ошибка, выводим её в консоль return False #Если произошла ошибка, возвращаем False def connect_close(self, connection): #Передаём переменную connection если в коде подключились, после каждого подключения к базе данных и выполнения какого-либо когда, нужно закрывать подключение try: connection.close() #Закрываем подключение к базе данных return True #Если успешно закрыли подключение к базе, возвращаем True except Exception as eror: print(eror) #Если произошла ошибка, выводим её в консоль return False #Если произошла ошибка, возвращаем False def create_table(self, connection, creat): #Функция для создания таблице в базе данных, в creat будем передавать текст запроса try: with connection.cursor() as cursor: cursor.execute(creat) connection.commit() return True except Exception as eror: print(eror) #Если произошла ошибка, выводим её в консоль return False #Если произошла ошибка, возвращаем False def edit_table(self, connection, edit): #Функция для редактирования данных в таблице, в edit будем передавать текст запроса try: with connection.cursor() as cursor: cursor.execute(edit) connection.commit() return True except Exception as eror: print(eror) #Если произошла ошибка, выводим её в консоль return False #Если произошла ошибка, возвращаем False def info_table(self, connection, info): #Функция для получения данных из таблицы, в info будем передавать текст запроса try: with connection.cursor() as cursor: cursor.execute(info) return cursor.fetchall()[0] #Возвращаем всё что нашли except Exception as eror: print(eror) #Если произошла ошибка, выводим её в консоль return False #Если произошла ошибка, возвращаем False def delete_from_table(self, connection, delete_): #Функция для редактирования данных в таблице, в delete_ будем передавать текст запроса try: with connection.cursor() as cursor: cursor.execute(delete_) connection.commit() return True except Exception as eror: print(eror) #Если произошла ошибка, выводим её в консоль return False #Если произошла ошибка, возвращаем False
И так класс для работы с базой данных написан, опять же сохраняем данные (нажимаем сочетание клавиш ctrl+s)
Давайте заполним файл test.py записав в него клавиатуру:
from aiogram.types import KeyboardButton, ReplyKeyboardMarkup #Импортируем классы для работы с текстовым меню\текстовыми кнопками mainy = [['Заполнить анкету'], ['Информация']] #Создайм список кнопок для текстового меню в данном примере кнопки расположены в 2 строки #Можно использовать такой список: [['Заполнить анкету', 'Информация']] Кнопки будут расположены в 1 строку mark_menu = ReplyKeyboardMarkup(mainy, resize_keyboard=True) #Создаём клавиатуру, передав список представленный выше #resize_keyboard=True нужен для того что-бы кнопки были маленькие, вы можете его на вписывать и посмотреть как это будет выглядеть
И так клавиатура создана, приступим к основному коду telegram бота, писать будем в файле main.py:
from aiogram.dispatcher.filters.state import State, StatesGroup from aiogram import Bot, Dispatcher, executor, types from aiogram.dispatcher import FSMContext from aiogram.types import ReplyKeyboardRemove from class_db import work_db #Импорируем класс для работы с базой данных из файла class_db.py import test as bb #Импортируем файл test.py в переменную bb (мне так удобнее работать с ним) import config as c #Импортируем файл config.py в переменную с (опять же мне так удобнее)
Создадим нужные нам экземпляры классов для работы:
bot = Bot(token=c.token) #Берём токен бота из файла config.py который импортировали ранее в переменную c dp = Dispatcher(bot) db_work = work_db(c.host, c.port, c.user, c.password, c.database) #Создаём экземпляр класса для работы с базой данных, передав все нужные параметры из конфига
Пишем обработчик текстовых сообщений:
@dp.message_handler(content_types=['text']) #Обрабатываем каждое сообщение от пользователей, добавляем content_types для обработки текста, в будущих уроках будет фото и видео async def text(message: types.Message): #Асинхронная функция с название "text" которая принимает в переменную message, текст пользователя отправевшего сообщение pass
Давайте напишем логику обработки сообщений, начнём с кнопки "информация" и команду /start:
@dp.message_handler(content_types=['text']) #Обрабатываем каждое сообщение от пользователей, добавляем content_types для обработки текста, в будущих уроках будет фото и видео async def text(message: types.Message): #Асинхронная функция с название "text" которая принимает в переменную message, текст пользователя отправевшего сообщение if message.text == '/start': await bot.send_message(message.chat.id, 'Привет, заполни анкету, или посмотри информацйию о мне', reply_markup=bb.mark_menu) #Обрабатываем команду /start и выводим клавиатуру из файлы test.py elif message.text == 'Информация': await bot.send_message(message.chat.id, f'Сюда пишем какую либо информацию, например я хочу вывести ID пользователя\nТвой ID: {message.chat.id}')
А теперь давайте напишем логику самого анкетирования
Перед началом объясню как работает if, elif, else
Допустим нам нужно проверить какое число пришло в функцию
И в зависимости от числа вернуть какой то текст
Для этого мы пишем саму функцию и запуск её с каким либо числом:
def info_number(number): #Данная функция принимает число в переменную number if number == 1: #Если полученное число равно одному, то возращаем слово Один return 'Один' elif number == 2: #Если же число равно двум, то возращаем слово Два return 'Два' else: #В ином случае говорим что такие числа мы не принимаем return 'Я не принимаю такие числа'
Ещё нам понадобиться такая вещь как машина состояний, мы её уже импортировали в начале
Давайте расскажу для чего нам машина состояний...
Она нам нужна для того что-бы работать с определённым пользователем, и определённой логикой
Это всё что нам требуется на этом этапе да и в принципе в программировании
Продолжим писать логику
Создаём класс для работы с машиной состояний:
class anketa(StatesGroup): name = State() floor = State() age = State()
Далее дописываем ожидание именно имени, у нас получился код:
@dp.message_handler(content_types=['text']) #Обрабатываем каждое сообщение от пользователей, добавляем content_types для обработки текста, в будущих уроках будет фото и видео async def text(message: types.Message): #Асинхронная функция с название "text" которая принимает в переменную message, текст пользователя отправевшего сообщение if message.text == '/start': await bot.send_message(message.chat.id, 'Привет, заполни анкету, или посмотри информацйию о мне', reply_markup=bb.mark_menu) #Обрабатываем текст /start и выводим клавиатуру из файлы test.py elif message.text == 'Информация': await bot.send_message(message.chat.id, f'Сюда пишем какую либо информацию, например я хочу вывести ID пользователя\nТвой ID: {message.chat.id}') elif message.text == 'Заполнить анкету': await bot.send_message(message.chat.id, 'Введите своё имя:') await anketa.name.set()
В файле test.py добавляем клавиатуру с выбором пола:
main_floor = [['Мужской', 'Женский']] menu_floor = ReplyKeyboardMarkup(main_floor, resize_keyboard=True)
Далее нам нужен сам обработчик ввода пользователя:
@dp.message_handler(state=anketa.name) async def anketa_name(message: types.Message, state: FSMContext): #Как видите у нас добавился FSMContext, он нам нужен как раз для обработки машины состояния #Записываем имя в кеш await state.update_data(name=name) #Далее просим ввести пол, с выбором пола из кнопок await bot.send_message(message.chat.id, 'Выберите свой пол:', reply_markup=bb.main_floor) #добавляем вывод кнопок с выбором пола
@dp.message_handler(state=anketa.floor) async def anketa_name(message: types.Message, state: FSMContext): #Записываем пол в кеш await state.update_data(floor=floor) #Далее просим ввести свой возраст await bot.send_message(message.chat.id, 'Выберите свой пол:', reply_markup=ReplyKeyboardRemove()) #убираем текстовую клавиатуру await anketa.age.set()
И наконец давайте обработаем возраст, и выведем данные которые ввёл пользователь, сначала пользователю, а затем админу по его ID из config.py
@dp.message_handler(state=anketa.age) async def anketa_name(message: types.Message, state: FSMContext): #Получим все данные записанные в кеш data = await state.get_data() #Сортируем их по переменным name = data['name'] #В ковычках пишем name, потому что сами записывали в такую переменную, там может быть любая другая floor = data['floor'] #В ковычках пишем floor, потому что сами записывали в такую переменную, там может быть любая другая age = message.text #записываем ввод пользователя в переменную age await state.finish() #Завершаем работу с машиной состояний #Выведем анкету пользователю await bot.send_message(message.chat.id, f'Ваша анкета:\n\nИмя: {name}\nПол: {floor}\nВозраст: {age}\n\nСпасибо за уделение времени', reply_markup=bb.mark_menu) #Отправляем анкету, благодарим, и выдаём ему текстовоем меню #Отправим анкету администратору по его ID из config.py #Так же выведем ID пользователя и его юзернейм await bot.send_message(c.admin, f'Пользователь: {message.chat.id}\n@{message.from_user.username} заполнил анкету\nЕго данные:\n\nИмя: {name}\nПол: {floor}\nВозраст: {age}') if __name__ == "__main__": #Если скрипт запущен с этого файла, запускаем executor executor.start_polling(dp, skip_updates=True) #skip_updates=True - нужен для того чтобы не обрабатывать сообщения которые были присланы пользователем в тот момент когда бот был выключен
Возможно у вас возникнет вопрос зачем я использую "\n" это для того что-бы перенести текст на 1 или более строк.
В итоге у нас получился такой код:
from aiogram.dispatcher.filters.state import State, StatesGroup from aiogram import Bot, Dispatcher, executor, types from aiogram.dispatcher import FSMContext from aiogram.types import ReplyKeyboardRemove from class_db import work_db #Импорируем класс для работы с базой данных из файла class_db.py import test as bb #Импортируем файл test.py в переменную bb (мне так удобнее работать с ним) import config as c #Импортируем файл config.py в переменную с (опять же мне так удобнее) #Создаём класс бота и дистпетчера bot = Bot(token=c.token) #Берём токен бота из файла config.py который импортировали ранее в переменную c dp = Dispatcher(bot) db_work = work_db(c.host, c.port, c.user, c.password, c.database) #Создаём экземпляр класса для работы с базой данных, передав все нужные параметры из конфига class anketa(StatesGroup): name = State() floor = State() age = State() @dp.message_handler(content_types=['text']) #Обрабатываем каждое сообщение от пользователей, добавляем content_types для обработки текста, в будущих уроках будет фото и видео async def text(message: types.Message): #Асинхронная функция с название "text" которая принимает в переменную message, текст пользователя отправевшего сообщение if message.text == '/start': await bot.send_message(message.chat.id, 'Привет, заполни анкету, или посмотри информацйию о мне', reply_markup=bb.mark_menu) #Обрабатываем команду /start и выводим клавиатуру из файлы test.py elif message.text == 'Информация': await bot.send_message(message.chat.id, f'Сюда пишем какую либо информацию, например я хочу вывести ID пользователя\nТвой ID: {message.chat.id}') elif message.text == 'Заполнить анкету': await bot.send_message(message.chat.id, 'Введите своё имя:') await anketa.name.set() @dp.message_handler(state=anketa.name) async def anketa_name(message: types.Message, state: FSMContext): #Как видите у нас добавился FSMContext, он нам нужен как раз для обработки машины состояния #Записываем имя в кеш await state.update_data(name=message.text) #Далее просим ввести пол, с выбором пола из кнопок await bot.send_message(message.chat.id, 'Выберите свой пол:', reply_markup=bb.main_floor) #добавляем вывод кнопок с выбором пола await anketa.floor.set() @dp.message_handler(state=anketa.floor) async def anketa_name(message: types.Message, state: FSMContext): #Записываем пол в кеш await state.update_data(floor=message.text) #Далее просим ввести свой возраст await bot.send_message(message.chat.id, 'Выберите свой пол:', reply_markup=ReplyKeyboardRemove()) #убираем текстовую клавиатуру await anketa.age.set() @dp.message_handler(state=anketa.age) async def anketa_name(message: types.Message, state: FSMContext): #Получим все данные записанные в кеш data = await state.get_data() #Сортируем их по переменным name = data['name'] #В ковычках пишем name, потому что сами записывали в такую переменную, там может быть любая другая floor = data['floor'] #В ковычках пишем floor, потому что сами записывали в такую переменную, там может быть любая другая age = message.text #записываем ввод пользователя в переменную age await state.finish() #Завершаем работу с машиной состояний #Выведем анкету пользователю await bot.send_message(message.chat.id, f'Ваша анкета:\n\nИмя: {name}\nПол: {floor}\nВозраст: {age}\n\nСпасибо за уделение времени', reply_markup=bb.mark_menu) #Отправляем анкету, благодарим, и выдаём ему текстовоем меню #Отправим анкету администратору по его ID из config.py #Так же выведем ID пользователя и его юзернейм await bot.send_message(c.admin, f'Пользователь: {message.chat.id}\n@{message.from_user.username} заполнил анкету\nЕго данные:\n\nИмя: {name}\nПол: {floor}\nВозраст: {age}') if __name__ == "__main__": #Если скрипт запущен с этого файла, запускаем executor executor.start_polling(dp, skip_updates=True) #skip_updates=True - нужен для того чтобы не обрабатывать сообщения которые были присланы пользователем в тот момент когда бот был выключен
Работу с базой данных я перенёс на третий урок, потому что информация для некоторых новая, и её достаточно много для новичка, поэтому ожидайте третий урок
В третьей части мы научимся работать с:
Так же советую посмотреть мою статью по форматированию сообщений - *КЛИКАБЕЛЬНО*
Но для них нам придётся переписать строку
bot = Bot(token=c.token)
bot = Bot(token=c.token, parse_mode=types.ParseMode.HTML)
Как видим у нас добавился parse_mode, это как раз для обработки HTML тегов в сообщениях...
Домашнее задание:
1. Выдели сообщения (запрос имени, пола, возраста) в жирный шрифт
(по желанию можешь добавить наклонный текст)
2. Так же выдели в итоговой анкете, ID пользователя (само число)
в Текст для лёгкого копирования (Моноширинный)
(и у пользователя и у админа)
В обратной связи жду:
1. Скриншот кода где видно, как реализовано выделение текста
2. Скриншот с telegram бота, где видно заполнение анкеты + выделения текстов
3. Скриншот с telegram бота где видна конечная анкета (у пользователя и админа)
Прислать можно: @Xacker_Name_new
Спасибо за внимание
С вами был @Xacker_Name_new
Поддержка: @Bsc_Black_Secret_Club_bot