在前一篇測試中我們使用 Telegram 的 HTTP API 傳送圖片到聊天室, 本篇改用 python-telegram-bot 套件的 Bot 物件來實作相同的功能. 本系列之前的文章參考 :
我的 Telegram Bot API 權杖與聊天室識別碼都存放在環境變數檔 .env 裡面, 然後用 dotenv 套件載入後用 os 模組讀取出來放在 token 與 chat_id 變數裡 :
>>> from dotenv import load_dotenv
>>> load_dotenv()
True
>>> import os
>>> token=os.environ.get('TELEGRAM_TOKEN')
>>> chat_id=os.environ.get('TELEGRAM_ID')
# load_secrets_py
from dotenv import load_dotenv
import os
load_dotenv()
token=os.environ.get('TELEGRAM_TOKEN')
chat_id=os.environ.get('TELEGRAM_ID')
參考 :
本篇直接從傳送圖片的函式入手, 關於 Bot 物件用法參考第二篇測試, .
7. 利用 Bot 物件傳送圖片 :
用 Bot 物件傳送圖片比起用 HTTP API 來說在程式碼結構上相對地簡單, 但是要注意的是, python-telegram-bot 套件從 v20 版開始採用 async (非同步) 架構, 所有 Bot 物件的方法都需要在 async 函式中使用 await 呼叫才行, 如果使用舊版的同步式呼叫函式, 雖然程式可以執行, 但實際上並不會傳送圖片.
(1). 傳送單一圖片 :
首先來改寫前一篇測試中傳送一個網路圖片的 telegram_web_image() 函式, 其 Bot 物件版的寫法如下 :
import asyncio
from telegram import Bot
async def send_photo_async(token, chat_id, photo_url, caption=None, parse_mode='Markdown'):
bot=Bot(token=token)
await bot.send_photo(
chat_id=chat_id,
photo=photo_url,
caption=caption,
parse_mode=parse_mode
)
print("Message sent successfully.")
return True
if __name__ == '__main__':
token='YOUR TOKEN'
chat_id='YOUR CHAT ID'
photo_url='URL OF PHOTO'
caption='THE CAPTION'
asyncio.run(send_photo_async(token, chat_id, photo_url, caption))
此函式定義的最前面要加上 async 表示為非同步, 呼叫 Bot 物件方法時前面要冠上 await 等待回應, 測試如下 :
>>> import asyncio
>>> from telegram import Bot
>>> async def send_photo_async(token, chat_id, photo_url, caption=None, parse_mode='Markdown'):
bot=Bot(token=token)
await bot.send_photo(
chat_id=chat_id,
photo=photo_url,
caption=caption,
parse_mode=parse_mode
)
print("Message sent successfully.")
return True
呼叫 asyncio.run() 來執行這個非同步函式 (已利用 dotenv 載入 token 與 chat_id) :
>>> asyncio.run(send_photo_async(token, chat_id, photo, caption))
Message(caption='Lena Söderberg', caption_entities=(MessageEntity(length=14, offset=0, type=<MessageEntityType.BOLD>),), channel_chat_created=False, chat=Chat(first_name='<我的名字>', id=<聊天室ID>, type=<ChatType.PRIVATE>, username='<我的 username>'), date=datetime.datetime(2025, 4, 11, 2, 20, 44, tzinfo=datetime.timezone.utc), delete_chat_photo=False, from_user=User(first_name='twstock', id=7938146214, is_bot=True, username='twstock168_bot'), group_chat_created=False, message_id=122, photo=(PhotoSize(file_id='AgACAgQAAxkDAANvZ_f6r1maJjJS-WDvkJQ-Py3JzbcAApKtMRtu9CVR606iM07xqbsBAAMCAANzAAM2BA', file_size=1964, file_unique_id='AQADkq0xG270JVF4', height=90, width=90), PhotoSize(file_id='AgACAgQAAxkDAANvZ_f6r1maJjJS-WDvkJQ-Py3JzbcAApKtMRtu9CVR606iM07xqbsBAAMCAANtAAM2BA', file_size=21503, file_unique_id='AQADkq0xG270JVFy', height=300, width=300)), supergroup_chat_created=False)
Message sent successfully.
True
這時檢查 Telegram App 果然有收到這張圖片 :
如果要傳送本機圖片, 需要將開啟的檔案用 telegram.InputFile 類別包起來再傳給 send_photo() 方法的 photo 參數, 程式碼如下 :
import asyncio
from telegram import Bot, InputFile
async def telegram_photo_async(token, chat_id, photo_path, caption=None, parse_mode='Markdown'):
bot=Bot(token=token)
try:
with open(photo_path, 'rb') as f:
photo=InputFile(f, filename=photo_path) # 用 InputFile 包裝本機檔案
await bot.send_photo(
chat_id=chat_id,
photo=photo,
caption=caption,
parse_mode=parse_mode
)
print("Message sent successfully.")
return True
except Exception as e:
print(f"Error sending photo: {e}")
return False
if __name__ == '__main__':
token='YOUR TOKEN'
chat_id='YOUR CHAT ID'
photo_path='PHOTO PATH' # 本機圖片檔案
caption='HE CAPTION'
asyncio.run(telegram_local_image_async(token, chat_id, photo_path, caption))
測試結果 :
>>> import asyncio
>>> from telegram import Bot, InputFile
>>> async def telegram_photo_async(token, chat_id, photo_path, caption=None, parse_mode='Markdown'):
bot=Bot(token=token)
try:
with open(photo_path, 'rb') as f:
photo=InputFile(f, filename=photo_path) # 用 InputFile 包裝本機檔案
await bot.send_photo(
chat_id=chat_id,
photo=photo,
caption=caption,
parse_mode=parse_mode
)
print("Message sent successfully.")
return True
except Exception as e:
print(f"Error sending photo: {e}")
return False
呼叫 asyncio.run() 來執行這個非同步函式 (已利用 dotenv 載入 token 與 chat_id) :
>>> asyncio.run(telegram_photo_async(token, chat_id, photo_path, caption))
Message(caption='可愛的貓咪 1', caption_entities=(MessageEntity(length=7, offset=0, type=<MessageEntityType.BOLD>),), channel_chat_created=False, chat=Chat(first_name='<我的名字>', id=<聊天室ID>, type=<ChatType.PRIVATE>, username='<我的 username>'), date=datetime.datetime(2025, 4, 11, 7, 24, 25, tzinfo=datetime.timezone.utc), delete_chat_photo=False, from_user=User(first_name='twstock', id=7938146214, is_bot=True, username='twstock168_bot'), group_chat_created=False, message_id=129, photo=(PhotoSize(file_id='AgACAgUAAxUHZ_eKuzGsDrmHcOe8XMClagTnAAHHAAKAwjEbBW64V_EWQQJaJNhaAQADAgADcwADNgQ', file_size=1394, file_unique_id='AQADgMIxGwVuuFd4', height=60, width=90), PhotoSize(file_id='AgACAgUAAxUHZ_eKuzGsDrmHcOe8XMClagTnAAHHAAKAwjEbBW64V_EWQQJaJNhaAQADAgADbQADNgQ', file_size=18479, file_unique_id='AQADgMIxGwVuuFdy', height=213, width=320), PhotoSize(file_id='AgACAgUAAxUHZ_eKuzGsDrmHcOe8XMClagTnAAHHAAKAwjEbBW64V_EWQQJaJNhaAQADAgADeAADNgQ', file_size=94085, file_unique_id='AQADgMIxGwVuuFd9', height=533, width=800), PhotoSize(file_id='AgACAgUAAxUHZ_eKuzGsDrmHcOe8XMClagTnAAHHAAKAwjEbBW64V_EWQQJaJNhaAQADAgADeQADNgQ', file_size=241570, file_unique_id='AQADgMIxGwVuuFd-', height=853, width=1280), PhotoSize(file_id='AgACAgUAAxUHZ_eKuzGsDrmHcOe8XMClagTnAAHHAAKAwjEbBW64V_EWQQJaJNhaAQADAgADdwADNgQ', file_size=540510, file_unique_id='AQADgMIxGwVuuFd8', height=1500, width=2250)), supergroup_chat_created=False)
App 顯示收到該圖片 :
(2). 傳送多張圖片 :
使用 Bot 物件傳送多張圖片時, 不論是網路圖片還是本機圖片, 都是使用 telegram. InputMediaPhoto 類別來包裝這些媒體, 差別只是本機圖片需要開啟檔案再打包, 網路圖片直接將網址打包而已.
傳送多張網路圖片的程式碼如下 :
#
import asyncio
from telegram import Bot, InputMediaPhoto
async def telegram_images_async(token, chat_id, photo_urls, captions=None):
bot=Bot(token=token)
try:
captions=captions or [] # 預設空串列表避免 NoneType 問題
media_group=[] # 儲存 InputMediaPhoto 物件的串列
for i, url in enumerate(photo_urls): # 取出索引與圖片網址
media=InputMediaPhoto( # 建立 InputMediaPhoto 物件
media=url,
caption=captions[i] if i < len(captions) else None,
parse_mode='Markdown'
)
media_group.append(media)
# 傳送 Media Group (多圖片)
await bot.send_media_group(chat_id=chat_id, media=media_group)
print("Media group sent successfully.")
return True
except Exception as e:
print(f"Error sending media group: {e}")
return False
if __name__ == '__main__':
token='YOUR TOKEN'
chat_id='YOUR CHAT ID'
photos=['https://yaohuang1966.github.io/images/cat.jpg',
'https://yaohuang1966.github.io/images/orchid.jpg']
captions=['可愛的小咪', '蘭花']
asyncio.run(telegram_images_async(token, chat_id, photos, captions))
此函式使用空串列 media_group 來儲存用 InputMediaPhoto 打包後之圖片媒體, 然後在呼叫 Bot 物件的 send_media_group() 方法時將此串列傳給 media 參數,
我準備了兩張圖片放在 GitHub :
測試如下 :
>>> import asyncio
>>> from telegram import Bot, InputMediaPhoto
>>> async def telegram_images_async(token, chat_id, photo_urls, captions=None):
bot=Bot(token=token)
try:
captions=captions or [] # 預設空串列表避免 NoneType 問題
media_group=[] # 儲存 InputMediaPhoto 物件的串列
for i, url in enumerate(photo_urls): # 取出索引與圖片網址
media=InputMediaPhoto( # 建立 InputMediaPhoto 物件
media=url, # 將網址指定給 media 參數
caption=captions[i] if i < len(captions) else None,
parse_mode='Markdown'
)
media_group.append(media)
# 傳送 Media Group (多圖片)
await bot.send_media_group(chat_id=chat_id, media=media_group)
print("Media group sent successfully.")
return True
except Exception as e:
print(f"Error sending media group: {e}")
return False
此處在呼叫 InputMediaPhoto() 建構式時將圖片網址只配給 media 字串建立 InputMediaPhoto 物件, 並將這打包後的圖片媒體物件放入 media_group 串列中儲存, 在呼叫 Bot 物件的 send_media_group() 方法時將媒體串列傳給 media 參數即可將這些圖片傳送出去.
定義圖片網址與標題串列 :
>>> photos=['https://yaohuang1966.github.io/images/cat.jpg',
'https://yaohuang1966.github.io/images/orchid.jpg']
>>> captions=['可愛的小咪', '蘭花']
呼叫 asyncio.run() 來執行這個非同步函式 (已利用 dotenv 載入 token 與 chat_id) :
>>> asyncio.run(telegram_images_async(token, chat_id, photos, captions))
(Message(caption='可愛的小咪', channel_chat_created=False, chat=Chat(first_name='<我的名字>', id=<聊天室 ID>, type=<ChatType.PRIVATE>, username='<我的 username>'), date=datetime.datetime(2025, 4, 11, 11, 30, 54, tzinfo=datetime.timezone.utc), delete_chat_photo=False, from_user=User(first_name='twstock', id=7938146214, is_bot=True, username='twstock168_bot'), group_chat_created=False, media_group_id='13954968437357869', message_id=137, photo=(PhotoSize(file_id='AgACAgQAAxUHZ_ZdsT7IDVJhtUCxnXNFh_cudJgAAhW4MRtSC7VT0zHgt9QirCMBAAMCAANzAAM2BA', file_size=1120, file_unique_id='AQADFbgxG1ILtVN4', height=57, width=90), PhotoSize(file_id='AgACAgQAAxUHZ_ZdsT7IDVJhtUCxnXNFh_cudJgAAhW4MRtSC7VT0zHgt9QirCMBAAMCAANtAAM2BA', file_size=10157, file_unique_id='AQADFbgxG1ILtVNy', height=204, width=320)), supergroup_chat_created=False), Message(caption='蘭花', channel_chat_created=False, chat=Chat(first_name='<我的名字>', id=<聊天室 ID>, type=<ChatType.PRIVATE>, username='<我的 username>'), date=datetime.datetime(2025, 4, 11, 11, 30, 54, tzinfo=datetime.timezone.utc), delete_chat_photo=False, from_user=User(first_name='twstock', id=7938146214, is_bot=True, username='twstock168_bot'), group_chat_created=False, media_group_id='13954968437357869', message_id=138, photo=(PhotoSize(file_id='AgACAgQAAxUHZ_Zgmtac22EDmiPs9y7K1GvmInkAAsS3MRs0HLRTjJ5CHHFKag8BAAMCAANzAAM2BA', file_size=1396, file_unique_id='AQADxLcxGzQctFN4', height=44, width=90), PhotoSize(file_id='AgACAgQAAxUHZ_Zgmtac22EDmiPs9y7K1GvmInkAAsS3MRs0HLRTjJ5CHHFKag8BAAMCAANtAAM2BA', file_size=19517, file_unique_id='AQADxLcxGzQctFNy', height=156, width=320), PhotoSize(file_id='AgACAgQAAxUHZ_Zgmtac22EDmiPs9y7K1GvmInkAAsS3MRs0HLRTjJ5CHHFKag8BAAMCAAN4AAM2BA', file_size=48815, file_unique_id='AQADxLcxGzQctFN9', height=292, width=600)), supergroup_chat_created=False))
檢視 App 果然收到這兩張圖片 :
傳送本機多個圖片同樣也是使用 InputMediaPhoto 類別來打包要上傳的圖檔, 與網路圖片不同之處是本機圖檔需先用 open() 開檔, 並將檔案參考傳給 InputMediaPhoto() 建構式的 media 參數建立物件並放進媒體串列中儲存, 然後在呼叫 Bot 物件的 send_media_group() 方法時將此 InputMediaPhoto 物件串列傳給 media 參數即可, 程式碼如下 :
# telegram_send_local_photos_async1.py
import asyncio
from telegram import Bot, InputMediaPhoto
async def telegram_images_async(token, chat_id, photo_paths, captions=None):
bot=Bot(token=token)
try:
captions=captions or [] # 預設空串列表避免 NoneType 問題
media_group=[] # 儲存 InputMediaPhoto 物件的串列
files=[] # 儲存檔案參考的串列
for i, path in enumerate(photo_paths): # 取出索引與圖片路徑
f=open(path, 'rb') # 開啟圖檔
files.append(f) # 暫存檔案參考以免被關閉
media=InputMediaPhoto( # 建立 InputMediaPhoto 物件
media=f, # 傳入檔案參考
caption=captions[i] if i < len(captions) else None,
parse_mode='Markdown'
)
media_group.append(media)
# 傳送 Media Group (多圖片)
await bot.send_media_group(chat_id=chat_id, media=media_group)
print("Media group sent successfully.")
return True
except Exception as e:
print(f"Error sending media group: {e}")
return False
finally: # 關閉所有檔案
for f in files:
f.close()
if __name__ == '__main__':
token='YOUR TOKEN'
chat_id='YOUR CHAT ID'
photos=['cat1.jpg', 'cat2.jpg', 'cat3.jpg']
captions=['貓咪 1', '貓咪 2', '貓咪 3']
asyncio.run(telegram_images_async(token, chat_id, photos, captions))
測試結果 :
>>> import asyncio
>>> from telegram import Bot, InputMediaPhoto
>>> async def telegram_images_async(token, chat_id, photo_paths, captions=None):
bot=Bot(token=token)
try:
captions=captions or [] # 預設空串列表避免 NoneType 問題
media_group=[] # 儲存 InputMediaPhoto 物件的串列
files=[] # 儲存檔案參考的串列
for i, path in enumerate(photo_paths): # 取出索引與圖片路徑
f=open(path, 'rb') # 開啟圖檔
files.append(f) # 暫存檔案參考以免被關閉
media=InputMediaPhoto( # 建立 InputMediaPhoto 物件
media=f,
caption=captions[i] if i < len(captions) else None,
parse_mode='Markdown'
)
media_group.append(media)
# 傳送 Media Group (多圖片)
await bot.send_media_group(chat_id=chat_id, media=media_group)
print("Media group sent successfully.")
return True
except Exception as e:
print(f"Error sending media group: {e}")
return False
finally: # 關閉所有檔案
for f in files:
f.close()
定義要傳送的本機圖片檔名路徑與標題 :
>>> photos=['cat1.jpg', 'cat2.jpg', 'cat3.jpg']
>>> captions=['貓咪 1', '貓咪 2', '貓咪 3']
呼叫 asyncio.run() 來執行這個非同步函式 (已利用 dotenv 載入 token 與 chat_id) :
>>> asyncio.run(telegram_images_async(token, chat_id, photos, captions))
(Message(caption='貓咪 1', channel_chat_created=False, chat=Chat(first_name='<我的名字>', id=<聊天室 ID>, type=<ChatType.PRIVATE>, username='<我的 username>'), date=datetime.datetime(2025, 4, 11, 9, 13, 20, tzinfo=datetime.timezone.utc), delete_chat_photo=False, from_user=User(first_name='twstock', id=7938146214, is_bot=True, username='twstock168_bot'), group_chat_created=False, media_group_id='13954902400686829', message_id=132, photo=(PhotoSize(file_id='AgACAgUAAxUHZ_eKuzGsDrmHcOe8XMClagTnAAHHAAKAwjEbBW64V_EWQQJaJNhaAQADAgADcwADNgQ', file_size=1394, file_unique_id='AQADgMIxGwVuuFd4', height=60, width=90), PhotoSize(file_id='AgACAgUAAxUHZ_eKuzGsDrmHcOe8XMClagTnAAHHAAKAwjEbBW64V_EWQQJaJNhaAQADAgADbQADNgQ', file_size=18479, file_unique_id='AQADgMIxGwVuuFdy', height=213, width=320), PhotoSize(file_id='AgACAgUAAxUHZ_eKuzGsDrmHcOe8XMClagTnAAHHAAKAwjEbBW64V_EWQQJaJNhaAQADAgADeAADNgQ', file_size=94085, file_unique_id='AQADgMIxGwVuuFd9', height=533, width=800), PhotoSize(file_id='AgACAgUAAxUHZ_eKuzGsDrmHcOe8XMClagTnAAHHAAKAwjEbBW64V_EWQQJaJNhaAQADAgADeQADNgQ', file_size=241570, file_unique_id='AQADgMIxGwVuuFd-', height=853, width=1280), PhotoSize(file_id='AgACAgUAAxUHZ_eKuzGsDrmHcOe8XMClagTnAAHHAAKAwjEbBW64V_EWQQJaJNhaAQADAgADdwADNgQ', file_size=540510, file_unique_id='AQADgMIxGwVuuFd8', height=1500, width=2250)), supergroup_chat_created=False), Message(caption='貓咪 2', channel_chat_created=False, chat=Chat(first_name='<我的名字>', id=<聊天室 ID>, type=<ChatType.PRIVATE>, username='<我的 username>'), date=datetime.datetime(2025, 4, 11, 9, 13, 20, tzinfo=datetime.timezone.utc), delete_chat_photo=False, from_user=User(first_name='twstock', id=7938146214, is_bot=True, username='twstock168_bot'), group_chat_created=False, media_group_id='13954902400686829', message_id=133, photo=(PhotoSize(file_id='AgACAgUAAxUHZ_eKvAi3dvXSZMjm4zc5H0ucywUAAoLCMRsFbrhXGCZ9_AoDXBMBAAMCAANzAAM2BA', file_size=1366, file_unique_id='AQADgsIxGwVuuFd4', height=60, width=90), PhotoSize(file_id='AgACAgUAAxUHZ_eKvAi3dvXSZMjm4zc5H0ucywUAAoLCMRsFbrhXGCZ9_AoDXBMBAAMCAANtAAM2BA', file_size=20757, file_unique_id='AQADgsIxGwVuuFdy', height=213, width=320), PhotoSize(file_id='AgACAgUAAxUHZ_eKvAi3dvXSZMjm4zc5H0ucywUAAoLCMRsFbrhXGCZ9_AoDXBMBAAMCAAN4AAM2BA', file_size=82754, file_unique_id='AQADgsIxGwVuuFd9', height=533, width=800), PhotoSize(file_id='AgACAgUAAxUHZ_eKvAi3dvXSZMjm4zc5H0ucywUAAoLCMRsFbrhXGCZ9_AoDXBMBAAMCAAN5AAM2BA', file_size=185302, file_unique_id='AQADgsIxGwVuuFd-', height=853, width=1280), PhotoSize(file_id='AgACAgUAAxUHZ_eKvAi3dvXSZMjm4zc5H0ucywUAAoLCMRsFbrhXGCZ9_AoDXBMBAAMCAAN3AAM2BA', file_size=719975, file_unique_id='AQADgsIxGwVuuFd8', height=1707, width=2560)), supergroup_chat_created=False), Message(caption='貓咪 3', channel_chat_created=False, chat=Chat(first_name='<我的名字>', id=<聊天室 ID>, type=<ChatType.PRIVATE>, username='<我的 username>'), date=datetime.datetime(2025, 4, 11, 9, 13, 20, tzinfo=datetime.timezone.utc), delete_chat_photo=False, from_user=User(first_name='twstock', id=7938146214, is_bot=True, username='twstock168_bot'), group_chat_created=False, media_group_id='13954902400686829', message_id=134, photo=(PhotoSize(file_id='AgACAgUAAxUHZ_eKu6BwUtic_8Lj7XblPfxa9lMAAoHCMRsFbrhXkvOMxCCzQocBAAMCAANzAAM2BA', file_size=1042, file_unique_id='AQADgcIxGwVuuFd4', height=90, width=80), PhotoSize(file_id='AgACAgUAAxUHZ_eKu6BwUtic_8Lj7XblPfxa9lMAAoHCMRsFbrhXkvOMxCCzQocBAAMCAANtAAM2BA', file_size=12276, file_unique_id='AQADgcIxGwVuuFdy', height=320, width=285), PhotoSize(file_id='AgACAgUAAxUHZ_eKu6BwUtic_8Lj7XblPfxa9lMAAoHCMRsFbrhXkvOMxCCzQocBAAMCAAN4AAM2BA', file_size=68931, file_unique_id='AQADgcIxGwVuuFd9', height=800, width=712), PhotoSize(file_id='AgACAgUAAxUHZ_eKu6BwUtic_8Lj7XblPfxa9lMAAoHCMRsFbrhXkvOMxCCzQocBAAMCAAN3AAM2BA', file_size=173001, file_unique_id='AQADgcIxGwVuuFd8', height=1500, width=1336), PhotoSize(file_id='AgACAgUAAxUHZ_eKu6BwUtic_8Lj7XblPfxa9lMAAoHCMRsFbrhXkvOMxCCzQocBAAMCAAN5AAM2BA', file_size=182085, file_unique_id='AQADgcIxGwVuuFd-', height=1280, width=1140)), supergroup_chat_created=False))
True
檢查 App 果然收到了三張圖片 :
沒有留言 :
張貼留言