Introduction to Python Applications 2023 - Python Telegram Bot
===
###### tags: `Python` `Python and Its Application 2023` `telegram`
## 簡介
對 Telegram Bot 的操作可以使用 HTTP 方式直接操作 [Telegram Bot API](https://core.telegram.org/bots/api),但直接操作步驟繁瑣,因此有許多 [Library](https://core.telegram.org/bots/samples) 針對 API 進行包裝,讓各個程式語言使用起來更簡單,這邊我們主要介紹 `python-telegram-bot` 套件。
## 申請 Bot
找 [@BotFather](https://t.me/botfather) 申請一個 Bot。
1. 首先輸入 /newbot 申請 Bot。

2. 輸入 Bot 名稱。

3. 輸入 Bot 的帳號,名稱一定要以 `bot` 結尾。

4. 最後會取得 Bot 的 ID 以及 API 的 Token,以範例來說至 https://t.me/yuching_test_bot 即可加 Bot 好友,1780604788:AAElXw3Kv0mATlJCtmu3_N3-jn9tYDrH0pQ 為 API 的 Token,Token 為往後對 API 操作驗證的 Key,請妥善保存。
## 安裝 python-telegram-bot
使用 pip 進行安裝
```
pip3 install python-telegram-bot
```
macOS 使用者會有 CERTIFICATE_VERIFY_FAILED 錯誤的問題,須執行:
```
/Applications/Python\ 3.10/Install\ Certificates.command
```
## Hello world
```python=
from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters
# Define a few command handlers. These usually take the two arguments update and
# context.
async def start(update, context):
"""Send a message when the command /start is issued."""
user = update.effective_user
await update.message.reply_text(f"Hi {user.name}!")
async def echo(update, context):
"""Echo the user message."""
await update.message.reply_text(update.message.text)
def main():
"""Start the bot."""
# Create the Application and pass it your bot's token.
application = Application.builder().token("TOKEN").build()
# on different commands - answer in Telegram
application.add_handler(CommandHandler("start", start))
# on non command i.e message - echo the message on Telegram
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
# Run the bot until the user presses Ctrl-C
application.run_polling()
if __name__ == "__main__":
main()
```
### 建立 [Application](https://docs.python-telegram-bot.org/en/latest/telegram.ext.application.html#telegram.ext.Application.builder)
token 參數需要給他剛剛申請的 Token,建立完後會產生 application。
```python=
application = Application.builder().token("TOKEN").build()
```
### 建立及註冊 Handler
這邊建立兩個 Function 分別對應不同 Handler 回應,使用 [update.message.reply_text(text)](https://docs.python-telegram-bot.org/en/latest/telegram.message.html#telegram.Message.reply_text)回應使用者訊息,`update.effective_user` 來取得使用者資訊
```python=
async def start(update, context):
"""Send a message when the command /start is issued."""
user = update.effective_user
await update.message.reply_text(f"Hi {user.name}!")
async def echo(update, context):
"""Echo the user message."""
await update.message.reply_text(update.message.text)
```
接著分別建立 handler 並註冊進去 application:
```python=
# on different commands - answer in Telegram
application.add_handler(CommandHandler("start", start))
# on non command i.e message - echo the message on Telegram
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
```
這邊示範兩種 Handler,分別為 CommandHalder() 與 MessageHandler():
- [CommandHalder](https://docs.python-telegram-bot.org/en/latest/telegram.ext.commandhandler.html)
```python=
start_handler = CommandHandler("start", start)
```
輸入 Command 時執行對應動作,例如這邊 command 參數為 'start',callback 參數為 start,因此當對 Bot 輸入 /start 時會執行 start() Function
- [MessageHandler](https://python-telegram-bot.readthedocs.io/en/latest/telegram.ext.messagehandler.html)
```
repeat_handler = MessageHandler(filters.TEXT & ~filters.COMMAND, echo)
```
輸入一般訊息時回應的動作,例如這邊 filters 參數為 ilters.TEXT & ~filters.COMMAND 條件為是文字且不是 command,callback 參數為 echo,因此當對 Bot 輸入不為 command 的訊息時會執行 echo() Function。
### [start_polling()](https://python-telegram-bot.readthedocs.io/en/latest/telegram.ext.updater.html#telegram.ext.Updater.start_polling)
```python=
application.run_polling()
```
### 執行結果

## 可獲取的訊息資訊
我們可以從 Application 中取得使用者傳送過來 Message 的基本資訊,可參考 [Message Class](https://python-telegram-bot.readthedocs.io/en/stable/telegram.message.html),其中常用的有:
- message_id
- from_user ([User Clas](https://docs.python-telegram-bot.org/en/stable/telegram.user.html#telegram.User))
- id
- first_name
- last_name
- full_name
- username
- date
- chat ([Chat Class](https://python-telegram-bot.readthedocs.io/en/stable/telegram.chat.html#telegram.Chat))
- id
- type
- title
- text
例如我們要拿取使用者的全名,我們可以使用 update.message.from_user.full_name 以及對話的文字內容我們可以使用 update.message.text。
## 建立按鈕
前面介紹了收 Command 和過濾特定訊息的的互動方式,接著我們來看互動按鈕的部分。
在 bot.send_message() 設定 reply_markup 為 [ReplayMarkup Class](https://python-telegram-bot.readthedocs.io/en/stable/telegram.replymarkup.html#telegram.ReplyMarkup),裡面再依 row 和 column 分別放置 (List[List[InlineKeyboardButton]]) 的 [InlineKeyboardButton](https://python-telegram-bot.readthedocs.io/en/stable/telegram.inlinekeyboardmarkup.html#telegram.InlineKeyboardMarkup.inline_keyboard) 按鈕,例如我們新增兩個簡單的連結按鈕:
```python=
from telegram import InlineKeyboardMarkup, InlineKeyboardButton
from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters
async def start(update, context):
await update.message.reply_text('參考資料', reply_markup=InlineKeyboardMarkup([[
InlineKeyboardButton(
'課程 E3', url='https://e3.nycu.edu.tw/course/view.php?id=23863'),
InlineKeyboardButton(
'課程 Github', url='https://github.com/mzshieh/pa21spring')
], [InlineKeyboardButton(
'Python Telegram Bot 文件', url='https://python-telegram-bot.readthedocs.io/en/stable/index.html')]])
)
def main():
"""Start the bot."""
# Create the Application and pass it your bot's token.
application = Application.builder().token("TOKEN").build()
# on different commands - answer in Telegram
application.add_handler(CommandHandler("start", start))
# Run the bot until the user presses Ctrl-C
application.run_polling()
if __name__ == "__main__":
main()
```
在 InlineKeyboardMarkup() 內放的結構為 [[課程 E3, 課程 Github], Python Telegram Bot 文件] 外層括號代表每個 row,內層括號代表每個 column,因此排列方式如下圖:

## 傳送資料
上述示範了如何產生互動按鈕以及擺放位置,接下來我們來接收按鈕傳回的資料,以不同資料觸發不同 Function,並示範三種回應方式。
```python=
from telegram import InlineKeyboardMarkup, InlineKeyboardButton
from telegram.ext import Application, CommandHandler, CommandHandler, CallbackQueryHandler
# Define a few command handlers. These usually take the two arguments update and
# context.
async def func(update, context):
if update.callback_query.data == 'a':
await context.bot.answer_callback_query(update.callback_query.id, '你按的是功能 A')
elif update.callback_query.data == 'b':
await context.bot.edit_message_text('你按的是功能 B', chat_id=update.callback_query.message.chat_id,
message_id=update.callback_query.message.message_id)
else:
await context.bot.send_message(chat_id=update.effective_chat.id, text="你按的是功能 C")
async def start(update, context):
await update.message.reply_text('參考資料', reply_markup=InlineKeyboardMarkup([[
InlineKeyboardButton('功能 A', callback_data='a'),
InlineKeyboardButton('功能 B', callback_data='b')
], [InlineKeyboardButton('功能 C', callback_data='c')]])
)
def main():
"""Start the bot."""
# Create the Application and pass it your bot's token.
application = Application.builder().token("TOKEN").build()
# on different commands - answer in Telegram
application.add_handler(CommandHandler("start", start))
application.add_handler(CallbackQueryHandler(func))
# Run the bot until the user presses Ctrl-C
application.run_polling()
if __name__ == "__main__":
main()
```
### [CallbackQueryHandler](https://python-telegram-bot.readthedocs.io/en/stable/telegram.ext.callbackqueryhandler.html)
我們使用 CallbackQueryHandler 接取回傳資料,可以從 update.callback_query.data 收取我們從按鈕傳送的 callback_data 資料。
```python=
application.add_handler(CallbackQueryHandler(func))
```
### 回應 CallbackQuery
接著示範三種回應方式,分別對應到功能 A、B、C 按鈕上,分別是
#### [bot.answer_callback_query](https://python-telegram-bot.readthedocs.io/en/stable/telegram.bot.html#telegram.Bot.answer_callback_query)
```python=
context.bot.answer_callback_query(
update.callback_query.id, '你按的是功能 A')
```
此種方式為在中間顯示。

#### [bot.edit_message_text](https://python-telegram-bot.readthedocs.io/en/stable/telegram.bot.html#telegram.Bot.edit_message_text)
```python=
context.bot.edit_message_text('你按的是功能 B',
chat_id=update.callback_query.message.chat_id,
message_id=update.callback_query.message.message_id)
```
此種方式為修改為新訊息。

#### [bot.send_message](https://python-telegram-bot.readthedocs.io/en/stable/telegram.bot.html#telegram.Bot.send_message)
```python=
context.bot.send_message(chat_id=update.effective_chat.id, text="你按的是功能 C")
```
此種方式上面有出現過,正常回應訊息。

#### 其他還有 [sendPhoto](https://python-telegram-bot.readthedocs.io/en/stable/telegram.bot.html#telegram.Bot.send_photo) 、 [sendAudio](https://python-telegram-bot.readthedocs.io/en/stable/telegram.bot.html#telegram.Bot.send_audio) 等等回應方式,詳細資訊請參考 [Document](https://python-telegram-bot.readthedocs.io/en/stable/telegram.callbackquery.html)
## 參考資料
- [Telegram Bot API](https://core.telegram.org/bots/api)