LIMB
Вход Регистрация

тема

Как создать Telegram-бота на Python + Aiogram

Барретт · · 0 ответов · 414 просмотров

Всем приветик!
Сегодня я постараюсь объяснить Вам основные аспекты создания бота в Telegram на Python с поддержкой библиотеки Aiogram


На какие части будет делиться тема?

Подготовка:

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

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

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

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

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


Создаём необходимые файлы

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

Вложение

Импорт модулей и компонентов

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

Код:
pip install -U aiogram

Импортируем необходимое

Код:
# -*- 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 ## ТО, ЧЕГО ВЫ ЖДАЛИ - FSM
from aiogram.dispatcher.filters import Command ## ТО, ЧЕГО ВЫ ЖДАЛИ - FSM
from aiogram.contrib.fsm_storage.memory import MemoryStorage ## ТО, ЧЕГО ВЫ ЖДАЛИ - FSM
from aiogram.dispatcher.filters.state import StatesGroup, State ## ТО, ЧЕГО ВЫ ЖДАЛИ - FSM
######################################################################

######################
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

Спойлер: Важно
Не забывайте сохранять все действия в файле CTRL+S

Первая команда, альтернативная замена БД и обычные кнопки

Прелюдии закончились. Пора приступать к сладенькому.
Перед тем как начать с команды старт, надо добавить кнопки, которые будут появляться при вводе команды.
Открываем 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.

Для этого создаём текстовик 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')


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

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

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


Запускаем бота, через файл 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)


Отлично. Кнопку оживили, но запускать пока что не будем. Добавим просмотр статистики, при помощи Inline-кнопки, callback_data, да еще и админку прикрутим (Сразу трёх зайчиков
https://i.imgur.com/rs67BMm.gif" alt="">
)
Заходим в keyboard.py и пишем следующее

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


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

Теперь заходим в config.py
Пишем наш ID для админки

Код:
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')


Спойлер: Зачем "F" перед кавычками?
У нас есть значение d которое содержит в себе количество строк. f-строки вступают в дело, чтобы помочь с форматированием.
Грубо говоря без f нашу несчастную d посчитают за текст.

Вложение

Вложение

Вложение

Вложение

Рассылка пользователям бота (+ отправка фотографии)


Делаем рассылку пользователям. Решил сделать так, чтобы с текстом отправлялась еще и фотка.
Скидываем фотку в нашу папку с ботом. Переименовываем её во что-то красивое (у меня это 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

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

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


Отлично. Всё чики-пуки
https://i.imgur.com/LwmxQm8.gif" alt="">

FSM


Спойлер
FSM. О дааа. Самое вкусное сейчас будет. Сколько нервов было потрачено, чтобы понять как это работает
https://i.imgur.com/Q7hSd0l.gif" alt="">

Приступим.
Создаём наши "БД" два файла в папку с ботом: 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


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

https://i.imgur.com/72MMxrl.gif" alt="">

👍42 ❤️6 🔥6 🤔11 👎7

Гостевой просмотр. Чтобы отвечать и ставить реакции, войдите или зарегистрируйтесь.