python-telegram-bot 是对 Telegram Bot API 的一个极其出色的封装;正如其简介中所说的:「We have made you a wrapper you can't refuse」,这的确是一个让你无法拒绝的封装器。本文将详述其中最主要的一个子模块 telegram.ext,并以此为基础,部署一个基于 Webhook 协议的 Telegram Bot。文章最后会对传统的轮询模式和 Webhook 模式进行解释和比较。

关于 Bot 的创建和设置,以及如何获取 Token,可参考「官网文档」,这里并不赘述了。需要注意的是,用户必须先发起会话,Bot 才能获取到会话 ID 来进行消息推送。

pip3 install python-telegram-bot

服务器环境:香港 VPS;CentOS 7.4;Nginx v1.14.0;python-telegram-bot v10.1.0。

子模块 telegram.ext 剖析

  • telegram.ext 模块中最重要的两个类是 UpdaterDispatcher。实例化 Updater ,用来接受来自 Telegram 的更新,其属性 dispatcher 是一个自动实例化的 Dispatcher 对象。之后便可以通过 add_handler 方法向 dispatcher 中注册各种事件处理句柄(Handler)。
from telegram.ext import Updater

updater = Updater(token='TOKEN')
dispatcher = updater.dispatcher

updater.job_queue 是另一个会被自动创建的实例。JobQueue 类可以用来延迟或周期性执行某个任务。

  • Handler 是继承自基类 telegram.ext.Handler 的实例,主要用于路由各种消息事件到绑定的回调函数(callback function)中。其中比较常用的是 CommandHandler 类和 MessageHandler 类。

CommandHandler 类可以响应用户输入的带 / 的命令(如 /start),并映射到对应的处理函数上。如果实例化时指定参数 pass_args=True,则它会以命令加参数的形式对用户的输入进行解析。

from telegram.ext import CommandHandler

def help_callback(bot, update):
    update.message.reply_text('This is a help reply.')

def start_callback(bot, update, args):
    input = " ".join(args)
    update.message.reply_text('Your input is ' + input)

dispatcher.add_handler(CommandHandler('help', help_callback))
dispatcher.add_handler(CommandHandler('start', start_callback))

MessageHandler 类用于处理常规的非命令形式的消息。结合 Filters 类可以对消息中不同类型的内容进行条件过滤。

from telegram.ext import MessageHandler, Filters

def echo(bot, update):
    bot.send_message(chat_id=update.message.chat_id, text=update.message.text)

def unknown(bot, update):
    bot.send_message(chat_id=update.message.chat_id, text="unknown command")

dispatcher.add_handler(MessageHandler(Filters.text, echo))
dispatcher.add_handler(MessageHandler(Filters.command, unknown))

如果要处理更复杂的输入可以使用基于 re 模块构建的 RegexHandler 类。

  • 通过 add_error_handler 方法可以向 Dispatcher 中注册一个异常处理函数,所有和 Telegram 相关的异常都封装在 TelegramError 类及其子类中。
from telegram.error import (TelegramError, Unauthorized, BadRequest,
                            TimedOut, ChatMigrated, NetworkError)

def error_callback(bot, update, error):
    try:
        raise error
    except Unauthorized:
        # ...
    except BadRequest:
        # ...
    except TelegramError:
        # handle all other telegram related errors

dispatcher.add_error_handler(error_callback)
  • 最后,以轮询的方式启动 Bot 脚本,并阻塞其执行直到用户发送 Ctrl+C 信号。
updater.start_polling()
updater.stop()

部署基于 Webhook 的 Telegram Bot

python-telegram-bot 中,轮询(Polling)是通过 get_updates 方法定期连接 Telegram 服务器检查是否有更新。而 Webhook 模式则是一次性传递一个 URL 给 Telegram,此后 Telegram 便会将新消息发送到这个指定的 URL 上。

# /usr/local/nginx/conf/nginx.conf
server {
    server_name  tg.<domain>.com;
    location /TOKEN {
        proxy_pass http://127.0.0.1:8081;
    }
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/tg.<domain>.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/tg.<domain>.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}

注意:这里的 TOKEN 可以是任意字符串,但建议设置为 Bot 的 Token 来避免他人向该地址恶意 POST 数据。

  • 将 Bot 原始的轮询模式修改为 Webhook 模式。这里的 TOKEN 字符串与 Nginx 中的设置保持一致即可。

特别注意:根据原始文档的说明,需要向 set_webhook 方法传递 certificate 参数。然而,这会造成无法正确接收消息。事实上,certificate 这个参数是不需要的,只需传递 Webhook 的 URL 即可,具体可参考「本文」。

updater.start_webhook(listen='127.0.0.1', port=8081, url_path=TOKEN)
updater.bot.set_webhook('https://tg.<domain>.com/' + TOKEN)
  • 最后是一个完整的基于 Webhook 模式部署的 Telegram Bot 的「Python 示例代码」,其中使用了 DEBUG 级的日志信息输出。
telegram.ext.updater - DEBUG - dispatcher - started
telegram.ext.updater - DEBUG - updater - started
telegram.ext.updater - DEBUG - Updater thread started (webhook)
telegram.utils.webhookhandler - DEBUG - Webhook Server started.
telegram.bot - DEBUG - Entering: set_webhook
telegram.ext.dispatcher - DEBUG - Dispatcher started
telegram.vendor.ptb_urllib3.urllib3.connectionpool - DEBUG - https://api.telegram.org:443 "POST /<TOKEN>/setWebhook HTTP/1.1" 200 64
telegram.bot - DEBUG - True
telegram.bot - DEBUG - Exiting: set_webhook

Polling v.s. Webhook

  • 轮询模式中,用户需要频繁向端点(endpoint)请求新的事件,显然其中很多请求是会被浪费的。
  • Webhook 模式(服务端/服务端架构)中,只需向端点提供一个 回调 URL当且仅当端点应用中有新的事件发生时,Webhook 会直接向这个特定的 URL 中 POST 数据。这意味着你可以立即获取到最新的数据,且所有的通信都是 100% 有效的。因此,Webhook 也称为网络回调(web callback)或者 HTTP Push API。

任何 API 设计的目标都是为了高效地传递和共享数据,因此它必须提供一种检测事件变化的机制。下图来自「Webhooks v.s. Polling | You're Better Than This」,最为直观地给出了两种模式的区别。

Polling v.s. Webhook

发表新评论

沪ICP备17018959号-3