Telegram бот з керуванням через файлову систему

Дмитро
Дмитро
Developer Python, PHP, Javascript
3.8
18.06.2019
15127
0

Завдання.

Нам надають картинки та опис товарів, і з них потрібно зробити каталог.

Давайте обговоримо Ваш проєкт
article-order-form__collapsed-text
Phone
Натискаючи кнопку “Відправити”, ви даєте згоду на обробку особистих даних. Детальніше

Цей каталог буде використовуватися телеграм-ботом, який подорожуватиме каталогами й пропонуватиме користувачеві товари, відображаючи картинки та опції для навігації каталогом.

Реалізація.

Картинки та текстові файли складатимемо в каталоги.

У нас буде загальний каталог, в якому кожен бот матиме свій власний, названий його ім'ям.

У каталозі бота буде багато підкаталогів, у кожному з яких буде картинка товару та текстовий файл message.txt з інструкціями для бота створення повідомлення при попаданні в цей каталог.

Цей файл містить вміст кожного повідомлення бота оформленого у вигляді xml документа.

Наприклад.

У мітці message вказується текстове повідомлення бота.

З тегів button будуть створені кнопки, за якими бот стрибатиме до інших директорій, зазначені в їхньому атрибуті value.

Для роботи нам знадобляться наступні бібліотеки:

python-telegram-bot - Для використання API Telegram;

bs4 - для парсингу xml текстового файлу.

Створимо робочий каталог.

mkdir tbot cd tbot

У ньому файл requirements.txt де перерахуємо необхідні залежності.

python-telegram-bot==12.0.0b1 bs4

Встановимо віртуальне оточення й встановимо пакети.

virtualenv -p python3 venv source ./venv/bin/activate pip install -r requirements.txt

Напишемо скрипт інсталяції робота install.py.

На початку створимо нову порожню директорію storage, де зберігатимемо каталоги ботів, тому що їх може бути кілька.

Потім, поставимо користувачеві 2 питання з проханням вказати:

1. Ім'я робота.
2. Секретний ключ.

На ім'я бота будемо створювати підкаталоги в теці storage.

Код інсталятора.

 #!/usr/bin/env python
import os
print('Встановлюємо бот!')
DATA_DIR = 'storage'

if __name__ == '__main__': # якщо запуск із консолі

# Створюємо каталог storage якщо його немає
if not os.path.exists(DATA_DIR):
print('Створюю директорію %s' % DATA_DIR)
os.makedirs(DATA_DIR)

name = input('Вкажіть ім'я бота:')
Формуємо шлях до каталогу бота
bot_path = %s/%s % (DATA_DIR, name)
if not os.path.exists(bot_path):
os.makedirs(bot_path)
if not os.path.exists(bot_path+'/index'):
print('Створюю директорію для бота %s' % name)
os.makedirs(bot_path+'/index')
else:
print("Такий бот з ім'ям %s вже є!" % bot_path)

key = input('Вкажіть ключ:')
# записуємо секретний ключ у файл
f = open(%s/key % bot_path, w+)
f.write(key)
f.close()
print("Все готово. Для активації бота запустіть команду ./run.py %s" % name)

Результат роботи із введеним не валідним ключем.

Як зареєструвати бота та отримати секретний ключ (токен).

Необхідно знайти контакт BotFather.

Написати команду /newbot

Підібрати унікальне ім'я, після чого вам надішле повідомлення такого виду.

Done! Congratulations on your new bot. You will find it at t.me/zdimon77_bot. You can now add a description, about section and profile picture for your bot, see /help for a list of commands. By the way, when you've finished creating your cool bot, ping our Bot Support if you want a better username for it. Just make sure the bot is fully operational before you do this. Use this token to access the HTTP API: 829228816:AAGSjgoh0hfj-fwyMBo4UlGvGxHtnp6Z_сs Keep your token secure and store it safely, it can be used by anyone to control your bot. For a description of the Bot API, see this page: https://core.telegram.org/bots/api

Почнемо писати бота у файл run.py.

Він запускатиметься з одним параметром - ім'ям бота.

Спочатку імпортуємо все, що нам потрібно.

import sys import os from telegram.ext import Updater from telegram.ext import CommandHandler, CallbackContext, CallbackQueryHandler from telegram import InlineKeyboardButton, InlineKeyboardMarkup from telegram import Bot from bs4 import BeautifulSoup from install import DATA_DIR

Забираємо ім'я бота з першого аргументу командного рядка, а якщо його немає, то повідомимо про це й вийдемо з програми.

try: botname = sys.argv[1] except: print("Введите в качестве аргумента имя бота например ./run.py my_bot") sys.exit()

Знайдемо секретний ключ та використовуючи його, створимо об'єкт бота.

print("Запускаю бота %s" % botname) bot_path = '%s/%s' % (DATA_DIR,botname) print("Читаю настройки из %s" % bot_path key = f.read() f.close() print("Ключ: %s" % key) bot = Bot(token=key)

Визначимо функцію start, яка спрацьовуватиме коли користувач натисне кнопку Start у програмі Telergam.

def start(update: Updater, context: CallbackContext): print("Start command!")

У цю функцію буде передано два об'єкти update та context.

Ми будемо використовувати update для отримання інформації про користувача.

Наприклад, отримати його логін та ідентифікатор кімнати можна так:

username = update.message.from_user['username']
room_id = update.message.chat_id

Ми будемо використовувати цей ідентифікатор для надсилання повідомлень у канал користувача.

Щоб прив'язати цю функцію до оброблювача події натискання на кнопку “Старт”, потрібно створити об'єкт-обробник класу CommandHandler і «згодувати» йому нашу функцію start.

start_handler = CommandHandler('start', start)

Перший параметр це ім'я команди, у разі це зарезервоване ім'я, але може бути довільним й визначено програмістом.

Контейнер для обробників буде об'єкт updater.

updater = Updater(token=key, use_context=True)

Додаємо обробник у контейнер та запускаємо нескінченний процес опитування telegram кімнати бота.

updater.dispatcher.add_handler(start_handler) updater.start_polling()

Повний код програми.

#!/usr/bin/env python import sys import os from telegram.ext import Updater from telegram.ext import CommandHandler, CallbackContext, CallbackQueryHandler from telegram import InlineKeyboardButton, InlineKeyboardMarkup from telegram import Bot from bs4 import BeautifulSoup from install import DATA_DIR try: botname = sys.argv[1] except: print("Введите в качестве аргумента имя бота например ./run.py my_bot") sys.exit() print("Запускаю бота %s" % botname) bot_path = '%s/%s' % (DATA_DIR,botname) print("Читаю настройки из %s" % bot_path) f = open(bot_path+'/key','r') key = f.read() f.close() bot = Bot(token=key) def start(update: Updater, context: CallbackContext): print("Start command!") username = update.message.from_user['username'] room_id = update.message.chat_id print("Новый пользователь %s room_id: %s" % (username, room_id)) start_handler = CommandHandler('start', start) updater = Updater(token=key, use_context=True) updater.dispatcher.add_handler(start_handler) updater.start_polling()

Результат праці.

Пробуємо надіслати ботом повідомлення вітання.

Це робиться функцією bot.send_message(), в яку, крім повідомлення, передається ідентифікатор кімнати.

def start(update: Updater, context: CallbackContext):
print("Start command!")
username = update.message.from_user['username']
room_id = update.message.chat_id
print("Новий користувач %s room_id: %s" % (username, room_id))
bot.send_message(room_id, 'Привіт!')

Якщо треба надіслати картинку, то зробити це можна іншою функцією.

bot.send_photo(chat_id=room_id, photo=open(img_path, 'rb'))

Тепер ми знаємо як надсилати повідомлення та картинки.

Розберімося з кнопками та як їх посилати?

Спочатку необхідно визначитися з їх компонуванням.

Припустимо, ми закинули кнопки в список, наприклад:

lst = ['btn1', 'btn2', 'btn3', 'btn4']

Щоб розташувати їх у два рядки по дві, необхідно з цього списку сформувати наступну структуру:

[['btn1', 'btn2'], ['btn3', 'btn4']]

Ось невелика функція, яка це робить.

def build_menu(buttons,n_cols):
menu = [buttons[i:i + n_cols] for i in range(0, len(buttons), n_cols)]
return menu

Тут я використовую генератор списку, який формує список із низкою вкладених списків за кількістю другого параметра.

Сама кнопка створюється такою конструкцією.

from telegram import InlineKeyboardButton
btn = InlineKeyboardButton('Натисніть мене',callback_data='button_pressed')

При створенні ми визначаємо назву кнопки та корисне навантаження, яке отримаємо після натискання і зможемо визначити, яка саме кнопка була натиснута.

Відправимо 4 тестові кнопки по 2 до ряду.

# список с кнопками
btn_list = []
# забрасываю 4 штучки
for cnt in range(0,4):
btn_list.append(InlineKeyboardButton('Нажми меня %s' % cnt,callback_data='button_%s_pressed' % cnt))
# формирую объект разметки из класса InlineKeyboardMarkup
markup_list = InlineKeyboardMarkup(build_menu(btn_list,n_cols=2))
# посылаю кнопки
bot.send_message(room_id, 'Кнопки', reply_markup=markup_list)
 

Сигнатура функції-обробника для кнопок виглядає за аналогією зі start.

def press_button(update: Updater, context: CallbackContext):
print("Pressing button %s" % update.callback_query.data)

Тільки зв'язування обробника відрізняється використанням класу CallbackQueryHandler, який прийме нашу функцію.

updater.dispatcher.add_handler(CallbackQueryHandler(press_button))

Тепер залишилося зв'язати це все разом, і змусити бота переміщатися каталогами та підбирати їх вміст.

У новій функції navigate, яка буде викликатись при натисканні на кнопку, я робитиму наступне:

  1. Перейти до потрібного каталогу.
  2. Посилати картинку image.png якщо вона там є.
  3. Прочитати файл message.txt.
  4. Надсилати вміст тега message і кнопки в тегах button.

Код функції.

def navigate(command, chat_id):
path = '%s/%s' % (bot_path,command)
img_path = '%s/image.png' % path
print(img_path)
# шлю картинку если нашел
if os.path.isfile(img_path):
bot.send_photo(chat_id=chat_id, photo=open(img_path, 'rb'))
message_path = path+'/message.txt'
# находим файл message.txt
if os.path.isfile(message_path):
with open(message_path) as f:
but_txt = f.read()
# парсим xml
soup = BeautifulSoup(but_txt, 'html.parser')
msg = soup.find('message')
btns = soup.findAll('button')
btn_lst = []
# формируем кнопки
for bt in btns:
btn_lst.append(InlineKeyboardButton(bt.text,callback_data=bt['value']))
button_list = InlineKeyboardMarkup(build_menu(btn_lst,n_cols=1))
# посылаем кнопки и сообщение
bot.send_message(chat_id, msg.text, reply_markup=button_list)

Останній пункт використовує бібліотеку BeautifulSoup та реалізовано в наступний спосіб.

 with open(message_path) as f:
but_txt = f.read()
soup = BeautifulSoup(but_txt, 'html.parser')
msg = soup.find('message') # один елемент
btns = soup.findAll('button') # кілька

Повний код робота:

 #!/usr/bin/env python3
import sys
import os
від telegram.ext import Updater
з telegram.ext import CommandHandler, CallbackContext, CallbackQueryHandler
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram import Bot
from bs4 import BeautifulSoup

from install import DATA_DIR
try:
botname = sys.argv[1]
except:
print("Введіть як аргумент ім'я бота наприклад ./run.py my_bot")
sys.exit()

print("Запускаю бота %s" % botname)
bot_path = %s/%s % (DATA_DIR,botname)
print("Читаю налаштування з %s" % bot_path)
f = open(bot_path+'/key','r')
key = f.read()
f.close()
bot = Bot (token = key)

def navigate(command, chat_id):
path = %s/%s % (bot_path,command)
img_path = %s/image.png % path
print(img_path)
# шлю картинку якщо знайшов
if os.path.isfile(img_path):
bot.send_photo(chat_id=chat_id, photo=open(img_path, 'rb'))
message_path = path+'/message.txt'
# знаходимо файл message.txt
if os.path.isfile(message_path):
with open(message_path) as f:
but_txt = f.read()
# Парсим xml
soup = BeautifulSoup(but_txt, 'html.parser')
msg = soup.find('message')
btns = soup.findAll('button')
btn_lst = []
# формуємо кнопки
for bt in btns:
btn_lst.append(InlineKeyboardButton(bt.text,callback_data=bt['value']))
button_list = InlineKeyboardMarkup(build_menu(btn_lst,n_cols=1))
# посилаємо кнопки та повідомлення
bot.send_message(chat_id, msg.text, reply_markup=button_list)


def build_menu(buttons,n_cols):
menu = [buttons[i:i + n_cols] for i in range(0, len(buttons), n_cols)]
return menu

def press_button(update: Updater, context: CallbackContext):
print("Pressing button %s" % update.callback_query.data)
navigate(update.callback_query.data,update.callback_query.message.chat_id)

def start(update: Updater, context: CallbackContext):
print("Start command!")
username = update.message.from_user['username']
room_id = update.message.chat_id
navigate('index',update.message.chat_id)

start_handler = CommandHandler('start', start)

updater = Updater(token=key, use_context=True)
updater.dispatcher.add_handler(start_handler)
updater.dispatcher.add_handler(CallbackQueryHandler(press_button))
updater.start_polling()

Результат роботи.

Висновки

У статті розглянуто процес створення телеграм-бота мовою Python. В його основу покладено принцип навігації за каталогами та відправлення повідомлень, що містять текст, картинки та кнопки для навігації до наступного каталогу. Висвітлено механізми управління ботом та реакції на події користувача.

Перспективи

Бракує функціоналу з прийняття контактної інформації від користувача зі збереженням замовлень у базі даних та повідомлення адміністратора. Про це намагатимуся розповісти у наступній статті.

Як вам стаття?
3.8
Проголосувало: 12
Давайте обговоримо Ваш проєкт
article-order-form__collapsed-text
Phone
Натискаючи кнопку “Відправити”, ви даєте згоду на обробку особистих даних. Детальніше
Звернути
Коментарі
(0)
Будьте першими, хто залишить коментар
have questions image
Залишились питання?
Залиште контактні дані. Наш менеджер зв'яжеться та проконсультує вас.
Підписуйтесь на розсилку Айтижблог
blog subscriber decor image
Бажаєте отримувати цікаві статті?
Натискаючи кнопку “Відправити”, ви даєте згоду на обробку особистих даних. Детальніше
Слідкуйте за нами у соціальних мережах