# Code snippets(翻譯)
###### tags: `telegram` `bot`
>[name=shaoe.chen]
:::danger
[官方文件](https://github.com/python-telegram-bot/python-telegram-bot/wiki/Code-snippets)
:::
可以單獨閱讀這個頁面以查找你現在需要的程式片段。
這頁面也是Introduction to the API的後續內容。如果你從那來,那你可以保持你的command line開啟,然後試一下其中的一些片段。
[TOC]
## Pure API
### Fetch updates
要取得發送到機器人的訊息,你可以使用API方法`getUpdates`。
**注意:** 如果你正使用`telegram.ext`子模組開發你的機器人,你並不需要使用`get_updates`,因為`telegram.ext.Updater`會為你取得所有更新。更多請參閱[這裡](https://github.com/python-telegram-bot/python-telegram-bot/wiki/Extensions-%E2%80%93-Your-first-Bot)。
```python
updates = bot.get_updates()
print([u.message.text for u in updates])
```
### Fetch images sent to your Bot
```python
updates = bot.get_updates()
print([u.message.photo for u in updates if u.message.photo])
```
### Reply to messages
你始終需要`chat_id`
```python
chat_id = bot.get_updates()[-1].message.chat_id
```
## General code snippets
這些片段通常適用於兩種取得更新的方法。如果你正在使用`telegram.ext`,那你可以在處理程序callback中以`update.message.chat_id`取得`chat_id`。
**注意:** 一般來說,你可以透過將用戶id做為`chat_id`來向用戶發送訊息。如果機器人與用戶聊天,那它將發送訊息到這個聊天室。
### Post a text message
[TELEGRAM](https://core.telegram.org/bots/api#sendmessage)
```python
bot.send_message(chat_id=chat_id, text="I'm sorry Dave I'm afraid I can't do that.")
```
**注意:** 方法`send_message`(與類別`Bot`的方法`send_*`一樣)回傳`Message`類別的實例,因此後續的程式碼可以使用它。
### Reply to a message
這是一個使用`bot.send_message`的快捷方式。更多請參閱[文件](https://python-telegram-bot.readthedocs.io/en/latest/telegram.html#telegram.Message.reply_text)。
```python
update.message.reply_text("I'm sorry Dave I'm afraid I can't do that.")
```
這個方法可以用於照片、音頻的回覆,而且整個套件都存在類似的快捷方式。
### Send a chat action
[TELEGRAM](https://core.telegram.org/bots/api#sendchataction)使用這個方法告訴用戶端,機器人端正發生那些情況:
```python
bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.TYPING)
```
或者,如果你有多個命令,而且不想要在所有命令中重覆上面的程式碼片段,你可以複製下面的程式片段,然後以`@send_typing_action`裝飾callback function:
```python
from functools import wraps
def send_typing_action(func):
"""Sends typing action while processing func command."""
@wraps(func)
def command_func(update, context, *args, **kwargs):
context.bot.send_chat_action(chat_id=update.effective_message.chat_id, action=ChatAction.TYPING)
return func(update, context, *args, **kwargs)
return command_func
@send_typing_action
def my_handler(update, context):
pass # Will send 'typing' action while processing the request.
```
### Requesting location and contact from user
```python
location_keyboard = telegram.KeyboardButton(text="send_location", request_location=True)
contact_keyboard = telegram.KeyboardButton(text="send_contact", request_contact=True)
custom_keyboard = [[ location_keyboard, contact_keyboard ]]
reply_markup = telegram.ReplyKeyboardMarkup(custom_keyboard)
bot.send_message(chat_id=chat_id,
... text="Would you mind sharing your location and contact with me?",
... reply_markup=reply_markup)
```
### Message Formatting (bold, italic, code, ...)
#### Post a text message with Markdown formatting
[TELEGRAM](https://core.telegram.org/bots/api#sendmessage)
```python
bot.send_message(chat_id=chat_id,
text="*bold* _italic_ `fixed width font` [link](http://google.com).",
parse_mode=telegram.ParseMode.MARKDOWN)
```
#### Post a text message with HTML formatting
[TELEGRAM](https://core.telegram.org/bots/api#sendmessage)
```python
bot.send_message(chat_id=chat_id,
text='<b>bold</b> <i>italic</i> <a href="http://google.com">link</a>.',
parse_mode=telegram.ParseMode.HTML)
```
#### Message entities
[TELEGRAM](https://core.telegram.org/bots/api#messageentity)
要使用MessageEntity,使用`parse_entities`從一個Message物件提取實體與它們各自的文本。
**注意:** 應該總是使用這個方法,而不是使用屬性`entities`,因為它基於UTF-16代碼點從訊息文本中計算正確的子字串 - 也就是說,即使處理像Emojis之類的奇怪字符,也可以正確提取字符串(?)。
```python
entities = message.parse_entities()
```
還有更多的API方法,讀取完整的API文件,可以參考[Telegram Bot API](https://core.telegram.org/bots/api)或[library documentation of telegram.Bot](https://python-telegram-bot.readthedocs.io/en/latest/telegram.bot.html)
### Working with files and media
#### Post an image file from disk
[TELEGRAM](https://core.telegram.org/bots/api#sendphoto)
```python
bot.send_photo(chat_id=chat_id, photo=open('tests/test.png', 'rb'))
```
#### Post a voice file from disk
[TELEGRAM](https://core.telegram.org/bots/api#sendvoice)
```python
bot.send_voice(chat_id=chat_id, voice=open('tests/telegram.ogg', 'rb'))
```
#### Post a photo from a URL
[TELEGRAM](https://core.telegram.org/bots/api#sendphoto)
```python
bot.send_animation(chat_id, animation, duration=None, width=None, height=None, thumb=None, caption=None, parse_mode=None, disable_notification=False, reply_to_message_id=None, reply_markup=None, timeout=20, **kwargs)
```
See the [online documentation](https://python-telegram-bot.readthedocs.io/en/latest/telegram.bot.html#telegram.Bot.send_animation)
#### Post an audio from disk
[TELEGRAM](https://core.telegram.org/bots/api#sendaudio)
```python
bot.send_audio(chat_id=chat_id, audio=open('tests/test.mp3', 'rb'))
```
#### Post a file from disk
[TELEGRAM](https://core.telegram.org/bots/api#senddocument)
```python
bot.send_document(chat_id=chat_id, document=open('tests/test.zip', 'rb'))
```
#### Post an image from memory
這個範例中,`image`是一個PIL(or Pillow)`Image`物件,但它對所有媒體類型的工作原理都一樣。
```python
from io import BytesIO
bio = BytesIO()
bio.name = 'image.jpeg'
image.save(bio, 'JPEG')
bio.seek(0)
bot.send_photo(chat_id, photo=bio)
```
#### Get image with dimensions closest to a desired size
其中`photos`是物件`PhotoSize`的列表(list),且`desired_size`是包含所需大小的tuple。
```python
def get_closest(photos, desired_size):
def diff(p): return p.width - desired_size[0], p.height - desired_size[1]
def norm(t): return abs(t[0] + t[1] * 1j)
return min(photos, key=lambda p: norm(diff(p)))
```
#### Download a file
[TELEGRAM](e.telegram.org/bots/api#getfile)
```python
file_id = message.voice.file_id
newFile = bot.get_file(file_id)
newFile.download('voice.ogg')
```
**注意:** 要下載照片,請記得,`update.message.photo`是一個不同尺寸照片的陣列。使用`update.message.photo[-1]`來得到最大尺寸的照片。
### Keyboard Menus
#### Custom Keyboards
[TELEGRAM](https://core.telegram.org/bots#keyboards)
```python
custom_keyboard = [['top-left', 'top-right'],
['bottom-left', 'bottom-right']]
reply_markup = telegram.ReplyKeyboardMarkup(custom_keyboard)
bot.send_message(chat_id=chat_id,
text="Custom Keyboard Test",
reply_markup=reply_markup)
```
See also: [Build a menu with Buttons](https://github.com/python-telegram-bot/python-telegram-bot/wiki/Code-snippets#build-a-menu-with-buttons)
#### Remove a custom keyboard
```python
reply_markup = telegram.ReplyKeyboardRemove()
bot.send_message(chat_id=chat_id, text="I'm back.", reply_markup=reply_markup)
```
### Other useful stuff
#### Generate flag emojis from country codes
任何國家的Unicode標誌表情符號(emoji)都可以根據國家/地區2字母國家代碼進行計算。下面程式碼片段只適用於Python 3。
```python
OFFSET = 127462 - ord('A')
def flag(code):
code = code.upper()
return chr(ord(code[0]) + OFFSET) + chr(ord(code[1]) + OFFSET)
>>> flag('de')
'🇩🇪'
>>> flag('us')
'🇺🇸'
>>> flag('ru')
'🇷🇺'
```
#### Get the add group message
```python
def add_group(update, context):
for member in update.message.new_chat_members:
update.message.reply_text("{username} add group".format(username=member.username))
add_group_handle = MessageHandler(Filters.status_update.new_chat_members, add_group)
dispatcher.add_handler(add_group_handle)
```
## Advanced snippets
### Restrict access to a handler (decorator)
這個裝飾器允許你限只有在指定在`LIST_OF_ADMINS`的`user_ids`才能訪問程序處理器。
```python
from functools import wraps
LIST_OF_ADMINS = [12345678, 87654321]
def restricted(func):
@wraps(func)
def wrapped(update, context, *args, **kwargs):
user_id = update.effective_user.id
if user_id not in LIST_OF_ADMINS:
print("Unauthorized access denied for {}.".format(user_id))
return
return func(update, context, *args, **kwargs)
return wrapped
```
#### Usage
增加裝飾器`@restricted `在你的程序處理器定義上:
```python
@restricted
def my_handler(update, context):
pass # only accessible if `user_id` is in `LIST_OF_ADMINS`.
```
### Send action while handling command (decorator)
這個參數化的裝飾器允許你根據你的機器人的不同的響應類型而發出不同的操作信息。這樣用戶會從機器人身上得到一個類似真實人類的反饋。
```python
from functools import wraps
def send_action(action):
"""Sends `action` while processing func command."""
def decorator(func):
@wraps(func)
def command_func(update, context, *args, **kwargs):
context.bot.send_chat_action(chat_id=update.effective_message.chat_id, action=action)
return func(update, context, *args, **kwargs)
return command_func
return decorator
```
#### Usage
你可以用`@send_action(ChatAction.<Action>)`直接裝飾程序處理器的callback,或者建立別名並用它們裝飾。
```python
send_typing_action = send_action(ChatAction.TYPING)
send_upload_video_action = send_action(ChatAction.UPLOAD_VIDEO)
send_upload_photo_action = send_action(ChatAction.UPLOAD_PHOTO)
```
下面的裝飾器與上面的別名用法是相同的
```python
@send_typing_action
def my_handler(update, context):
pass # user will see 'typing' while your bot is handling the request.
@send_action(ChatAction.TYPING)
def my_handler(update, context):
pass # user will see 'typing' while your bot is handling the request.
```
All possible actions are documented [here](https://core.telegram.org/bots/api#sendchataction).
### Build a menu with Buttons
通常,你會發現你需要一個動態內容的選單。使用下面方法`build_menu`建立一個擁有`n_cols`個column的`buttons`清單的佈局。
```python
def build_menu(buttons,
n_cols,
header_buttons=None,
footer_buttons=None):
menu = [buttons[i:i + n_cols] for i in range(0, len(buttons), n_cols)]
if header_buttons:
menu.insert(0, [header_buttons])
if footer_buttons:
menu.append([footer_buttons])
return menu
```
你可以以list格式分別於第一或最後一個row放置`header_buttons`或`footer_buttons`按鈕。
#### Usage

以適當的參數取代下面程式片段的`...`,如[InlineKeyboardButton](https://python-telegram-bot.readthedocs.io/en/latest/telegram.inlinekeyboardbutton.html)文件所述。如果你想使用`KeyboardButtons`,那就需要使用`ReplyKeyboardMarkup`,而不是`InlineKeyboardMarkup`。
```python
button_list = [
InlineKeyboardButton("col1", callback_data=...),
InlineKeyboardButton("col2", callback_data=...),
InlineKeyboardButton("row 2", callback_data=...)
]
reply_markup = InlineKeyboardMarkup(util.build_menu(button_list, n_cols=2))
bot.send_message(..., "A two-column menu", reply_markup=reply_markup)
```
或者如果你需要一個動態的版本,那就使用列表生成式動態從字中或列表(list)生成你的`button_list`:
```python
some_strings = ["col1", "col2", "row2"]
button_list = [[KeyboardButton(s)] for s in some_strings]
```
如果你放一個小助手的方法,像是`get_data_buttons`,處理動態資料,然後根據用戶輸入更新功能表。
要處理`callback_data`,你應該設置一個`CallbackQueryHandler`。
### Cached Telegram group administrator check
如果你想要限制部份機器人功能為管理者群組,那就必須測試用戶是否為相關群組中的管理員。然而,這需要一個額外的API的請求,這就是為什麼要緩存這信息一段時間才有意義的原因,特別是如果你的機器人非常忙碌。
這個程式碼片段需要[this timeout-based cache decorator](http://code.activestate.com/recipes/325905-memoize-decorator-with-timeout/#c1)。
將這個裝飾器保存在`mwt.py`文件,然後加入下面這一行import:
```python
from mwt import MWT
```
接著,加入裝飾器函數到你的腳本。你可以依需求調整timeout。
```python
@MWT(timeout=60*60)
def get_admin_ids(bot, chat_id):
"""Returns a list of admin IDs for a given chat. Results are cached for 1 hour."""
return [admin.user.id for admin in bot.get_chat_administrators(chat_id)]
```
你可以像這樣使用這個函數:
```python
if update.effective_user.id in get_admin_ids(context.bot, update.message.chat_id):
# admin only
```
**注意:** 這個程式片段不包括帶有`all_members_are_administrator`標誌的私人聊天與群組。記得確認處理好它們。
### Simple way of restarting the bot
下面的範例可以讓你透過處理程序重新啟動機器人。不用多說你也知道,你應該保護這個方法不會被未經授權的使用者訪問,這也是為什麼我們使用過濾器`Filters.user`的原因。如果你想要讓很多用戶可以訪問重新啟動的命令,你也可以傳遞一個用戶列表(list)。你也可以利用用戶IDs來過濾,這會安全一點,因為他們無法變更。更多資訊請參閱[文件](https://python-telegram-bot.readthedocs.io/en/latest/telegram.ext.filters.html#telegram.ext.filters.Filters.user)說明。
這個範例使用閉包,因此它會去訪問變數`updater`。或者你可以將它設置為全域變數。
```python
import os
import sys
from threading import Thread
# Other code
def main():
updater = Updater("TOKEN", context=True)
dp = updater.dispatcher
# Add your other handlers here...
def stop_and_restart():
"""Gracefully stop the Updater and replace the current process with a new one"""
updater.stop()
os.execl(sys.executable, sys.executable, *sys.argv)
def restart(update, context):
update.message.reply_text('Bot is restarting...')
Thread(target=stop_and_restart).start()
# ...or here...
dp.add_handler(CommandHandler('r', restart, filters=Filters.user(username='@jh0ker')))
# ...or here, depending on your preference :)
updater.start_polling()
updater.idle()
if __name__ == '__main__':
main()
```
### Store ConversationHandler States
Version 12 and up includes tools for [making your bot persistent](https://github.com/python-telegram-bot/python-telegram-bot/wiki/Making-your-bot-persistent).
### Save and load jobs using pickle
下面程式碼會定期或在機器人關閉的時候持久化(利用pickle)工作隊列內的工作,然後在重新啟動的時候再將工作寫回隊列。因為pickle並不支援執行緒基元(threading primitives),因此他們被轉換。
**注意:** 當在對工作進行持久化的時候,使用`job.job_queue`、`job.removed`、`job.schedule_remove`或`job.enabled`的非同步作業的競爭危害。
```python
import pickle
from threading import Event
from time import time
from datetime import timedelta
JOBS_PICKLE = 'job_tuples.pickle'
def load_jobs(jq):
now = time()
with open(JOBS_PICKLE, 'rb') as fp:
while True:
try:
next_t, job = pickle.load(fp)
except EOFError:
break # Loaded all job tuples
# Create threading primitives
enabled = job._enabled
removed = job._remove
job._enabled = Event()
job._remove = Event()
if enabled:
job._enabled.set()
if removed:
job._remove.set()
next_t -= now # Convert from absolute to relative time
jq._put(job, next_t)
def save_jobs(jq):
if jq:
job_tuples = jq._queue.queue
else:
job_tuples = []
with open(JOBS_PICKLE, 'wb') as fp:
for next_t, job in job_tuples:
# Back up objects
_job_queue = job._job_queue
_remove = job._remove
_enabled = job._enabled
# Replace un-pickleable threading primitives
job._job_queue = None # Will be reset in jq.put
job._remove = job.removed # Convert to boolean
job._enabled = job.enabled # Convert to boolean
# Pickle the job
pickle.dump((next_t, job), fp)
# Restore objects
job._job_queue = _job_queue
job._remove = _remove
job._enabled = _enabled
def save_jobs_job(context):
save_jobs(context.job_queue)
def main():
# updater = Updater(..)
job_queue = updater.job_queue
# Periodically save jobs
job_queue.run_repeating(save_jobs_job, timedelta(minutes=1))
try:
load_jobs(job_queue)
except FileNotFoundError:
# First run
pass
# updater.start_[polling|webhook]()
# updater.idle()
# Run again after bot has been properly shut down
save_jobs(job_queue)
if __name__ == '__main__':
main()
```
### An (good) error handler
下面範例是一個異常的程序處理片段範例。當異常發生的時候會通知用戶以及開發人員,包含追溯及其發生位置。程式碼內的註解試著解釋何時發生什麼事,為什麼發生,因此根據你的特殊需求對它進行編輯應該是輕而易舉的事。
```python
from telegram import ParseMode
from telegram.utils.helpers import mention_html
import sys
import traceback
# this is a general error handler function. If you need more information about specific type of update, add it to the
# payload in the respective if clause
def error(update, context):
# add all the dev user_ids in this list. You can also add ids of channels or groups.
devs = [208589966]
# we want to notify the user of this problem. This will always work, but not notify users if the update is an
# callback or inline query, or a poll update. In case you want this, keep in mind that sending the message
# could fail
if update.effective_message:
text = "Hey. I'm sorry to inform you that an error happened while I tried to handle your update. " \
"My developer(s) will be notified."
update.effective_message.reply_text(text)
# This traceback is created with accessing the traceback object from the sys.exc_info, which is returned as the
# third value of the returned tuple. Then we use the traceback.format_tb to get the traceback as a string, which
# for a weird reason separates the line breaks in a list, but keeps the linebreaks itself. So just joining an
# empty string works fine.
trace = "".join(traceback.format_tb(sys.exc_info()[2]))
# lets try to get as much information from the telegram update as possible
payload = ""
# normally, we always have an user. If not, its either a channel or a poll update.
if update.effective_user:
payload += f' with the user {mention_html(update.effective_user.id, update.effective_user.first_name)}'
# there are more situations when you don't get a chat
if update.effective_chat:
payload += f' within the chat <i>{update.effective_chat.title}</i>'
if update.effective_chat.username:
payload += f' (@{update.effective_chat.username})'
# but only one where you have an empty payload by now: A poll (buuuh)
if update.poll:
payload += f' with the poll id {update.poll.id}.'
# lets put this in a "well" formatted text
text = f"Hey.\n The error <code>{context.error}</code> happened{payload}. The full traceback:\n\n<code>{trace}" \
f"</code>"
# and send it to the dev(s)
for dev_id in devs:
context.bot.send_message(dev_id, text, parse_mode=ParseMode.HTML)
# we raise the error again, so the logger module catches it. If you don't use the logger module, use it.
raise
```
## What to read next?
如果你還沒有閱讀教程"[Extensions – Your first Bot](https://github.com/python-telegram-bot/python-telegram-bot/wiki/Extensions-%E2%80%93-Your-first-Bot)",你可能想現在就做。