自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

詳解如何使用 Python 操作 Telegram(電報(bào))機(jī)器人

人工智能 機(jī)器人
Telegram(電報(bào))相信大家都知道,關(guān)于它的介紹和注冊(cè)方式這里就跳過(guò)了,我假設(shè)你已經(jīng)注冊(cè)好了。本篇文章來(lái)聊一聊 Telegram 提供的機(jī)器人,以及如何用 Python 為機(jī)器人實(shí)現(xiàn)各種各樣的功能。

楔子

Telegram(電報(bào))相信大家都知道,關(guān)于它的介紹和注冊(cè)方式這里就跳過(guò)了,我假設(shè)你已經(jīng)注冊(cè)好了。本篇文章來(lái)聊一聊 Telegram 提供的機(jī)器人,以及如何用 Python 為機(jī)器人實(shí)現(xiàn)各種各樣的功能。

創(chuàng)建機(jī)器人

首先我們使用瀏覽器打開(kāi) https://web.telegram.org,然后用手機(jī)上的 APP 掃碼登錄。

圖片圖片

登錄之后搜索 BotFather,機(jī)器人需要通過(guò) BotFather 來(lái)創(chuàng)建,當(dāng)然 BotFather 本身也是一個(gè)機(jī)器人,但它同時(shí)管理著其它的機(jī)器人。

我們點(diǎn)擊 BotFather,下面將通過(guò)和它聊天的方式來(lái)創(chuàng)建機(jī)器人,過(guò)程如下。

1)在頁(yè)面中輸入命令 /newbot 并回車,相當(dāng)于給 BotFather 發(fā)指令,表示要?jiǎng)?chuàng)建機(jī)器人。注:命令要以 / 開(kāi)頭。

2)BotFather 收到之后會(huì)將機(jī)器人創(chuàng)建好,并提示我們給機(jī)器人起一個(gè)名字,這里我起名為:古明地覺(jué)。

3)回車之后,BotFather 會(huì)繼續(xù)讓我們給機(jī)器人起一個(gè)用戶名,這個(gè)用戶名會(huì)作為機(jī)器人的唯一標(biāo)識(shí),用于定位和查找。這里我起名為 Satori_Koishi_bot,注:用戶名必須以 Bot 或 bot 結(jié)尾。

下面來(lái)實(shí)際演示一下。

圖片圖片

我們點(diǎn)擊 t.me/Satori_Koishi_bot,看看結(jié)果如何。

圖片圖片

點(diǎn)擊 t.me/Satori_Koishi_bot 之后,再點(diǎn)擊屏幕中的 start(相當(dāng)于發(fā)送了一條 /start 指令),就可以和機(jī)器人聊天了。因?yàn)槲覀冞€沒(méi)有編寫代碼,來(lái)為機(jī)器人添加相應(yīng)的功能,所以目前不會(huì)有任何事情發(fā)生。

然后我們給自定義的機(jī)器人添加一些描述信息,顯然這依賴于 BotFather。向其發(fā)送 /mybots 指令,會(huì)返回我們創(chuàng)建的所有的機(jī)器人,當(dāng)然這里目前只有一個(gè)。

圖片圖片

我們點(diǎn)擊它,看看結(jié)果:

圖片圖片

里面提供了很多的選項(xiàng),這里我們?cè)冱c(diǎn)擊 Edit Bot,來(lái)編輯機(jī)器人的相關(guān)信息。

圖片圖片

不難發(fā)現(xiàn),我們除了給當(dāng)前機(jī)器人一個(gè)名字之外,其它的信息就沒(méi)有了,所以 Telegram 提供了一系列按鈕,供我們進(jìn)行編輯。比如我們點(diǎn)擊 Edit Botpic,編輯頭像。

圖片圖片

然后機(jī)器人的頭像會(huì)發(fā)生改變,當(dāng)然這些都屬于錦上添花的東西,最重要的是 Edit Commands,它是機(jī)器人能夠產(chǎn)生行為的核心,否則當(dāng)前的機(jī)器人就是個(gè)繡花枕頭,中看不中用。

下面我們點(diǎn)擊 Edit Commands,添加一個(gè) /help 命令。

圖片圖片

添加格式為命令 - 描述,可同時(shí)添加多個(gè)。

圖片圖片

目前機(jī)器人便支持了 /help 命令,另外如果點(diǎn)擊 Edit Command 之后再輸入 /empty,那么也可以將機(jī)器人現(xiàn)有的命令清空掉。

雖然 /help 命令有了,但發(fā)送這個(gè)命令之后,機(jī)器人不會(huì)有任何的反應(yīng),因?yàn)槲覀冞€沒(méi)有給命令綁定相應(yīng)的處理函數(shù),下面就來(lái)看看如何綁定。當(dāng)然啦,機(jī)器人不光要對(duì)命令做出反應(yīng),就算是普通的文本、表情、圖片等消息,也應(yīng)該做出反應(yīng)。至于命令本質(zhì)上就是一個(gè)純文本,只不過(guò)它應(yīng)該以 / 開(kāi)頭。

接收消息并處理

我們可以使用 Python 連接 Telegram 機(jī)器人,為它綁定處理函數(shù),首先需要安裝一個(gè)第三方庫(kù)。

安裝:pip3 install "python-telegram-bot[all]"

然后獲取機(jī)器人的 Token,這個(gè) Token 怎么獲取呢?

圖片圖片

像 BotFather 發(fā)送 /mybots 命令,點(diǎn)擊指定機(jī)器人的 API Token 即可獲取。

圖片圖片

有了這個(gè) Token 之后,就可以和機(jī)器人建立連接了。

import asyncio
import telegram
from telegram.request import HTTPXRequest
# 代理,由于不方便展示,因此我定義在了一個(gè)單獨(dú)的文件中
# 這里的 PROXY 是一個(gè)字符串,類似于 "http://username:password@ip:port"
from proxy import PROXY

BOT_API_TOKEN = "6485526535:AAEvGr9EDqtc4QPehkgohH6gczOTO5RIYRE"

async def main():
    # 傳遞機(jī)器人的 Token,內(nèi)部會(huì)自動(dòng)和它建立連接
    bot = telegram.Bot(
        BOT_API_TOKEN,
        # 指定代理
        request=HTTPXRequest(proxy=PROXY),
        get_updates_request=HTTPXRequest(proxy=PROXY),
    )
    async with bot:
        # 測(cè)試連接是否成功,如果成功,會(huì)返回機(jī)器人的信息
        print(await bot.get_me())

asyncio.run(main())
"""
User(api_kwargs={'has_main_web_app': False}, 
     can_connect_to_business=False, 
     can_join_groups=True, 
     can_read_all_group_messages=False, 
     first_name='古明地覺(jué)', 
     id=6485526535, 
     is_bot=True, 
     supports_inline_queries=False, 
     username='Satori_Koishi_bot')
"""

返回值包含了機(jī)器人的具體信息,還是比較簡(jiǎn)單的,只需指定一個(gè) Token 即可訪問(wèn)。當(dāng)然啦,由于網(wǎng)絡(luò)的原因還需要使用代理。

然后通過(guò)該模塊還可以給機(jī)器人發(fā)消息,但這顯然不是我們的重點(diǎn),因?yàn)橄⒖隙ㄊ峭ㄟ^(guò) APP 或者瀏覽器發(fā)送的。我們要做的是,定義機(jī)器人的回復(fù)邏輯,當(dāng)用戶給它發(fā)消息時(shí),它應(yīng)該做些什么事情。

先來(lái)一個(gè)簡(jiǎn)單的案例,當(dāng)用戶輸入 /start 命令時(shí),回復(fù)一段文本。

from telegram import Update
from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler
from proxy import PROXY

BOT_API_TOKEN = "6485526535:AAEvGr9EDqtc4QPehkgohH6gczOTO5RIYRE"

# 定義一個(gè)處理函數(shù)
# update 封裝了用戶發(fā)送的消息數(shù)據(jù)
# context 則封裝了 Bot 對(duì)象和一些會(huì)話數(shù)據(jù)
# 這兩個(gè)對(duì)象非常重要,后面還會(huì)詳細(xì)說(shuō)
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # context.bot 便是機(jī)器人,可以調(diào)用它的 send_message 方法回復(fù)消息
    await context.bot.send_message(
        # 關(guān)于 chat_id 稍后解釋
        chat_id=update.message.chat.id,
        # 回復(fù)的文本內(nèi)容
        text="歡迎來(lái)到地靈殿"
    )

# 構(gòu)建一個(gè)應(yīng)用
application = ApplicationBuilder().token(BOT_API_TOKEN).proxy(PROXY).build()
# 創(chuàng)建一個(gè) CommandHandler 實(shí)例,當(dāng)用戶輸入 /start 的時(shí)候,執(zhí)行 start 函數(shù)
start_handler = CommandHandler("start", start)
# 將 start_handler 加到應(yīng)用當(dāng)中
application.add_handler(start_handler)
# 開(kāi)啟無(wú)限循環(huán),監(jiān)聽(tīng)事件
application.run_polling()

我們來(lái)測(cè)試一下:

圖片圖片

顯然結(jié)果是成功的,不過(guò)目前這個(gè)機(jī)器人只能處理 /start 命令,如果希望它支持更多的命令,那么就定義多個(gè) CommandHandler 即可。但是問(wèn)題來(lái)了,如果我們希望這個(gè)機(jī)器人能處理普通文本的話,該怎么辦呢?

from telegram import Update
from telegram.ext import (
    ApplicationBuilder, ContextTypes,
    MessageHandler, filters
)
from proxy import PROXY

BOT_API_TOKEN = "6485526535:AAEvGr9EDqtc4QPehkgohH6gczOTO5RIYRE"

async def reply(update: Update, context: ContextTypes.DEFAULT_TYPE):
    await context.bot.send_message(
        chat_id=update.message.chat.id,
        # 通過(guò) update.message.text 可以拿到用戶發(fā)送的消息
        text=f"古明地覺(jué)已收到,你發(fā)的內(nèi)容是:{update.message.text}"
    )

application = ApplicationBuilder().token(BOT_API_TOKEN).proxy(PROXY).build()
# 前面使用了 CommandHandler,它專門用來(lái)處理命令,第一個(gè)參數(shù)應(yīng)該是字符串
# 比如第一個(gè)參數(shù)是 "start",那么就給機(jī)器人增加了一個(gè)回復(fù) /start 命令的功能
# 而 MessageHandler 可以用于回復(fù)所有類型的消息,比如文本、表情、圖片、視頻等等
# 具體能回復(fù)哪些,通過(guò)第一個(gè)參數(shù)指定。這里表示只要用戶發(fā)送了文本消息,就執(zhí)行 reply 函數(shù)
reply_handler = MessageHandler(filters.TEXT, reply)
application.add_handler(reply_handler)
application.run_polling()

測(cè)試一下:

圖片圖片

結(jié)果沒(méi)有問(wèn)題,并且 /start 命令也被當(dāng)成普通的文本處理了,因?yàn)槊畋举|(zhì)上就是一個(gè)文本。然后代碼中的 filters,它里面除了有表示文本類型的 TEXT,還有很多其它類型。

# 命令
filters.COMMAND
# 普通文本(包括 emoji)
filters.TEXT
# Telegram 貼紙包中的貼紙
filters.Sticker.ALL
# 圖片文件
filters.PHOTO
# 音頻文件
filters.AUDIO
# 視頻文件
filters.VIDEO
# 文檔(例如 PDF、DOCX 等等)
filters.Document.ALL
# 語(yǔ)音(使用 Telegram 錄制的語(yǔ)音)
filters.VOICE
# 地理位置
filters.LOCATION
# 聯(lián)系人
filters.CONTACT
# 動(dòng)畫,通常是 GIF
filters.ANIMATION
# 通過(guò) Telegram 的視頻筆記功能錄制的視頻
filters.VIDEO_NOTE

# 如果希望同時(shí)支持多種類型,那么可以使用 | 進(jìn)行連接
# 比如同時(shí)支持 "文本" 和 "圖片"
filters.TEXT | filters.PHOTO
# 當(dāng)然也可以取反,~filters.TEXT 表示除了文本以外的類型
~filters.TEXT
# | 和 ~ 都出現(xiàn)了,顯然還剩下 &,而 & 也是支持的 
# 我們知道命令本質(zhì)上就是一個(gè)以 / 開(kāi)頭的文本
# 如果我們希望只處理普通文本,不處理命令,該怎么辦呢?
# 很簡(jiǎn)單,像下面這樣指定即可,此時(shí)以 / 開(kāi)頭的文本(命令)會(huì)被忽略掉
filters.TEXT & ~filters.COMMAND

# 除了以上這些,filters 還支持其它類型,有興趣可以看一下
# 當(dāng)然 filters 還提供了一個(gè) ALL,表示所有類型
filters.ALL

然后注意一下里面的 filters.Sticker 和 filters.Document,這兩個(gè)類型比較特殊,它們內(nèi)部還可以細(xì)分,這里我們就不細(xì)分了,直接 .ALL 即可。

我們來(lái)測(cè)試一下,看看這些類型消息都長(zhǎng)什么樣子。

from telegram import Update
from telegram.ext import (
    ApplicationBuilder, ContextTypes,
    MessageHandler, filters
)
from proxy import PROXY

BOT_API_TOKEN = "6485526535:AAEvGr9EDqtc4QPehkgohH6gczOTO5RIYRE"

async def get_message_type(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # 獲取消息
    message = update.message
    # 獲取消息類型
    if message.text:
        if message.text[0] == "/":
            message_type = "filters.COMMAND"
        else:
            message_type = "filters.TEXT"
    elif message.sticker:
        message_type = "filters.Sticker"
    elif message.photo:
        message_type = "filters.PHOTO"
    elif message.audio:
        message_type = "filters.AUDIO"
    elif message.video:
        message_type = "filters.VIDEO"
    elif message.document:
        message_type = "filters.Document"
    elif message.voice:
        message_type = "filters.VOICE"
    elif message.location:
        message_type = "filters.LOCATION"
    elif message.contact:
        message_type = "filters.CONTACT"
    elif message.animation:
        message_type = "filters.ANIMATION"
    elif message.video_note:
        message_type = "filters.VIDEO_NOTE"
    else:
        message_type = "filters.<OTHER TYPE>"
    await context.bot.send_message(
        chat_id=update.message.chat.id,
        text=f"你發(fā)送的消息的類型是 {message_type}"
    )

application = ApplicationBuilder().token(BOT_API_TOKEN).proxy(PROXY).build()
reply_handler = MessageHandler(filters.ALL, get_message_type)
application.add_handler(reply_handler)
application.run_polling()

我們發(fā)幾條消息,讓機(jī)器人告訴我們消息的類型。

圖片圖片

至于其它類型,感興趣可以測(cè)試一下。

update 和 context

處理函數(shù)里面有兩個(gè)參數(shù),分別是 update 和 context。它們非常重要,我們來(lái)打印一下,看看長(zhǎng)什么樣子。

async def reply(update: Update, context: ContextTypes.DEFAULT_TYPE):
    pprint(update.to_dict())
    await context.bot.send_message(chat_id=update.message.chat.id,
                                   text="不想說(shuō)話")

application = ApplicationBuilder().token(BOT_API_TOKEN).proxy(PROXY).build()
reply_handler = MessageHandler(filters.ALL, reply)
application.add_handler(reply_handler)
application.run_polling()

下面發(fā)送一條文本消息。

圖片圖片

然后查看 update.to_dict() 的輸出是什么,為了方便理解,我將字段順序調(diào)整了一下。

{
    'message': {
        # 是否創(chuàng)建了頻道,因?yàn)槭撬搅?,所以?False
        'channel_chat_created': False,
        # 聊天照片是否已被刪除,私聊一般也為 False
        'delete_chat_photo': False,
        # 是否創(chuàng)建了群組,因?yàn)槭撬搅模詾?False
        'group_chat_created': False,
        # 是否創(chuàng)建了超級(jí)群組,因?yàn)槭撬搅?,所以?False
        'supergroup_chat_created': False,
        # "發(fā)送者" 發(fā)送的消息
        # 因?yàn)榘l(fā)送的是文本,所以這里是 text 字段
        'text': '這是一條文本消息',
        # 消息發(fā)送的時(shí)間
        'date': 1722623118,
        # 消息的 ID
        'message_id': 84,
        # 消息發(fā)送者的信息
        'from': {
            'first_name': '小云',
            'id': 6353481551,
            'is_bot': False,
            'language_code': 'zh-hans',
            'last_name': '同學(xué)'
        },
        # chat 表示會(huì)話環(huán)境,機(jī)器人要通過(guò) chat 判斷消息應(yīng)該回復(fù)給誰(shuí)
        # 因?yàn)槟壳笆呛蜋C(jī)器人私聊,所以機(jī)器人的回復(fù)對(duì)象就是消息的發(fā)送者
        # 因此里面的 first_name、last_name、id 和消息發(fā)送者是一致的
        # 但如果是群聊,那么里面的 id 字段則表示群組的 id
        # 此外還會(huì)包含一個(gè) title 字段,表示群組的名稱
        'chat': {
            'first_name': '小云',
            'last_name': '同學(xué)',
            # 不管 chat 的類型是什么,里面一定會(huì)包含 id 字段
            # 這個(gè) id 可能是用戶的 id,也可能是群組的 id
            # 總之有了這個(gè) id,機(jī)器人就知道要將消息回復(fù)給誰(shuí)
            # 所以代碼中的 send_message 方法至少要包含兩個(gè)參數(shù)
            # 分別是 chat_id(發(fā)送給誰(shuí))和 text(發(fā)送的內(nèi)容)
            'id': 6353481551,
            # chat 的類型,定義在 filters.ChatType 中
            # ChatType.PRIVATE:私人對(duì)話
            # ChatType.GROUP:普通群組聊天
            # ChatType.SUPERGROUP:超級(jí)群組聊天
            # ChatType.GROUPS:普通群組聊天或超級(jí)群組聊天
            # ChatType.CHANNEL:頻道,用于向訂閱者廣播消息
            'type': '<ChatType.PRIVATE>'
        },
    },
    # 每發(fā)送一條消息,會(huì)話都在更新,所以 update_id 表示更新的唯一標(biāo)識(shí)符
    # 用于跟蹤更新,以確保消息處理沒(méi)有丟失或重復(fù)
    'update_id': 296857735
}

以上就是 update.to_dict() 的輸出結(jié)果,當(dāng)用戶向 bot 發(fā)送消息時(shí),Telegram 服務(wù)器會(huì)將這些數(shù)據(jù)以 JSON 的形式發(fā)送給當(dāng)前的應(yīng)用程序,以便 bot 可以處理和響應(yīng)這些消息。當(dāng)然啦,我們這里使用的庫(kù)會(huì)將數(shù)據(jù)封裝成 Update 對(duì)象,因此獲取數(shù)據(jù)時(shí),可以有以下兩種獲取方式。

chat_id = update.to_dict()["message"]["chat"]["id"]
chat_id = update.message.chat.id

以上是當(dāng)用戶發(fā)送文本消息時(shí),Telegram 發(fā)送的數(shù)據(jù),我們?cè)僭囈幌缕渌?,比如上傳一個(gè)文檔。

{
    'message': {
        'channel_chat_created': False,
        'delete_chat_photo': False,
        'group_chat_created': False,
        'supergroup_chat_created': False,
        'chat': {'first_name': '小云',
                 'id': 6353481551,
                 'last_name': '同學(xué)',
                 'type': '<ChatType.PRIVATE>'},
        'date': 1722628661,
        # 因?yàn)榘l(fā)送的是文檔,所以這里是 document 字段
        'document': {'file_id': 'BQACAgUAAxkBAANgZq06NVL6......',
                     'file_name': 'OpenAI.pdf',
                     'file_size': 2279632,
                     'file_unique_id': 'AgADLw8AAn36cFU',
                     'mime_type': 'application/pdf',
                     'thumb': {
                         'file_id': 'AAMCBQADGQEAA2BmrTo1Uv......',
                         'file_size': 22533,
                         'file_unique_id': 'AQADLw8AAn36cFVy',
                         'height': 320,
                         'width': 243},
                     'thumbnail': {
                         'file_id': 'AAMCBQADGQEAA2BmrTo1U......',
                         'file_size': 22533,
                         'file_unique_id': 'AQADLw8AAn36cFVy',
                         'height': 320,
                         'width': 243}},
        'from': {'first_name': '小云',
                 'id': 6353481551,
                 'is_bot': False,
                 'language_code': 'zh-hans',
                 'last_name': '同學(xué)'},
        'message_id': 96,
    },
    'update_id': 296857741
}

至于其它的類型也是類似的,可以自己試一下,比如上傳一段視頻,看看打印的輸出是什么。

不過(guò)還有一個(gè)問(wèn)題,就是當(dāng)用戶上傳音頻、視頻、文檔等,bot 如何獲取它們呢?顯然要依賴?yán)锩娴?file_id。

async def download(update: Update, context: ContextTypes.DEFAULT_TYPE):
    document = update.message.document

    file_id = document.file_id  # 文件 id
    file_size = document.file_size  # 文件大小
    file_name = document.file_name  # 文件名
    # 用戶上傳的文件會(huì)保存在 Telegram 服務(wù)器,我們可以基于文件 id 獲取
    file_obj = await context.bot.get_file(file_id)
    # file_obj.file_path 便是文件的地址,直接下載即可
    with open(file_name, "wb") as f:
        resp = httpx.get(file_obj.file_path, proxy=PROXY)
        f.write(resp.content)
    await context.bot.send_message(
        chat_id=update.message.chat.id,
        text=f"{file_name} 下載完畢,大小 {file_size} 字節(jié)"
    )

application = ApplicationBuilder().token(BOT_API_TOKEN).proxy(PROXY).build()
download_handler = MessageHandler(filters.Document.ALL, download)
application.add_handler(download_handler)
application.run_polling()

我們上傳幾個(gè)文件試試。

圖片圖片

結(jié)果沒(méi)有問(wèn)題,用戶上傳的文件也下載到了本地。

回復(fù)富文本消息

目前機(jī)器人回復(fù)的都是普通的純文本,但也可以回復(fù)富文本消息。

async def rich_msg(update: Update, context: ContextTypes.DEFAULT_TYPE):
    message = update.message
    if message.text == "baidu":
        text = '<a 
    elif message.text == "zhihu":
        text = '<a 
    elif message.text == "bilibili":
        text = '<a >點(diǎn)擊進(jìn)入 B 站頁(yè)面</a>'
    else:
        text = 'Unsupported Website'
    await context.bot.send_message(
        chat_id=update.message.chat.id,
        text=text,
        # 按照 HTML 進(jìn)行解析
        parse_mode="HTML"
    )

測(cè)試一下:

圖片圖片

結(jié)果沒(méi)有問(wèn)題,另外我們看到 a 標(biāo)簽自帶預(yù)覽功能,如果不希望預(yù)覽,那么也可以禁用掉。

圖片圖片

將 disable_web_page_preview 參數(shù)指定為 False,即可禁用 a 標(biāo)簽的預(yù)覽功能。另外發(fā)送的消息除了可以按照 HTML 格式解析,還可以按照 Markdown 格式解析,將 parse_mode 參數(shù)指定為 "Markdown" 或者 "MarkdownV2" 即可。

回復(fù)其它類型的消息

目前機(jī)器人回復(fù)的都是文本,那么能不能回復(fù)音頻、視頻、圖片呢?顯然是可以的,并且它們還可以和文本一起返回。

# 發(fā)送圖片
await context.bot.send_photo(
    chat_id=update.message.chat.id,
    # 可以是路徑、句柄、bytes 對(duì)象
    # 已經(jīng)上傳到 Telegram 服務(wù)器的文件會(huì)有一個(gè) file_id
    # 指定 file_id 也是可以的
    photo="path/to/image.jpg",
)

# 發(fā)送音頻
await context.bot.send_audio(
    chat_id=update.message.chat.id,
    # 可以是 路徑、句柄、bytes 對(duì)象、file_id
    audio="path/to/audio.mp3"
)

# 發(fā)送視頻
await context.bot.send_video(
    chat_id=update.message.chat.id,
    # 可以是 路徑、句柄、bytes 對(duì)象、file_id
    video="path/to/video.mp4"
)

# 發(fā)送文檔
await context.bot.send_document(
    chat_id=update.message.chat.id,
    # 可以是 路徑、句柄、bytes 對(duì)象、file_id
    document="path/to/document.pdf"
)

# 發(fā)送語(yǔ)音
await context.bot.send_voice(
    chat_id=update.message.chat.id,
    # 可以是 路徑、句柄、bytes 對(duì)象、file_id
    voice=r"path/to/voice.ogg",
)

# 發(fā)送位置
await context.bot.send_location(
    chat_id=update.message.chat.id,
    latitude=40.4750280, lnotallow=116.2676535
)

# 發(fā)送聯(lián)系人
from telegram import Contact
contact = Contact(
    phone_number='+8618510286802',
    first_name='芙蘭朵露',
    # 以下兩個(gè)參數(shù)也可以不指定
    last_name='斯卡雷特',
    user_id=5783657687
)
await context.bot.send_contact(
    chat_id=update.message.chat.id,
    cnotallow=contact
)

# 發(fā)送貼紙
await context.bot.send_sticker(
    chat_id=update.message.chat.id,
    # 可以是 路徑、句柄、bytes 對(duì)象、file_id
    sticker="CAACAgIAAxkBAAO5Zq5kRNKkIGZpH......"
)

# 發(fā)送 GIF
await context.bot.send_animation(
    chat_id=update.message.chat.id,
    # 可以是 路徑、句柄、bytes 對(duì)象、file_id
    animatinotallow="CgACAgIAAxkBAAPBZq5lekVT95I......"
)

除了以上這些,還可以發(fā)送其它類型的消息,不過(guò)不常用,有興趣的話可以自己看一下,這些方法都以 send_ 開(kāi)頭。然后我們來(lái)發(fā)幾條消息,測(cè)試一下。

圖片圖片

結(jié)果沒(méi)有問(wèn)題。

媒體組

現(xiàn)在我們已經(jīng)知道如何讓機(jī)器人回復(fù)不同種類的消息了,但如果我想實(shí)現(xiàn)更復(fù)雜的功能,比如同時(shí)發(fā)送多張圖片、多個(gè)視頻,并且還配帶文字,要怎么做呢?可能有人覺(jué)得這還不簡(jiǎn)單,寫個(gè)循環(huán)不就行了,比如要發(fā)送 5 個(gè)視頻,那么調(diào)用 5 次 send_video 方法不就好了。

首先這是一種方法,但循環(huán) 5 次,那么這 5 個(gè)視頻是作為不同的消息分開(kāi)發(fā)送的。更多時(shí)候,我們是希望作為一個(gè)整體發(fā)送,那么此時(shí)可以使用媒體組功能。

from telegram import Update, InputMediaPhoto
from telegram.ext import (
    ApplicationBuilder,
    ContextTypes,
    CommandHandler
)
from proxy import PROXY

BOT_API_TOKEN = "6485526535:AAEvGr9EDqtc4QPehkgohH6gczOTO5RIYRE"

async def send_media_group(update: Update,
                           context: ContextTypes.DEFAULT_TYPE):
    media_group = [
        # 可以是 URL、bytes 對(duì)象、文件句柄、file_id
        InputMediaPhoto(open('satori1.png', "rb"), captinotallow="古"),
        InputMediaPhoto(open('satori2.png', "rb"), captinotallow="明"),
        InputMediaPhoto(open('satori3.png', "rb"), captinotallow="地"),
        InputMediaPhoto(open('satori4.png', "rb"), captinotallow="覺(jué)")
    ]

    # 發(fā)送媒體組
    await context.bot.send_media_group(
        chat_id=update.message.chat.id,
        media=media_group
    )

application = ApplicationBuilder().token(BOT_API_TOKEN).proxy(PROXY).build()
download_handler = CommandHandler("satori", send_media_group)
application.add_handler(download_handler)
application.run_polling()

我們輸入命令 /satori,應(yīng)該會(huì)返回 4 張圖片。

圖片圖片

結(jié)果沒(méi)有問(wèn)題,并且這 4 張圖片是整體作為一條消息發(fā)送的。然后我們?cè)诖a中還指定了一個(gè) caption 參數(shù),它是做什么的呢?我們點(diǎn)擊一下圖片就知道了。

圖片圖片

點(diǎn)擊圖片放大查看時(shí),captaion 會(huì)顯示在圖片下方。另外,如果發(fā)送了多張圖片,但只有一張圖片指定了 caption 參數(shù),那么該 caption 會(huì)和圖片一起顯示,我們舉例說(shuō)明。

async def send_media_group(update: Update,
                           context: ContextTypes.DEFAULT_TYPE):
    caption = "+v ?(^_-) 解鎖地靈殿隱藏福利"
    media_group = [
        # 可以是 URL、bytes 對(duì)象、文件句柄、file_id
        InputMediaPhoto(open('satori1.png', "rb")),
        InputMediaPhoto(open('satori2.png', "rb")),
        InputMediaPhoto(open('satori3.png', "rb"), captinotallow=caption),
        InputMediaPhoto(open('satori4.png', "rb"))
    ]

    # 發(fā)送媒體組
    await context.bot.send_media_group(
        chat_id=update.message.chat.id,
        media=media_group
    )

只有一張圖片指定了 caption 參數(shù),我們看看效果。

圖片圖片

此時(shí)圖片會(huì)和文字一起顯示,當(dāng)然你也可以不指定 caption 參數(shù),而是在發(fā)送完圖片之后,再調(diào)用一次 send_message。這種做法也是可以的,只不過(guò)此時(shí)圖片和文字會(huì)作為兩條消息分開(kāi)顯示。

以上是發(fā)送圖片,除了圖片之外還可以發(fā)送音頻、視頻、文檔,并且只支持這 4 種。但要注意:它們不能混在一起發(fā),只有圖片和視頻可以,我們測(cè)試一下。

from telegram import (
    Update,
    InputMediaPhoto,
    InputMediaAudio,
    InputMediaVideo,
    InputMediaDocument
)
from telegram.ext import (
    ApplicationBuilder,
    ContextTypes,
    CommandHandler
)
from proxy import PROXY

BOT_API_TOKEN = "6485526535:AAEvGr9EDqtc4QPehkgohH6gczOTO5RIYRE"

async def send_media_group(update: Update,
                           context: ContextTypes.DEFAULT_TYPE):
    video_caption = (
        "這游戲我玩不下去了,裝備喂養(yǎng)和貼膜就算了,"
        "但自定義詞條我是真忍不了,洗不出來(lái),根本洗不出來(lái)。"
    )
    media_group = [
        InputMediaPhoto(open("satori1.png", "rb")),
        InputMediaVideo(open("DNF 裝備銷毀.mp4", "rb"), 
                        captinotallow=video_caption),
        # 也支持發(fā)送音頻和文檔,但不能混在一起
        # InputMediaAudio(open("3rd eye.mp3", "rb")),
        # InputMediaDocument(open('OpenAI.pdf', 'rb'))
    ]

    # 發(fā)送媒體組
    await context.bot.send_media_group(
        chat_id=update.message.chat.id,
        media=media_group
    )

application = ApplicationBuilder().token(BOT_API_TOKEN).proxy(PROXY).build()
download_handler = CommandHandler("test_media_group", send_media_group)
application.add_handler(download_handler)
application.run_polling()

測(cè)試一下:

圖片圖片

結(jié)果正常,只是因?yàn)橐曨l和圖片是一起返回的,所以沒(méi)有預(yù)覽功能,需要點(diǎn)擊之后才會(huì)播放。并且我們只給視頻指定了 caption 參數(shù),所以文字直接顯示在了下方,如果媒體組中有多個(gè) caption,那么就不會(huì)單獨(dú)顯示了,需要點(diǎn)擊放大之后才能看到。

當(dāng)然啦,如果你不需要同時(shí)發(fā)送多個(gè)媒體文件,那么就沒(méi)必要調(diào)用 send_media_group 方法了,直接使用之前的方法即可。

  • send_photo;
  • send_audio;
  • send_video;
  • send_document;

這些方法一次性只能發(fā)送一個(gè)媒體文件,比如發(fā)送視頻。

async def send_video(update: Update, context: ContextTypes.DEFAULT_TYPE):
    video_caption = (
        "這游戲我玩不下去了,裝備喂養(yǎng)和貼膜就算了,"
        "但自定義詞條我是真忍不了,洗不出來(lái),根本洗不出來(lái)。"
    )
    await context.bot.send_video(
        chat_id=update.message.chat.id,
        video="DNF 裝備銷毀.mp4",
        captinotallow=video_caption,
        # 讓 caption 顯示在上方,默認(rèn)顯示在下方
        show_caption_above_media=True,
    )

application = ApplicationBuilder().token(BOT_API_TOKEN).proxy(PROXY).build()
download_handler = CommandHandler("destroy", send_video)
application.add_handler(download_handler)
application.run_polling()

測(cè)試一下:

圖片圖片

怎么樣,是不是很有趣呢?另外 caption 還可以是富文本,只需將 parse_mode 參數(shù)指定為 "HTML"、"Markdown" 或 "MarkdownV2" 即可。

關(guān)于機(jī)器人如何回復(fù)不同種類的消息,以及同時(shí)回復(fù)多條消息,相關(guān)內(nèi)容我們就說(shuō)完了。有了這些功能,我們的機(jī)器人就已經(jīng)很強(qiáng)大了,你也可以把它和公司的業(yè)務(wù)結(jié)合起來(lái)。

比如創(chuàng)建一個(gè)命令:/get,它的功能如下。

圖片圖片

然后在代碼中添加一個(gè) CommandHandler("get", get_table),便可讓用戶通過(guò) Telegram 查詢數(shù)據(jù)庫(kù)表,當(dāng)然這里只是打個(gè)比方,具體怎么做取決于你的想法。另外多說(shuō)一句,如果你希望輸入 / 之后能像上面那樣有提示,那么需要通過(guò) BotFather 進(jìn)行設(shè)置。

圖片圖片

要強(qiáng)調(diào)的是,這種方式只是起到一個(gè)提示作用,提示機(jī)器人支持 /get 命令。但機(jī)器人實(shí)際上是否支持,取決于代碼中是否為機(jī)器人實(shí)現(xiàn)了 /get。所以當(dāng)我們?cè)诖a中為機(jī)器人添加完命令之后,可以再通過(guò) Edit Commands 進(jìn)行設(shè)置,這樣當(dāng)用戶輸入 / 之后,機(jī)器人有哪些命令以及描述都會(huì)顯示出來(lái)。

當(dāng)然啦,如果你不通過(guò) Edit Commands 進(jìn)行設(shè)置的話,也是可以的,只是用戶輸入 / 之后不會(huì)有提示罷了,但命令是會(huì)回復(fù)的,只要在代碼中實(shí)現(xiàn)了。同理,如果通過(guò) Edit Commands 設(shè)置了,但代碼中沒(méi)實(shí)現(xiàn),那么該命令也不會(huì)有效果。

自定義按鈕

雖然目前的機(jī)器人已經(jīng)很強(qiáng)大了,但是還不夠,我們看一下 BotFather。

圖片圖片

你會(huì)發(fā)現(xiàn)它下面帶了很多的按鈕,點(diǎn)擊按鈕之后會(huì)執(zhí)行相應(yīng)的邏輯,那我們要怎么實(shí)現(xiàn)這些按鈕呢?

from telegram import (
    Update,
    InlineKeyboardMarkup,
    InlineKeyboardButton,
)
from telegram.ext import (
    ApplicationBuilder,
    ContextTypes,
    CommandHandler
)
from proxy import PROXY

BOT_API_TOKEN = "6485526535:AAEvGr9EDqtc4QPehkgohH6gczOTO5RIYRE"

async def add_button(update: Update, context: ContextTypes.DEFAULT_TYPE):
    text = "作為<i>程序猿</i>,你最喜歡哪種編程語(yǔ)言呢?"
    # 設(shè)置按鈕
    reply_markup = InlineKeyboardMarkup([
        # 第一行
        [InlineKeyboardButton(text="Python", url="https://www.python.org")],
        # 第二行
        [InlineKeyboardButton(text="Golang", url="https://golang.org")],
        # 第三行
        [InlineKeyboardButton(text="Rust", url="https://www.rust-lang.org")],
        # 第四行
        [InlineKeyboardButton(text="Zig", url="https://ziglang.org")],
    ])
    await context.bot.send_message(
        chat_id=update.message.chat.id,
        text=text,
        parse_mode="HTML",
        reply_markup=reply_markup
    )

application = ApplicationBuilder().token(BOT_API_TOKEN).proxy(PROXY).build()
download_handler = CommandHandler("language", add_button)
application.add_handler(download_handler)
application.run_polling()

測(cè)試一下:

圖片圖片

此時(shí)按鈕就實(shí)現(xiàn)了,由于在 InlineKeyboardButton 里面指定的是 url,所以這是跳轉(zhuǎn)按鈕,點(diǎn)擊之后會(huì)打開(kāi)指定的頁(yè)面。并且按鈕的右上角還有一個(gè)小箭頭,表示按鈕是跳轉(zhuǎn)按鈕。

但除了跳轉(zhuǎn)按鈕之外,還有回調(diào)按鈕,也就是點(diǎn)擊按鈕之后會(huì)執(zhí)行回調(diào)函數(shù),我們舉例說(shuō)明。

from telegram import (
    Update,
    InlineKeyboardMarkup,
    InlineKeyboardButton,
)
from telegram.ext import (
    ApplicationBuilder,
    ContextTypes,
    CommandHandler,
    CallbackQueryHandler,
)
from proxy import PROXY

BOT_API_TOKEN = "6485526535:AAEvGr9EDqtc4QPehkgohH6gczOTO5RIYRE"

async def add_button(update: Update, context: ContextTypes.DEFAULT_TYPE):
    text = "o(╥﹏╥)o??╭(╯^╰)╮"
    # 設(shè)置按鈕
    reply_markup = InlineKeyboardMarkup([
        # 第一行,兩個(gè)跳轉(zhuǎn)按鈕
        [InlineKeyboardButton(text="百度", url="https://www.baidu.com"),
         InlineKeyboardButton(text="谷歌", url="https://www.google.com"),],
        # 第二行,兩個(gè)回調(diào)按鈕
        [InlineKeyboardButton(text="油管", callback_data="youtube"),
         InlineKeyboardButton(text="B站", callback_data="bilibili"),],
    ])
    await context.bot.send_message(
        chat_id=update.message.chat.id,
        text=text,
        reply_markup=reply_markup
    )

async def callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # 當(dāng)點(diǎn)擊回調(diào)按鈕時(shí),會(huì)執(zhí)行相應(yīng)的回調(diào)函數(shù)
    cb_data = update.callback_query.data  # 回調(diào)按鈕中指定的 callback_data
    if cb_data == "youtube":
        text = "歡迎來(lái)到油管"
    elif cb_data == "bilibili":
        text = "歡迎來(lái)到 B 站"
    else:
        text = "Unknown Website"
    await context.bot.send_message(
        # 注意:這里是 update.callback_query.message.chat.id
        chat_id=update.callback_query.message.chat.id,
        text=text
    )

application = ApplicationBuilder().token(BOT_API_TOKEN).proxy(PROXY).build()
# 添加 Handler
application.add_handler(
    CommandHandler("website", add_button)
)
# 處理回調(diào)的 Handler,否則點(diǎn)擊按鈕不會(huì)有效果
application.add_handler(
    CallbackQueryHandler(callback)
)
application.run_polling()

測(cè)試一下效果:

圖片圖片

點(diǎn)擊油管和 B站的時(shí)候會(huì)執(zhí)行回調(diào)函數(shù),結(jié)果沒(méi)有問(wèn)題。但是我們發(fā)現(xiàn),這些文字是單獨(dú)發(fā)送的,那可不可以本地修改呢,也就是將按鈕上方的文字替換掉。答案是可以的,我們來(lái)測(cè)試一下。

from telegram import (
    Update,
    InlineKeyboardMarkup,
    InlineKeyboardButton,
)
from telegram.ext import (
    ApplicationBuilder,
    ContextTypes,
    CommandHandler,
    CallbackQueryHandler,
)
from proxy import PROXY

BOT_API_TOKEN = "6485526535:AAEvGr9EDqtc4QPehkgohH6gczOTO5RIYRE"

def get_reply_markup():
    reply_markup = InlineKeyboardMarkup([
        [InlineKeyboardButton(text="古明地覺(jué)", callback_data="satori")],
        [InlineKeyboardButton(text="古明地戀", callback_data="koishi")],
        [InlineKeyboardButton(text="霧雨魔理沙", callback_data="marisa")],
        [InlineKeyboardButton(text="琪露諾", callback_data="cirno")],
    ])
    return reply_markup

async def add_button(update: Update, context: ContextTypes.DEFAULT_TYPE):
    text = "點(diǎn)擊想要攻略的角色"
    await context.bot.send_message(
        chat_id=update.message.chat.id,
        text=text,
        reply_markup=get_reply_markup()
    )

async def callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
    cb_data = update.callback_query.data
    if cb_data == "satori":
        img = "你將要攻略古明地覺(jué)"
    elif cb_data == "koishi":
        img = "你將要攻略古明地戀"
    elif cb_data == "marisa":
        img = "你將要攻略霧雨魔理沙"
    elif cb_data == "cirno":
        img = "你將要攻略琪露諾"
    else:
        raise RuntimeError("Unreachable")
    # 點(diǎn)擊按鈕之后,要對(duì)上方的文字進(jìn)行修改,替換成其它內(nèi)容
    # 所以這相當(dāng)于編輯已有消息,既然要編輯,那么除了 chat_id 之外還要指定 message_id
    # 因?yàn)槭腔卣{(diào),所以要多調(diào)用一次 callback_query
    message_id = update.callback_query.message.message_id
    chat_id = update.callback_query.message.chat.id
    # 調(diào)用 edit_message_media 方法,編輯消息
    await context.bot.edit_message_text(
        text=img,
        chat_id=chat_id,
        message_id=message_id,
        reply_markup=get_reply_markup()
    )

application = ApplicationBuilder().token(BOT_API_TOKEN).proxy(PROXY).build()
application.add_handler(
    CommandHandler("gogogo", add_button)
)
application.add_handler(
    CallbackQueryHandler(callback)
)
application.run_polling()

測(cè)試一下:

圖片圖片

然后點(diǎn)擊按鈕,看看文字內(nèi)容有沒(méi)有發(fā)生改變。

圖片圖片

點(diǎn)擊按鈕,文字的內(nèi)容被替換了。所以當(dāng)機(jī)器人回復(fù)一條消息時(shí),只需知道 chat_id 即可。但如果是修改某條消息,那么除了 chat_id 之外,還要知道 message_id。

修改文字調(diào)用的方法是 edit_message_text,但除了修改文字之外,還可以修改其它內(nèi)容。

圖片圖片

比如修改媒體文件,修改媒體文件的 caption,修改按鈕等等。

修改消息綜合案例

關(guān)于修改消息我們已經(jīng)知道怎么做了,下面來(lái)做一個(gè)綜合案例。假設(shè)當(dāng)前有 N 張圖片,用戶默認(rèn)會(huì)看到第一張,然后點(diǎn)擊按鈕可以查看下一張圖片,當(dāng)然也可以查看上一張。那么這個(gè)需求怎么實(shí)現(xiàn)呢?

from telegram import (
    Update,
    InlineKeyboardMarkup,
    InlineKeyboardButton,
    InputMediaPhoto
)
from telegram.ext import (
    ApplicationBuilder,
    ContextTypes,
    CommandHandler,
    CallbackQueryHandler,
)
from proxy import PROXY

BOT_API_TOKEN = "6485526535:AAEvGr9EDqtc4QPehkgohH6gczOTO5RIYRE"
# 這里我就用 4 張圖片為例
IMAGES = ["satori.png", "koishi.png", "marisa.png", "cirno.png"]

def get_navigation_buttons(index):
    reply_markup = InlineKeyboardMarkup([
        [InlineKeyboardButton(text="上一張", callback_data=f"prev:{index}"),
         InlineKeyboardButton(text="下一張", callback_data=f"next:{index}")],
    ])
    return reply_markup

async def get_pic(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # 默認(rèn)發(fā)送第一張圖片
    await context.bot.send_photo(
        chat_id=update.message.chat.id,
        photo=IMAGES[0],
        captinotallow=f"正在瀏覽第 1 / {len(IMAGES)} 張圖片",
        reply_markup=get_navigation_buttons(0)
    )

async def callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # 點(diǎn)擊按鈕,觸發(fā)回調(diào)
    op, index = update.callback_query.data.split(":")
    if op == "prev":
        index = (int(index) - 1) % len(IMAGES)
    else:  # op == "next"
        index = (int(index) + 1) % len(IMAGES)
    # int(index) 減 1 和加 1 之后,就是上一張圖片和下一張圖片的索引
    # 但這里又對(duì) len(IMAGES) 進(jìn)行取模,主要是為了實(shí)現(xiàn)循環(huán)瀏覽
    # 比如第一張的上一張會(huì)返回最后一張,最后一張的下一張會(huì)返回第一張
    await context.bot.edit_message_media(
        chat_id=update.callback_query.message.chat.id,
        message_id=update.callback_query.message.message_id,
        media=InputMediaPhoto(
            open(IMAGES[index], "rb"),
            captinotallow=f"正在瀏覽第 {index + 1} / {len(IMAGES)} 張圖片"
        ),
        reply_markup=get_navigation_buttons(index)
    )

application = ApplicationBuilder().token(BOT_API_TOKEN).proxy(PROXY).build()
application.add_handler(
    CommandHandler("get_pic", get_pic)
)
application.add_handler(
    CallbackQueryHandler(callback)
)
application.run_polling()

測(cè)試一下:

圖片圖片

此時(shí)點(diǎn)擊按鈕下一張,就會(huì)返回下一張圖片,同理也可以返回上一張圖片。如果已經(jīng)是最后一張圖片了,那么點(diǎn)擊下一張,會(huì)返回第一張圖片。

但問(wèn)題來(lái)了,程序要如何得知用戶正在瀏覽的是第幾張圖片呢?顯然要借助于按鈕。在創(chuàng)建按鈕時(shí),參數(shù) callback_data 里面保存了 index,當(dāng)點(diǎn)擊下一張或上一張時(shí),更新 index,返回新的圖片,同時(shí)刷新按鈕。

以上返回的是圖片,你也可以換成視頻,并增加一些點(diǎn)贊、是否喜歡等按鈕。

小結(jié)

以上就是 Python 操作 Telegram 相關(guān)的內(nèi)容,當(dāng)然這里只介紹了一部分,還有一些更復(fù)雜的功能沒(méi)有說(shuō),比如按鈕的嵌套等等。另外目前是用戶和機(jī)器人一對(duì)一私聊,但我們還可以創(chuàng)建一個(gè)組,讓機(jī)器人回復(fù)組成員的消息。而關(guān)于這些內(nèi)容,后續(xù)有空補(bǔ)上,本文就先到這兒,寫的有點(diǎn)累了。

責(zé)任編輯:武曉燕 來(lái)源: 古明地覺(jué)的編程教室
相關(guān)推薦

2023-10-13 09:00:00

2024-07-09 14:15:48

AIAgent機(jī)器人

2023-11-27 19:14:41

2021-08-13 11:33:18

Telegramrobocall機(jī)器人密碼

2021-07-22 10:17:55

加密機(jī)器人加密貨幣機(jī)器人

2021-05-07 13:20:39

Python機(jī)器人編程語(yǔ)言

2020-10-15 15:42:00

人工智能

2018-07-27 16:18:30

PythonTwitter機(jī)器人

2020-12-31 06:55:37

機(jī)器人自然語(yǔ)言人工智能

2021-08-19 15:44:20

機(jī)器人人工智能機(jī)器學(xué)習(xí)

2015-07-28 09:36:11

機(jī)器人

2017-03-28 12:21:21

機(jī)器人定義

2017-01-12 21:08:23

機(jī)器人開(kāi)源ROS

2009-06-18 08:00:50

機(jī)器人PR2馬拉松長(zhǎng)跑

2018-09-29 16:16:43

微軟機(jī)器人Windows

2017-03-07 16:10:36

腦控機(jī)器人糾錯(cuò)

2015-12-10 21:49:32

IM機(jī)器人

2022-07-22 16:36:23

協(xié)作機(jī)器人機(jī)器人

2012-03-08 09:42:16

開(kāi)源軟件Linux

2020-10-29 17:37:14

人工智能
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)