DevGang
Авторизоваться

Полное руководство Python по созданию Telegram Bot с использованием python-telegram-bot

Telegram-боты - это увлечение которые позволяют вам играть в игры, находить друзей, находить новых ботов и даже создавать ботов - возможности безграничны. Сегодня я расскажу о том, как я создал telegram-бота для борьбы с пищевыми отходами, предоставляя полное учебное пособие, начиная с:

1) как создать telegram-бота с помощью BotFather

2) задавать вопросы

3) получать и хранить ответы пользователей и фотографии

4) размещать эту информацию на общедоступном Telegram-канале

5) вызов API Google Maps, чтобы показать местоположение еды на карте.

Пищевые отходы - абсурдная проблема

По данным продовольственно-сельскохозяйственной организации, ежегодно в мире растрачивается около 1,3 миллиарда тонн продуктов, что составляет одну треть всего продовольствия, производимого для потребления человеком. Самые распространенные виновники этой ситуации - это буфеты, рестораны и даже домашние хозяйства, по-причине закупки большого количества продуктов и не рационального их использования, превращая их в отходы даже по абсурдным причинам их внешнего вида.

В то же время 820 миллионов человек ежедневно голодают. Это кажется противоречивым - как мы можем тратить так много еды и в то же время позволить стольким людям в мире голодать? Это сводится к отсутствию связи между теми, у кого остались излишки и теми, кто нуждается в пище. Конечно, есть благотворительные организации, которые принимают пожертвования на питание, но нет прямой связи между теми, кто нуждается в еде и у того у кого продукты в избытке. В сочетании с удручающе запутанными регулирующими процессами неудивительно, что существует значительный временной промежуток между пожертвованиями продуктов питания и тем, когда они действительно приносят пользу, кто в них больше всего нуждается.

Учитывая эту проблему, почему бы не облегчить общение между теми у кого есть лишние продукты и теми, кто нуждается в еде? Это поможет связать людей с остатками пищи непосредственно с теми, кто нуждается в еде. Таким образом, пища может быть быстро получена теми, кто в ней нуждается, без значительной задержки.

Представляем Telegram Bots

Конечно, это все звучит хорошо, но как мы можем реализовать это? Именно здесь боты и каналы Telegram предоставляют простое решение для связи поставщиков продуктов питания с теми, кто нуждается в пище.

Поставщики продуктов питания могут сначала пообщаться с ботом Telegram, чтобы предоставить важную информацию о еде, такую ​​как местоположение пищи, диетические характеристики и т.д. После того, как эта информация будет собрана, бот опубликует свои списки продуктов на канале Telegram, который доступен для общественности. Как только еда размещена на канале, нуждающиеся люди могут просто забрать еду. Все это может быть выполнено без какой-либо существенной задержки - фактически весь процесс отправки еды конечному пользователю, получающему информацию, может занять менее 10 секунд!

Иллюстрированный поток процесса размещения продуктов питания
Иллюстрированный поток процесса размещения продуктов питания

Для этого бот будет задавать следующие вопросы из поста о продуктах:

  1. Расположение еды. Используя API Карт Google, местоположение будет преобразовано в соответствующие координаты широты и долготы, которые затем могут быть отображены на карте, чтобы пользователи могли легко перемещаться к этому местоположению.
  2. Изображение еды.
  3. Диетические характеристики, то есть, является ли еда халяльной, вегетарианской и так далее.
  4. Количество порций, доступных для приема.
  5. Время, за которое еда должна быть собрана, прежде чем она будет выброшена.

Пост с едой ответит на эти вопросы, а бот соберет ответы в одно сообщение. С этим кратким сообщением бот отправит информацию общедоступному каналу после того, как автор подтвердит правильность введенных данных.

Создание вашего бота

Теперь, когда у нас есть понимание того, как работает этот процесс, давайте перейдем к мельчайшим деталям!

Прежде всего, мы создадим бота. Для этого отправьте в телеграмме команду /newbot в BotFather. Он запросит у вас имя бота, а затем имя пользователя (которое должно заканчиваться на боте). Вы можете легко изменить имя бота с помощью команды /setname, но невозможно изменить имя пользователя бота через BotFather, поэтому, если вы не уверены, вы можете сначала создать тестового бота со случайным именем пользователя и после того, как вы проверили все функции и убедились, что ваш бот работает, создайте нового бота с желаемым именем пользователя.

Первое общение с BotFather - «one bot to rule them all»!
Первое общение с BotFather - «one bot to rule them all»!

После создания бота, BotFather отправит вам ключ API бота (под строкой «Use this token to access the HTTP API:»). Держите этот ключ API у себя, потому что его может использовать любой человек с ключом API для управления вашим ботом, что для вас не желательно! Есть несколько команд, которые вы можете отправить в BotFather для настройки вашего бота, включая изменение описания бота, его профиля и т.д.

Использование python-telegram-bot для взаимодействия с ботом

Создав нашего бота, пришло время дать ему дополнительный функционал! Поскольку цель состоит в том, чтобы создать бота, который взаимодействует с постом еды, он должен иметь возможность отправлять ему вопросы, чтобы запросить необходимую информацию, а затем сохранить информацию, прежде чем пересылать ее на общедоступный канал. Для этого мы можем использовать очень удобную оболочку Python, python-telegram-bot. На странице Github приведено множество примеров ботов, написанных с помощью оболочки, так что проверьте страницу, чтобы увидеть, есть ли примеры, подходящие для вашего варианта использования!  

Во-первых, мы установим необходимые пакеты. Поскольку мы вызываем API Карт Google для предоставления карты местоположения продуктов питания, мы также установим пакет Python googlemaps. Если вы не хотите использовать Google Maps, вы можете пропустить установку этого пакета.

В вашем терминале / командной строке введите следующие команды для установки двух библиотек:

pip install python-telegram-bot # using version 12.7
pip install googlemaps # using version 4.3.1

Теперь, когда мы установили две библиотеки, давайте погрузимся в код! Вот код (на случай, если вы захотите перейти прямо к нему), который я подробно покажу ниже.

Полный код бота Telegram, включая вызов в Google Maps API
"""
telegrambot.py
Starter code to create a telegram bot using the python-telegram-bot library.
Includes call to Google Maps API to extract map coordinates from location.
Author: liuhh02 https://medium.com/@liuhh02
"""

import logging
import telegram
from telegram import (ReplyKeyboardMarkup, ReplyKeyboardRemove)
from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters,
                          ConversationHandler)
from googlemaps import Client as GoogleMaps
import os

# Включить ведение журнала
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
                    level=logging.INFO)

logger = logging.getLogger(__name__)

LOCATION, PHOTO, DIET, SERVINGS, TIME, CONFIRMATION = range(6)

reply_keyboard = [['Confirm', 'Restart']]
markup = ReplyKeyboardMarkup(reply_keyboard, resize_keyboard=True, one_time_keyboard=True)
TOKEN = 'YOURTELEGRAMBOTTOKEN'
bot = telegram.Bot(token=TOKEN)
chat_id = 'YOURTELEGRAMCHANNEL'
GMAPSAPI = 'YOURGOOGLEMAPSAPITOKEN'
gmaps = GoogleMaps(GMAPSAPI)

PORT = int(os.environ.get('PORT', 5000))

def facts_to_str(user_data):
    facts = list()

    for key, value in user_data.items():
        facts.append('{} - {}'.format(key, value))

    return "\n".join(facts).join(['\n', '\n'])


def start(update, context):
    update.message.reply_text(
        "Hi! I am your posting assistant to help you advertise your leftover food to reduce food waste. "
        "To start, please type the location of the leftover food.")
    return LOCATION


def location(update, context):
    user = update.message.from_user
    user_data = context.user_data
    category = 'Location'
    text = update.message.text
    user_data[category] = text
    logger.info("Location of %s: %s", user.first_name, update.message.text)

    update.message.reply_text('I see! Please send a photo of the leftovers, '
                              'so users will know how the food looks like, or send /skip if you don\'t want to.')
    return PHOTO


def photo(update, context):
    user = update.message.from_user
    user_data = context.user_data
    photo_file = update.message.photo[-1].get_file()
    photo_file.download('user_photo.jpg')
    category = 'Photo Provided'
    user_data[category] = 'Yes'
    logger.info("Photo of %s: %s", user.first_name, 'user_photo.jpg')
    update.message.reply_text('Great! Is the food halal? Vegetarian? Please type in the dietary specifications of the food.')

    return DIET


def skip_photo(update, context):
    user = update.message.from_user
    user_data = context.user_data
    category = 'Photo Provided'
    user_data[category] = 'No'
    logger.info("User %s did not send a photo.", user.first_name)
    update.message.reply_text('Is the food halal? Vegetarian? Please type in the dietary specifications of the food.')

    return DIET


def diet(update, context):
    user = update.message.from_user
    user_data = context.user_data
    category = 'Dietary Specifications'
    text = update.message.text
    user_data[category] = text
    logger.info("Dietary Specification of food: %s", update.message.text)
    update.message.reply_text('How many servings are there?')

    return SERVINGS

def servings(update, context):
    user = update.message.from_user
    user_data = context.user_data
    category = 'Number of Servings'
    text = update.message.text
    user_data[category] = text
    logger.info("Number of servings: %s", update.message.text)
    update.message.reply_text('What time will the food be available until?')

    return TIME
    
def time(update, context):
	user = update.message.from_user
	user_data = context.user_data
	category = 'Time to Take Food By'
	text = update.message.text
	user_data[category] = text
	logger.info("Time to Take Food By: %s", update.message.text)
	update.message.reply_text("Thank you for providing the information! Please check the information is correct:"
								"{}".format(facts_to_str(user_data)), reply_markup=markup)

	return CONFIRMATION

def confirmation(update, context):
    user_data = context.user_data
    user = update.message.from_user
    update.message.reply_text("Thank you! I will post the information on the channel @" + chat_id + "  now.", reply_markup=ReplyKeyboardRemove())
    if (user_data['Photo Provided'] == 'Yes'):
        del user_data['Photo Provided']
        bot.send_photo(chat_id=chat_id, photo=open('user_photo.jpg', 'rb'), 
		caption="<b>Food is Available!</b> Check the details below: \n {}".format(facts_to_str(user_data)) +
		"\n For more information, message the poster {}".format(user.name), parse_mode=telegram.ParseMode.HTML)
    else:
        del user_data['Photo Provided']
        bot.sendMessage(chat_id=chat_id, 
            text="<b>Food is Available!</b> Check the details below: \n {}".format(facts_to_str(user_data)) +
        "\n For more information, message the poster {}".format(user.name), parse_mode=telegram.ParseMode.HTML)
    geocode_result = gmaps.geocode(user_data['Location'])
    lat = geocode_result[0]['geometry']['location'] ['lat']
    lng = geocode_result[0]['geometry']['location']['lng']
    bot.send_location(chat_id=chat_id, latitude=lat, longitude=lng)

    return ConversationHandler.END

def cancel(update, context):
    user = update.message.from_user
    logger.info("User %s canceled the conversation.", user.first_name)
    update.message.reply_text('Bye! Hope to see you again next time.',
                              reply_markup=ReplyKeyboardRemove())

    return ConversationHandler.END


def error(update, context):
    """Log Errors caused by Updates."""
    logger.warning('Update "%s" caused error "%s"', update, context.error)


def main():
    # Создайте программу обновления и передайте ей токен вашего бота.
    # Убедитесь, что use_context=true используется для новых условиях на основе обратных вызовов
    # Пост версия12 в этом больше не будет необходимости
    updater = Updater(TOKEN, use_context=True)

    # Пусть диспетчер зарегистрирует обработчиков
    dp = updater.dispatcher

    # Добавьте обработчик разговоров с состояниями пол, фото, местонахождение и биография
    conv_handler = ConversationHandler(
        entry_points=[CommandHandler('start', start)],

        states={

            LOCATION: [CommandHandler('start', start), MessageHandler(Filters.text, location)],

            PHOTO: [CommandHandler('start', start), MessageHandler(Filters.photo, photo),
                    CommandHandler('skip', skip_photo)],

            DIET: [CommandHandler('start', start), MessageHandler(Filters.text, diet)],

            SERVINGS: [CommandHandler('start', start), MessageHandler(Filters.text, servings)],

            TIME: [CommandHandler('start', start), MessageHandler(Filters.text, time)],

            CONFIRMATION: [MessageHandler(Filters.regex('^Confirm$'),
                                      confirmation),
            MessageHandler(Filters.regex('^Restart$'),
                                      start)
                       ]

        },

        fallbacks=[CommandHandler('cancel', cancel)]
    )

    dp.add_handler(conv_handler)

    # лог всех ошибок
    dp.add_error_handler(error)

    updater.start_webhook(listen="0.0.0.0", port=int(PORT), url_path=TOKEN)
    updater.bot.setWebhook('https://YOURHEROKUAPPNAME.herokuapp.com/' + TOKEN)

    # Запускайте бота до тех пор, пока вы не нажмете Ctrl-C или процесс не получит SIGINT,
    # SIGTERM или SIGABRT. Это следует использовать большую часть времени, так как
    # start_polling() является неблокирующим и остановит бота.
    updater.idle()


if __name__ == '__main__':
    main()

Во-первых, мы импортируем соответствующие библиотеки:

import logging
import telegram
from telegram import (ReplyKeyboardMarkup, ReplyKeyboardRemove)
from telegram.ext import (Updater, CommandHandler, MessageHandler,  Filters, ConversationHandler)
from googlemaps import Client as GoogleMaps

Мы импортируем логи, чтобы показать сообщения, когда пользователь находится в терминале и когда он взаимодействует с ботом, а также различные модули под python-telegram-bot. Мы также импортируем googlemaps, чтобы показать местоположение еды на карте.

Далее мы настраиваем параметры ведения журнала следующим образом:

# Включить ведение журнала
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO) 
logger = logging.getLogger(__name__)

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

LOCATION, PHOTO, DIET, SERVINGS, TIME, CONFIRMATION = range(6)

Для подтверждения появится клавиатура с ответами, где пользователь сможет выбрать два варианта: подтвердить правильность информации или перезапустить весь процесс.

Кнопка подтверждения и перезагрузки
Кнопка подтверждения и перезагрузки

Итак, мы указываем две опции, используя модуль ReplyKeyboardMarkup в модуле python-telegram-bot, используя следующую строку кода:

reply_keyboard = [['Confirm', 'Restart']]
markup = ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True)

После этого мы указываем некоторые переменные, включая API вашего бота Telegram (который вы получили от BotFather), а также API Google Maps.

Чтобы получить API Карт Google, вам потребуется аккаунт Google Cloud Platform. После создания учетной записи (будут предоставлены бесплатные кредиты, поэтому она должна быть бесплатной, если вы не превышаете квоту), перейдите в консоль Google Cloud Platform и выполните следующие действия:  

  1. Поиск API и услуг в строке поиска.
  2. Нажмите синюю кнопку «plus » с надписью "ENABLE APIS AND SERVICES" прямо под строкой поиска
  3. Найдите API геокодирования и включите его.
  4. В разделе « API & Services» перейдите в раздел «Credentials» и выберите «CREATE CREDENTIALS» > API key.

Будет сгенерирован ключ API, поэтому просто скопируйте его. Это будет ваш токен API карт Google. Опять же, держите его в безопасности! API геокодирования предоставляет 200 долларов в кредит, так что этого будет достаточно для ваших целей.

TOKEN = 'YOURTELEGRAMBOTTOKEN'
bot = telegram.Bot(token=TOKEN) 
GMAPSAPI = 'YOURGOOGLEMAPSAPITOKEN'
gmaps = GoogleMaps(GMAPSAPI)

Теперь, когда мы определили переменные, давайте углубимся в фактическую часть кода! Во-первых, мы определяем функцию для суммирования информации, представленной рекламным постом, как показано ниже:

def facts_to_str(user_data):
    facts = list()
    for key, value in user_data.items():
        facts.append('{} - {}'.format(key, value))
    return "\n".join(facts).join(['\n', '\n'])

Когда эта функция вызывается, она возвращает информацию в формате ключ-значение (например, Dietary Specifications - Halal).

Далее мы определяем, что происходит, когда пользователь запускает бота. Это делается с помощью команды /start (она работает аналогично тому, как вы использовали команду /newbot с BotFather для создания своего бота). Когда пост запускает бота, мы хотим, чтобы бот приветствовал пользователя и предложил ему отправить местоположение еды, поэтому мы определяем начало следующим образом:

def start(update, context):
    update.message.reply_text("Hi! I am your posting assistant to help you advertise your leftover food to reduce food waste. To start, please type the location of the leftover food.")
    return LOCATION

Предыдущая функция возвращает LOCATION, которая является следующей функцией, потому что бот сохранит местоположение, предоставленное пользователем, для создания сводки в конце. В расположении функции, код делает несколько вещей: во-первых он говорит боту, что есть новое сообщение от пользователя, а затем хранит информацию предоставленную пользователем под ключ «Location» для удобства при печати резюме. Затем он регистрирует информацию для целей отладки, на случай, если что-то пойдет не так. Наконец, бот отвечает сообщением с просьбой сфотографировать еду. Затем он возвращает PHOTO, которое является следующей функцией.

def location(update, context):
    user = update.message.from_user
    user_data = context.user_data
    category = 'Location'
    text = update.message.text
    user_data[category] = text
    logger.info("Location of %s: %s", user.first_name, update.message.text)
    update.message.reply_text('I see! Please send a photo of the leftovers, so users will know how the food looks like, or send /skip if you don\'t want to.')
     return PHOTO

В этой следующей функции у пользователя есть два варианта: либо сфотографировать еду и отправить ее боту, либо пропустить, если у него нет готового изображения.

def photo(update, context):
    user = update.message.from_user
    photo_file = update.message.photo[-1].get_file()
    photo_file.download('user_photo.jpg')
    logger.info("Photo of %s: %s", user.first_name, 'user_photo.jpg')
    update.message.reply_text('Great! Is the food halal? Vegetarian? Please type in the dietary specifications of the food.')
     return DIET 
def skip_photo(update, context):
    user = update.message.from_user
    logger.info("User %s did not send a photo.", user.first_name)
    update.message.reply_text('Is the food halal? Vegetarian? Please type in the dietary specifications of the food.')
    return DIET

Опять же, бот сначала получает сообщение от пользователя, а затем получает от него файл фотографии, который затем сохраняется как user_photo.jpg. Затем он регистрирует информацию, и бот отвечает следующим вопросом о диетических характеристиках пищи.

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

def diet(update, context):
    user = update.message.from_user
    user_data = context.user_data
    category = 'Dietary Specifications'
    text = update.message.text
    user_data[category] = text
    logger.info("Dietary Specification of food: %s", update.message.text)
    update.message.reply_text('How many servings are there?')
    return SERVINGS 
def servings(update, context):
    user = update.message.from_user
    user_data = context.user_data
    category = 'Number of Servings'
    text = update.message.text
    user_data[category] = text
    logger.info("Number of servings: %s", update.message.text)    update.message.reply_text('What time will the food be available until?')
     return TIME
def time(update, context):
    user = update.message.from_user
    user_data = context.user_data
    category = 'Time to Take Food By'
    text = update.message.text
    user_data[category] = text
    logger.info("Time to Take Food By: %s", update.message.text) 
    update.message.reply_text("Thank you for providing the information! Please check the information is correct:{}".format(facts_to_str(user_data)), reply_markup=markup)
    return CONFIRMATION

Получив всю необходимую информацию, настало время ее обобщить и подтвердить с помощью поста. Во-первых, бот объединяет информацию, а затем отправляет сводку, используя функцию fact_to_str (user_data), которую мы определили выше.

Если пользователь выберет «Confirm», бот отправит информацию на общедоступный канал. Параметр chat_id файла bot.send_photo представляет ссылку канала.

def confirmation(update, context):
    user_data = context.user_data
    user = update.message.from_user
    update.message.reply_text("Thank you! I will post the information on the channel now.", reply_markup=ReplyKeyboardRemove())
    bot.send_photo(chat_id='@nameofchannel', photo=open('user_photo.jpg', 'rb'), caption="<b>Food is Available!</b> Check the details below: \n {}".format(facts_to_str(user_data)) +  "\n For more information, message the poster {}".format(user.name), parse_mode=telegram.ParseMode.HTML)
    geocode_result = gmaps.geocode(user_data['Location'])
    lat = geocode_result[0]['geometry']['location'] ['lat']
    lng = geocode_result[0]['geometry']['location']['lng']
    bot.send_location(chat_id='@nameofchannel', latitude=lat, longitude=lng)

Кроме того, теперь мы обращаемся к картам Google, чтобы точно определить местоположение, указанное на плакате. Местоположение было сохранено в user_data ['Location'] ранее, когда пользователь ответил на первый вопрос о нем, поэтому теперь мы будем извлекать широту и долготу, используя карты Google. Получив эту информацию, бот отправляет данные о местоположении вместе с картой на канал, указанный в @nameofchannel.

Вот некоторые вспомогательные функции на случай, если пользователь отменит разговор с ботом или возникнет ошибка:

def cancel(update, context):
    user = update.message.from_user
    logger.info("User %s canceled the conversation.", user.first_name)
    update.message.reply_text('Bye! Hope to see you again next time.', reply_markup=ReplyKeyboardRemove())
     return ConversationHandler.END
def error(update, context):
    """Log Errors caused by Updates."""
    logger.warning('Update "%s" caused error "%s"', update, context.error)

Мы находимся в последнем сегменте кода! Здесь код определяет, что бот должен делать для каждого из шести пунктов. Наконец, мы начинаем опрос, который в основном позволяет нам получать информацию с постов. GitHub wiki python-telegram-bot лучше объясняет код здесь, так что проверьте его, если вы не уверены, что делает каждая строка!

#!/usr/bin/python
# -- coding: utf-8 --


def main():
    updater = Updater(TOKEN, use_context=True)

     # Пусть диспетчер зарегистрирует обработчиков
    dp = updater.dispatcher

     # Добавьте обработчик разговоров с состояниями LOCATION, PHOTO, DIET, SERVINGS, TIME and DONE

    conv_handler = \
        ConversationHandler(entry_points=[CommandHandler('start',
                            start)], states={
        LOCATION: [MessageHandler(Filters.text, location)],
        PHOTO: [MessageHandler(Filters.photo, photo),
                CommandHandler('skip', skip_photo)],
        DIET: [MessageHandler(Filters.text, location)],
        SERVINGS: [MessageHandler(Filters.text, bio)],
        TIME: [MessageHandler(Filters.text, time)],
        CONFIRMATION: [MessageHandler(Filters.regex('^Confirm$'),
                       done), MessageHandler(Filters.regex('^Restart$'
                       ), start)],
        }, fallbacks=[CommandHandler('cancel', cancel),
                      CommandHandler('start', start)])
    dp.add_handler(conv_handler)  # log all errors
    dp.add_error_handler(error)

    # Запускаем бота

    updater.start_polling()

    # Запускайте бота, нажмете кнопку Ctrl-C

    updater.idle()


if _name_ == '_main_':
    main()

Чтобы запустить своего бота, просто перейдите в terminal/command и запустите файл python. После выполнения файла перейдите к боту и введите /start. Ваш бот должен ответить и задать вам вопросы, как написано выше! Когда вы отвечаете, вы также должны увидеть следующие журналы:

Регистрация ответа автора, как видно из командной строки
Регистрация ответа автора, как видно из командной строки

Итак, это все! 

Источник:

#Google API
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

Поделитесь своим опытом, расскажите о новом инструменте, библиотеке или фреймворке. Для этого не обязательно становится постоянным автором.

Попробовать

В этом месте могла бы быть ваша реклама

Разместить рекламу