Создаём Telegram-бота на Python + Aiogram

Создаём Telegram-бота на Python + Aiogram

LOLZTEAM

Создаем бота для Telegram!

Статья носит образовательный характер, мы ни к чему не призываем и не обязываем. Информация представлена исключительно в ознакомительных целях.

Больше интересных статей на нашем форуме: https://lolz.guru/articles/

Подписывайтесь на канал и делитесь ссылкой на статью с друзьями!

Подготовка:

  • Создаём необходимые файлы
  • Импорт модулей и компонентов

Разработка бота:

  • Первая команда, альтернативная замена БД и обычные кнопки
  • Оживляем обычные кнопки
  • Вывод статистики (Inline-кнопки, callback_data)
  • Рассылка пользователям бота (+ отправка фотографии)
  • FSM

Подводим итог:

  • Чему научились?

Создаём папку с названием нашего Бота, в ней три файла: main.py , keyboard.py , config.py. Открываем эти файлы в любом текстовом редакторе ( я использую Sublime, ибо удобно переключаться между файлами + дизайн ).

Устанавливаем библиотеку с помощью pip в консоли

# -*- coding: utf8 -*-
################################################################################################################################from aiogram import Bot, types
from aiogram.utils import executor
from aiogram.dispatcher import Dispatcher
from aiogram.types import ReplyKeyboardRemove, ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardMarkup, InlineKeyboardButton
import asyncio
#################################################################################################################################

######################################################################
from aiogram.dispatcher import FSMContext
from aiogram.dispatcher.filters import Command 
from aiogram.contrib.fsm_storage.memory import MemoryStorage  
from aiogram.dispatcher.filters.state import StatesGroup, State 
######################################################################

######################
import config       ## ИМПОРТИРУЕМ ДАННЫЕ ИЗ ФАЙЛОВ config.py
import keyboard       ## ИМПОРТИРУЕМ ДАННЫЕ ИЗ ФАЙЛОВ keyboard.py
######################

import logging # ПРОСТО ВЫВОДИТ В КОНСОЛЬ ИНФОРМАЦИЮ, КОГДА БОТ ЗАПУСТИТСЯ

Подключаем токен нашего Бота:

storage = MemoryStorage() # FOR FSM
bot = Bot(token=config.botkey, parse_mode=types.ParseMode.HTML)
dp = Dispatcher(bot, storage=storage)

logging.basicConfig(format=u'%(filename)s [LINE:%(lineno)d] #%(levelname)-8s [%(asctime)s] %(message)s',
                   level=logging.INFO,
                   )

Попутно с этим заходим в config.py и прописываем наш ключ с BotFather:

botkey = '1770592647:AAHrIpW5XW6jYKmB56Kg63r_2LcCK8gOKtg' # ТОКЕН С BOTFATHER

Перед тем как начать с команды старт, надо добавить кнопки, которые будут появляться при вводе команды. Открываем keyboard.py и пишем такие строки, в начале импортируя библиотеку.

from aiogram import Bot, types
from aiogram.types import ReplyKeyboardRemove, ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardMarkup, InlineKeyboardButton

######################################################
start = types.ReplyKeyboardMarkup(resize_keyboard=True) # СОЗДАЕМ ВООБЩЕ ОСНОВУ ДЛЯ КНОПОК

info = types.KeyboardButton("Информация")           # ДОБАВЛЯЕМ КНОПКУ ИНФОРМАЦИИ
stats = types.KeyboardButton("Статистика")           # ДОБАВЛЯЕМ КНОПКУ СТАТИСТИКИ

start.add(stats, info) #ДОБАВЛЯЕМ ИХ В БОТА


Вернулись в main.py .

Мы создаём бота, который может собирать статистику и делать рассылку. Значит, для начала, надо научить его записывать id пользователя, который отправил команду /start.

  • Чтобы не возиться с БД ( На подобии Sql3 ), я сделаю его альтернативу через обычный txt - файл.

Для этого создаём текстовик user.txt в папке с ботом.

Начнём с команды /start

@dp.message_handler(Command("start"), state=None)

async def welcome(message):
   joinedFile = open("user.txt","r")
   joinedUsers = set ()
   for line in joinedFile:
       joinedUsers.add(line.strip())

   if not str(message.chat.id) in joinedUsers:
       joinedFile = open("user.txt","a")
       joinedFile.write(str(message.chat.id)+ "\n")
       joinedUsers.add(message.chat.id)

   await bot.send_message(message.chat.id, f"ПРИВЕТ, *{message.from_user.first_name},* БОТ РАБОТАЕТ", reply_markup=keyboard.start, parse_mode='Markdown')

Что мы сделали?

Открыли файл для чтения. Поставили условие, если в файле нет пользователя с таким id, то записываем его. Если есть - не трогаем. Ну и естественно присылаем ответ на команду пользователя.

Чтобы бот работал без остановок после выполнения команд, прописываем в самом конце main.py

##############################################################
if __name__ == '__main__':
   print('Монстр пчелы запущен!')  # ЧТОБЫ БОТ РАБОТАЛ ВСЕГДА с выводом в начале вашего любого текста  
executor.start_polling(dp)
##############################################################

Запускаем бота, через файл main.py.

Можно нажать два раза на файл, либо же открыть cmd и прописать
cd путь к папке с ботом
python main.py

Если у вас так-же - Вы всё сделали правильно, можно идти к самому боту.

Отправляем команду /start:

Всё работает. Смотрим в файлик user.txt. Наш ID записался. Кнопки не нажимаются, потому что в них ничего не добавили. Если нажмете на кнопку - выдаст ошибку в консоли ( кнопка никуда не ведёт ), но бот будет работать.

Попробуем оживить кнопку "Информация". Простенькие строчки, чтобы бот ответил на кнопку.​

@dp.message_handler(content_types=['text'])
async def get_message(message):
   if message.text == "Информация":
       await bot.send_message(message.chat.id, text = "Информация\nБот создан специально для моих любимых девочек и мальчиков с lzt ", parse_mode='Markdown')

Отлично. Кнопку оживили, но запускать пока что не будем. Добавим просмотр статистики, при помощи Inline-кнопки, callback_data, да еще и админку прикрутим.

Заходим в keyboard.py и пишем следующее:

stats = InlineKeyboardMarkup()   # СОЗДАЁМ ОСНОВУ ДЛЯ ИНЛАЙН КНОПКИ
stats.add(InlineKeyboardButton(f'Да', callback_data = 'join')) # СОЗДАЁМ КНОПКУ И КАЛБЭК К НЕЙ
stats.add(InlineKeyboardButton(f'Нет', callback_data = 'cancle')) # СОЗДАЁМ КНОПКУ И КАЛБЭК К НЕЙ

Так же, как мы делали обычные кнопки, но оформлено по эстетики Inline-кнопки.

Теперь заходим в config.py. Пишем наш ID для админки. Взять его можно с нашего файла user.txt.

botkey = '1770592647:AAHrIpW5XW6jYKmB56Kg63r_2LcCK8gOKtg' # ТОКЕН С BOTFATHER
admin = 1212341234

Возвращаемся в main.py и дополняем код.

Оживляем кнопку статистика.

@dp.message_handler(content_types=['text'])
async def get_message(message):
   if message.text == "Информация":
       await bot.send_message(message.chat.id, text = "Информация\nБот создан специально для моих любимых девочек и мальчиков с lzt ", parse_mode='Markdown')


   if message.text == "Статистика":
       await bot.send_message(message.chat.id, text = "Хочешь просмотреть статистику бота?", reply_markup=keyboard.stats, parse_mode='Markdown')

Далее прописываем "Ловлю callback" и что должно при этом выполняться. Делаем фичу, чтобы при нажатии на "да", Бот присылал не новое сообщение, а редактировал старое (выглядит более эстетично), выводя при этом количество пользователей бота.

Нам надо посчитать все строчки ( ID ) с текстовика user.txt и вывести их количество.

Всё это выглядит вот так:

@dp.callback_query_handler(text_contains='join') # МЫ ПРОПИСЫВАЛИ В КНОПКАХ КАЛЛБЭК "JOIN" ЗНАЧИТ И ТУТ МЫ ЛОВИМ "JOIN"
async def join(call: types.CallbackQuery):
   if call.message.chat.id == config.admin:
       d = sum(1 for line in open('user.txt'))
       await bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, text=f'Вот статистика бота: *{d}* человек', parse_mode='Markdown')
   else:
       await bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, text = "У тебя нет админки\n Куда ты полез", parse_mode='Markdown')



@dp.callback_query_handler(text_contains='cancle') # МЫ ПРОПИСЫВАЛИ В КНОПКАХ КАЛЛБЭК "cancle" ЗНАЧИТ И ТУТ МЫ ЛОВИМ "cancle"
async def cancle(call: types.CallbackQuery):
   await bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, text= "Ты вернулся В главное меню. Жми опять кнопки", parse_mode='Markdown')

Пора запускать бота. Жмем "Статистика".

Жмём "Да".

Эти же действия, но без админки.

Нажали "Информация".

Делаем рассылку пользователям. Решил сделать так, чтобы с текстом отправлялась еще и фотка. Скидываем фотку в нашу папку с ботом. Переименовываем её во что-то красивое (у меня это lzt.png)

@dp.message_handler(commands=['rassilka'])
async def rassilka(message):
   if message.chat.id == config.admin:
       await bot.send_message(message.chat.id, f"*Рассылка началась \nБот оповестит когда рассылку закончит*", parse_mode='Markdown')
       receive_users, block_users = 0, 0
       joinedFile = open ("user.txt", "r")
       jionedUsers = set ()
       for line in joinedFile:
           jionedUsers.add(line.strip())
       joinedFile.close()
       for user in jionedUsers:
           try:
               await bot.send_photo(user, open('lzt.jpg', 'rb'), message.text[message.text.find(' '):])
               receive_users += 1
           except:
               block_users += 1
           await asyncio.sleep(0.4)
       await bot.send_message(message.chat.id, f"*Рассылка была завершена *\n"
                                                             f"получили сообщение: *{receive_users}*\n"
                                                             f"заблокировали бота: *{block_users}*", parse_mode='Markdown')

Вставляем этот код сразу же после хэндлера со стартом. Проверяйте какого фотка типа. У меня это jpg, поэтому я и пишу в коде .jpg

Коротко про код :

Команду для рассылки поставили /rassilka. Сделали так, чтобы бот рассылал текст, который написан через пробел к команде. Вот так: /rassilka текст рассылки
Опять же, выполняться будет только если ты админ. Читаем ID с текстовика и рассылаем по ним сообщения с фоткой. asyncio.sleep(0.4) - это задержка перед отправкой сообщения новому пользователю ( Чтобы не получить ограничение со стороны Telegram ). Записываем сколько пользователей получили рассылку и сколько пользователей заблокировали бота. Когда рассылка завершится, выводить статистику рассылки.

Запускаем бота и проверяем.

Смотрим на результат:

Отлично.

Сделаем такую штучку. Для админов, при вводе команды /me, бот просит отправить ссылку на профиль lolz и ждёт ответа. Мы отправляем ему свой профиль. Он его записывает в БД ( Но т.к мы хотим сделать всё по простому, я опять возьму текстовик вместо БД ). Далее он просит указать нам текст, который так же записывает в нашу "БД" ( Дальше будет понятно какой текст ). Добавим кнопку СОЗДАТЕЛЬ. Нажав на неё бот нам отправит ссылку на профиль lolz и текст, который мы указали через /me.

Приступим.

Создаём наши "БД" два файла в папку с ботом: link.txt и text.txt в них будут храниться наши ответы.

Переходим в main.py Я поместил код для этого выше старта, ибо всё, что связанно с FMS и State лучше писать в начале, дабы посреди основного кода он не отвлекал.

Прописываем State-группу. Называем её meinfo.

  • Q1 и Q2 что это? Объяснить сейчас я это не могу, но по ходу процесса всё станет ясно (прописываем их столько, сколько у нас будет вопросов. В данном случае 2 ( бот просит ссылку и текст )
class meinfo(StatesGroup):    
    Q1 = State()
    Q2 = State()

Теперь пишем, когда же бот начнет слушать наши ответы.

class meinfo(StatesGroup):
   Q1 = State()
   Q2 = State()

@dp.message_handler(Command("me"), state=None)       # Создаем команду /me для админа.
async def enter_meinfo(message: types.Message):
   if message.chat.id == config.admin:         
       await message.answer("начинаем настройку.\n"       # Бот спрашивает ссылку
                        "№1 Введите линк на ваш профиль")

       await meinfo.Q1.set()                                   # и начинает ждать наш ответ.
@dp.message_handler(state=meinfo.Q1)                               # Как только бот получит ответ, вот это выполнится
async def answer_q1(message: types.Message, state: FSMContext):
   answer = message.text
   await state.update_data(answer1=answer)                           # тут же он записывает наш ответ (наш линк)

   await message.answer("Линк сохранён. \n"
                        "№2 Введите текст.")
   await meinfo.Q2.set()                                   # дальше ждёт пока мы введем текст


@dp.message_handler(state=meinfo.Q2)                   # Текст пришел а значит переходим к этому шагу
async def answer_q1(message: types.Message, state: FSMContext):
   answer = message.text 
   await state.update_data(answer2=answer)               # опять же он записывает второй ответ

   await message.answer("Текст сохранён.")

   data = await state.get_data()               #
   answer1 = data.get("answer1")               # тут он сует ответы в переменную, чтобы сохранить их в "БД" и вывести в след. сообщении
   answer2 = data.get("answer2")               #

   joinedFile = open("link.txt","w", encoding="utf-8")       # Вносим в "БД" encoding="utf-8" НУЖЕН ДЛЯ ТОГО, ЧТОБЫ ЗАПИСЫВАЛИСЬ СМАЙЛИКИ
   joinedFile.write(str(answer1))
   joinedFile = open("text.txt","w", encoding="utf-8")       # Вносим в "БД" encoding="utf-8" НУЖЕН ДЛЯ ТОГО, ЧТОБЫ ЗАПИСЫВАЛИСЬ СМАЙЛИКИ
   joinedFile.write(str(answer2))

   await message.answer(f'Ваша ссылка на профиль : {answer1}\nВаш текст:\n{answer2}')   # Ну и выводим линк с текстом который бот записал

   await state.finish()

Запускаем проверяем. Вводим команду /me:


Всё работает и сохранилось в нашу "БД" текстовики link.txt и text.txt. Теперь создаём кнопку "Разработчик". В keyboard.py добавляем к основным кнопкам еще одну.

from aiogram import Bot, types
from aiogram.types import ReplyKeyboardRemove, ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardMarkup, InlineKeyboardButton

######################################################
start = types.ReplyKeyboardMarkup(resize_keyboard=True) # СОЗДАЕМ ВООБЩЕ ОСНОВУ ДЛЯ КНОПОК

info = types.KeyboardButton("Информация")           # ДОБАВЛЯЕМ КНОПКУ ИНФОРМАЦИИ
stats = types.KeyboardButton("Статистика")           # ДОБАВЛЯЕМ КНОПКУ СТАТИСТИКИ
razrab = types.KeyboardButton("Разработчик")           # ДОБАВЛЯЕМ КНОПКУ РАЗРАБОТЧИК

start.add(stats, info) #ДОБАВЛЯЕМ ИХ В БОТА
start.add(razrab)
######################################################
######################################################

stats = InlineKeyboardMarkup()   # СОЗДАЁМ ОСНОВУ ДЛЯ ИНЛАЙН КНОПКИ
stats.add(InlineKeyboardButton(f'Да', callback_data = 'join')) # СОЗДАЁМ КНОПКУ И КАЛБЭК К НЕЙ
stats.add(InlineKeyboardButton(f'Нет', callback_data = 'cancle')) # СОЗДАЁМ КНОПКУ И КАЛБЭК К НЕЙ

######################################################

В main.py, где @dp.message_handler(content_types=['text'])

Прописываем следующее:

@dp.message_handler(content_types=['text'])
async def get_message(message):
   if message.text == "Информация":
       await bot.send_message(message.chat.id, text = "Информация\nБот создан специально для моих любимых девочек и мальчиков с lzt ", parse_mode='Markdown')


   if message.text == "Статистика":
       await bot.send_message(message.chat.id, text = "Хочешь просмотреть статистику бота?", reply_markup=keyboard.stats, parse_mode='Markdown')

   if message.text == "Разработчик":
       link1 = open('link.txt', encoding="utf-8") # Вытаскиваем с нашей "БД" инфу, помещаем в переменную и выводим её
       link = link1.read()

       text1 = open('text.txt', encoding="utf-8") # Вытаскиваем с нашей "БД" инфу, помещаем в переменную и выводим её
       text = text1.read()

       await bot.send_message(message.chat.id, text = f"Создатель: {link}\n{text}", parse_mode='Markdown')

Запускаем, проверяем. Прописываем /start заново, чтобы новая кнопка появилась.

Отлично. Всё работает. Если сменить опять данные через /me, то при нажатии на "Разработчик" будут уже другие ваши текст и ссылка.


Подводим итог :

  • Мы научились создавать как обычные, так и inline кнопки.
  • Собирать статистику ( Подсчитывать пользователей в боте )
  • Подключать админку
  • Разобрались худо-бедно в callback-data
  • Бот теперь умеет редактировать свои сообщения
  • Победили FSM

На этом все! Удачи в программировании!





Report Page