Serverless 简明教程
Serverless - Telegram Echo Bot
这是官方 Serverless 项目列表中提供的另一个有趣的项目。我们基本上在 Telegram 上创建一个新机器人,然后使用 set_webhook 方法将其连接到我们的 Lambda 函数,然后在 Lambda 函数内编写将使机器人回显其收到的任何消息的代码。
This is another interesting project available within the official serverless projects list. We basically create a new bot on Telegram, then use the set_webhook method to connect it to our lambda function, and within the lambda function, write the code that will make the bot echo back whatever message it receives.
Prerequisites
你需要在你的手机或桌面上安装 Telegram App。下载选项可在 here 中找到。Telegram 是一款消息传递应用,有点类似于 WhatsApp 或 Messenger。安装该应用后,你需要在应用内创建一个新机器人。为此,单击“New Message”(圆形铅笔图标,在右下角),然后搜索 BotFather。单击经过验证的账号。
You will need the Telegram App installed on your mobile phone or desktop. The download options can be found here. Telegram is a messaging app,kind of similar to WhatsApp or Messenger. Once the app is installed, you will need to create a new bot within the app. For that, click on the 'New Message' icon (the round pencil icon atthe bottom right), and search for BotFather. Click on the verified account.

一旦你开始与 BotFather 聊天,创建一个新机器人就是很直观的。你发送 \newbot 命令,输入机器人的名称和用户名,然后你会得到你需要记下的访问令牌。
Once you begin your chat with BotFather, creating a new bot is quite self-explanatory. You send the \newbot command, enter a name and username for the bot, and you get an access token, which you need to note down.

Code Walkthrough
The code can be found on GitHub - https://github.com/serverless/examples/tree/master/aws-python-telegram-bot
我们将查看项目结构,然后执行 serverless.yml 文件和 handler.py 文件的演练。
We will have a look at the project structure and then perform the walkthrough of the serverless.yml file and the handler.py file.
Project Structure
我们可以看到,此项目具有列在 requirements.py 中的一个外部依赖项(Python Telegram Bot 库) −
We can see that this project has an external dependency (the python telegram bot library), listed in requirements.py −
python-telegram-bot==8.1.1
package.json 和 serverless.yml 都显示,已使用 serverless-python-requirements 插件来捆绑 Python 要求(在此情况下为 Telegram Bot 库)。因此,README.md 文件还建议你执行 npm install 以安装必需的插件。我个人建议你删除 package.json,并使用 sls plugin install -n serverless-python-requirements 安装 serverless-python-requirements。这将自动创建 package.json 文件。这也将确保你安装最新版本的 serverless-python-requirements。通过运行 npm install ,你将安装现有 package.json 中提到的版本,该版本可能已过时。
The package.json and the serverless.yml both show that the serverless-python-requirements plugin has been used for bundling the python requirements (the telegram bot library in this case).Therefore, the README.md file also suggests that you perform npm install to install the necessary plugins. I would personally recommend that you delete package.json, and install serverless-python-requirements using sls plugin install -n serverless-python-requirements. This will create the package.json file automatically. This willalso ensure that you install the latest version of serverless-python-requirements. By running npm install, you will be installing the version mentioned in the existing package.jsonwhich may be outdated.
如果你阅读了 README.md 文件,你将发现一个被引用的文件实际上不存在于项目中:serverless.env.yml。系统已要求你创建此文件,并在文件中输入你的 TELEGRAM_TOKEN。这是出于安全原因。TELEGRAM_TOKEN 应该是机密的,你不希望公开分享它。因此,此项目的创建者未在 GitHub 上添加 serverless.env.yml 文件。但你需要在你的本地计算机上创建它。
If you read the README.md file, you will find that one file which is referenced, is not actually present in the project − serverless.env.yml. You have been asked to create this file and enter your TELEGRAM_TOKEN in this file. This has been done for security reasons. The TELEGRAM_TOKEN is supposed to be confidential, and you don’t want to share it publicly. Therefore, the creator of this project has not added the serverless.env.yml file on GitHub. But you will need to create it on your local machine.
serverless.yml Walkthrough
serverless.yml 文件以服务定义开头。
The serverless.yml file begins with the definition of the service.
service: serverless-telegram-bot
接下来,定义提供程序。在此处,再次设置了环境变量。此变量的值(TELEGRAM_TOKEN)从你应该在本地创建的 serverless.env.yml 文件中获取。在此处,再次使用 $ 表示变量。
Next, the provider is defined. Here again, an environment variable has been set. The value for this variable (TELEGRAM_TOKEN) is fetched from the serverless.env.yml file which you are supposed to create locally. Here again, we’ve used $ to signify variables.
provider:
name: aws
runtime: python3.6
profile: ckl
environment:
TELEGRAM_TOKEN: ${file(./serverless.env.yml):TELEGRAM_TOKEN, ''}
函数块非常简单。定义了两个函数,它们都是 HTTP 触发的。不过,HTTP 事件参数在此处定义在一行中。
The functions block is quite straightforward. Two functions are defined, both HTTP triggered. The http event parameters, however, are defined in a single line here.
- http:
path: /set_webhook
method: post
代替一行执行使用 - http − POST /set_webhook
Instead of the single line execution used is - http − POST /set_webhook
此外,请注意 webhook 和 set_webhook 函数都位于同一处理程序文件中。
Also, note that both the webhook and set_webhook functions lie within the same handler file.
functions:
webhook:
handler: handler.webhook
events:
- http: POST /
set_webhook:
handler: handler.set_webhook
events:
- http: POST /set_webhook
最后,定义 serverless-python-requirements 插件。
Finally, the serverless-python-requirements plugin is defined.
plugins:
- serverless-python-requirements
Walkthrough of handler.py
我们从几个导入语句开始
We being with a couple of import statements
import json
import telegram
import os
import logging
接下来,定义一个 log 对象,它在本质上帮助我们输入 log 语句。请注意,这对于 python 运行时函数不是必需的。简单的 print 语句也会被记录下来。
Next, a logger object is defined, which basically helps us enter log statements. Please note that this is not required for python runtime functions. Simple print statements also get logged.
唯一的区别是,日志的输出包括 log 级别、时间戳和请求 ID。你可以详细了解日志记录库 here 。
The only difference is that the output from logger includes log level, timestamp, and request ID. You can read more about the logging library here.
# Logging is cool!
logger = logging.getLogger()
if logger.handlers:
for handler in logger.handlers:
logger.removeHandler(handler)
logging.basicConfig(level=logging.INFO)
接下来,为 OK_RESPONSE 和 ERROR_RESPONSE 定义 JSON。它们充当函数的返回值。
Next, the JSONs for OK_RESPONSE and ERROR_RESPONSE are defined. These serve as return values of the functions.
OK_RESPONSE = {
'statusCode': 200,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps('ok')
}
ERROR_RESPONSE = {
'statusCode': 400,
'body': json.dumps('Oops, something went wrong!')
}
接下来,定义一个 helper 函数,供两个 API 函数使用。该函数使用在 serverless.yml 中以环境变量提供 Token,返回一个 bot 实例。
Next, a helper function, that is used by both the API functions is defined. This function returns a bot instance, using the Token provided as an environment variable in serverless.yml.
def configure_telegram():
"""
Conimages the bot with a Telegram Token.
Returns a bot instance.
"""
TELEGRAM_TOKEN = os.environ.get('TELEGRAM_TOKEN')
if not TELEGRAM_TOKEN:
logger.error('The TELEGRAM_TOKEN must be set')
raise NotImplementedError
return telegram.Bot(TELEGRAM_TOKEN)
接下来,为这两个 API 定义处理函数。我们先来看一下 set_webhook 函数。在此处,bot 实例是从我们之前看到的 configure_telegram 函数获得的。下一步,从标头中提取主机字段,并从传入事件的 requestContext 块中提取 stage 字段。使用这些,构造 webhook 的 URL。最后,使用 bot.set_webhook(url) 函数将其应用于 bot。如果 webhook 设置正确,则设置 OK_RESPONSE;否则,设置 ERROR_RESPONSE。请注意,必须使用 POSTMAN 之类的工具手动触发一次此 set_webhook API。
Next, the handler functions for the two APIs are defined. Let’s look at the set_webhook function first. Over here, the bot instance is obtained from the configure_telegram function that we saw earlier. Next, the host field is extracted from the headers, and the stage field is extracted from the requestContext block of the incoming event. Using these, two fields, the URL of the webhook is constructed. Finally, it is applied to the bot using the bot.set_webhook(url) function. If the webhook is set correctly, the OK_RESPONSE is set, else the ERROR_RESPONSE is set. Please note that this set_webhook API has to be triggered manually once, using a tool like POSTMAN.
def set_webhook(event, context):
"""
Sets the Telegram bot webhook.
"""
logger.info('Event: {}'.format(event))
bot = configure_telegram()
url = 'https://{}/{}/'.format(
event.get('headers').get('Host'),
event.get('requestContext').get('stage'),
)
webhook = bot.set_webhook(url)
if webhook:
return OK_RESPONSE
return ERROR_RESPONSE
让我们了解一下 set_webhook 函数如何获取 webhook 的正确 URL。注意,set_webhook 函数和 webhook 函数在其路径中仅相差 '/set_webhook'。它们共享相同的主机和阶段。因此,我们可以使用在 set_webhook 函数的事件中接收到的主机和 dev 来派生 webhook 函数的端点。如果你的端点是 'https://abcdefghijk.execute-api.us-east-1.amazonaws.com/dev',则主机将是 'https://abcdefghijk.execute-api.us-east-1.amazonaws.com',阶段将是 'dev'。set_webhook 函数通过 'https://abcdefghijk.execute-api.us-east-1.amazonaws.com/dev/set_webhook' 触发,而 webhook 函数通过 'https://abcdefghijk.execute-api.us-east-1.amazonaws.com/dev' 触发。因此,set_webhook 事件中的参数可以帮助我们构造 webhook 函数的端点 URL。
Let us understand how the set_webhook function is able to get the correct URL for the webhook. Note that the set_webhook and the webhook functions only have a difference of '/set_webhook' in their path. They share the same host and stage. Therefore, we can derive the endpoint of the webhook function using the host and dev received in the set_webhook function’s event. If your endpoint is 'https://abcdefghijk.execute-api.us-east-1.amazonaws.com/dev', then the host will be 'https://abcdefghijk.execute-api.us-east-1.amazonaws.com' and the stage will be 'dev'. The set_webhook function is triggered by 'https://abcdefghijk.execute-api.us-east-1.amazonaws.com/dev/set_webhook' and the webhook function is triggered by 'https://abcdefghijk.execute-api.us-east-1.amazonaws.com/dev'. Thus, the parameters in set_webhook’s event can help us construct the endpoint URL of the webhook function.
最后,我们来看一下 webhook 函数。它相当简单。它从 configure_telegram helper 函数接收 bot 实例。然后,它检查事件。如果它是一个 POST 事件并包含文本,那么它将从正文中提取聊天 ID 和消息。如果文本是 '/start',表示对话开始,它将使用 bot.sendMessage(chat_id=chat_id, text=text) 命令以标准问候进行回复。否则,它将以接收到的相同文本进行回复。
Finally, let’s look at the webhook function. It is pretty straightforward. It receives the bot instance from the configure_telegram helper function. It then checks the event. If it is a POST event and contains a body, then it extracts the chat ID and message from the body. If the text is '/start', indicating the start of conversation, it replies back with a standard greeting using the bot.sendMessage(chat_id=chat_id, text=text) command. Else, it replies back with the same text that it received.
def webhook(event, context):
"""
Runs the Telegram webhook.
"""
bot = configure_telegram()
logger.info('Event: {}'.format(event))
if event.get('httpMethod') == 'POST' and event.get('body'):
logger.info('Message received')
update = telegram.Update.de_json(json.loads(event.get('body')), bot)
chat_id = update.message.chat.id
text = update.message.text
if text == '/start':
text = """Hello, human! I am an echo bot, built with Python and the Serverless Framework.
You can take a look at my source code here: https://github.com/jonatasbaldin/serverless-telegram-bot.
If you have any issues, please drop a tweet to my creator: https://twitter.com/jonatsbaldin. Happy botting!"""
bot.sendMessage(chat_id=chat_id, text=text)
logger.info('Message sent')
return OK_RESPONSE
return ERROR_RESPONSE
通过 POSTMAN 等工具触发 set_webhook 函数后,你可以打开 Telegram 中的 bot 并与它聊天。它将按预期的方式回显消息。
Once you’ve triggered the set_webhook function through a tool like POSTMAN, you can open your bot on Telegram and have a chat with it. It will echo back messages as expected.

恭喜你拥有了第一个 Telegram bot!
Congratulations on your first Telegram bot!