# 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 ![](https://i.imgur.com/MF4yDXi.png) 以適當的參數取代下面程式片段的`...`,如[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)",你可能想現在就做。