详解如何使用 Python 操作 Telegram(电报)机器人(二)

简介: 详解如何使用 Python 操作 Telegram(电报)机器人(二)

接上篇:https://developer.aliyun.com/article/1617543

媒体组



现在我们已经知道如何让机器人回复不同种类的消息了,但如果我想实现更复杂的功能,比如同时发送多张图片、多个视频,并且还配带文字,要怎么做呢?可能有人觉得这还不简单,写个循环不就行了,比如要发送 5 个视频,那么调用 5 次 send_video 方法不就好了。

首先这是一种方法,但循环 5 次,那么这 5 个视频是作为不同的消息分开发送的。更多时候,我们是希望作为一个整体发送,那么此时可以使用媒体组功能。

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 对象、文件句柄、file_id
        InputMediaPhoto(open('satori1.png', "rb"), caption="古"),
        InputMediaPhoto(open('satori2.png', "rb"), caption="明"),
        InputMediaPhoto(open('satori3.png', "rb"), caption="地"),
        InputMediaPhoto(open('satori4.png', "rb"), caption="觉")
    ]
    # 发送媒体组
    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,应该会返回 4 张图片。

b6def82170950d006986c6e8b5ed4f68.png

结果没有问题,并且这 4 张图片是整体作为一条消息发送的。然后我们在代码中还指定了一个 caption 参数,它是做什么的呢?我们点击一下图片就知道了。

e6861ec6365c4d3f74065768c73aad0d.png

点击图片放大查看时,captaion 会显示在图片下方。另外,如果发送了多张图片,但只有一张图片指定了 caption 参数,那么该 caption 会和图片一起显示,我们举例说明。

async def send_media_group(update: Update,
                           context: ContextTypes.DEFAULT_TYPE):
    caption = "+v ❥(^_-) 解锁地灵殿隐藏福利"
    media_group = [
        # 可以是 URL、bytes 对象、文件句柄、file_id
        InputMediaPhoto(open('satori1.png', "rb")),
        InputMediaPhoto(open('satori2.png', "rb")),
        InputMediaPhoto(open('satori3.png', "rb"), caption=caption),
        InputMediaPhoto(open('satori4.png', "rb"))
    ]
    # 发送媒体组
    await context.bot.send_media_group(
        chat_id=update.message.chat.id,
        media=media_group
    )

只有一张图片指定了 caption 参数,我们看看效果。

c33455a975830d72a629553985ff53f7.png

此时图片会和文字一起显示,当然你也可以不指定 caption 参数,而是在发送完图片之后,再调用一次 send_message。这种做法也是可以的,只不过此时图片和文字会作为两条消息分开显示。

以上是发送图片,除了图片之外还可以发送音频、视频、文档,并且只支持这 4 种。但要注意:它们不能混在一起发,只有图片和视频可以,我们测试一下。

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 = (
        "这游戏我玩不下去了,装备喂养和贴膜就算了,"
        "但自定义词条我是真忍不了,洗不出来,根本洗不出来。"
    )
    media_group = [
        InputMediaPhoto(open("satori1.png", "rb")),
        InputMediaVideo(open("DNF 装备销毁.mp4", "rb"), 
                        caption=video_caption),
        # 也支持发送音频和文档,但不能混在一起
        # InputMediaAudio(open("3rd eye.mp3", "rb")),
        # InputMediaDocument(open('OpenAI.pdf', 'rb'))
    ]
    # 发送媒体组
    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()

测试一下:

76a13642cafd00df2f1208a544a17735.png

结果正常,只是因为视频和图片是一起返回的,所以没有预览功能,需要点击之后才会播放。并且我们只给视频指定了 caption 参数,所以文字直接显示在了下方,如果媒体组中有多个 caption,那么就不会单独显示了,需要点击放大之后才能看到。

当然啦,如果你不需要同时发送多个媒体文件,那么就没必要调用 send_media_group 方法了,直接使用之前的方法即可。

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


这些方法一次性只能发送一个媒体文件,比如发送视频。

async def send_video(update: Update, context: ContextTypes.DEFAULT_TYPE):
    video_caption = (
        "这游戏我玩不下去了,装备喂养和贴膜就算了,"
        "但自定义词条我是真忍不了,洗不出来,根本洗不出来。"
    )
    await context.bot.send_video(
        chat_id=update.message.chat.id,
        video="DNF 装备销毁.mp4",
        caption=video_caption,
        # 让 caption 显示在上方,默认显示在下方
        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()

测试一下:

92c44029b024758d8e252aa36744cf58.png

怎么样,是不是很有趣呢?另外 caption 还可以是富文本,只需将 parse_mode 参数指定为 "HTML"、"Markdown" 或 "MarkdownV2" 即可。

关于机器人如何回复不同种类的消息,以及同时回复多条消息,相关内容我们就说完了。有了这些功能,我们的机器人就已经很强大了,你也可以把它和公司的业务结合起来。

比如创建一个命令:/get,它的功能如下。

d0228ec7406a529855469c34719f0d75.png

然后在代码中添加一个 CommandHandler("get", get_table),便可让用户通过 Telegram 查询数据库表,当然这里只是打个比方,具体怎么做取决于你的想法。另外多说一句,如果你希望输入 / 之后能像上面那样有提示,那么需要通过 BotFather 进行设置。

1c76d05adc765b952d61d695bab7d233.png

要强调的是,这种方式只是起到一个提示作用,提示机器人支持 /get 命令。但机器人实际上是否支持,取决于代码中是否为机器人实现了 /get。所以当我们在代码中为机器人添加完命令之后,可以再通过 Edit Commands 进行设置,这样当用户输入 / 之后,机器人有哪些命令以及描述都会显示出来。

当然啦,如果你不通过 Edit Commands 进行设置的话,也是可以的,只是用户输入 / 之后不会有提示罢了,但命令是会回复的,只要在代码中实现了。同理,如果通过 Edit Commands 设置了,但代码中没实现,那么该命令也不会有效果。


自定义按钮



虽然目前的机器人已经很强大了,但是还不够,我们看一下 BotFather。

4210a09a6b81488460c566cfab3fc037.png

你会发现它下面带了很多的按钮,点击按钮之后会执行相应的逻辑,那我们要怎么实现这些按钮呢?

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>,你最喜欢哪种编程语言呢?"
    # 设置按钮
    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()

测试一下:

d30e164585486fa53fb4aaa96f8ee422.png

此时按钮就实现了,由于在 InlineKeyboardButton 里面指定的是 url,所以这是跳转按钮,点击之后会打开指定的页面。并且按钮的右上角还有一个小箭头,表示按钮是跳转按钮。

但除了跳转按钮之外,还有回调按钮,也就是点击按钮之后会执行回调函数,我们举例说明。

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😂╭(╯^╰)╮"
    # 设置按钮
    reply_markup = InlineKeyboardMarkup([
        # 第一行,两个跳转按钮
        [InlineKeyboardButton(text="百度", url="https://www.baidu.com"),
         InlineKeyboardButton(text="谷歌", url="https://www.google.com"),],
        # 第二行,两个回调按钮
        [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):
    # 当点击回调按钮时,会执行相应的回调函数
    cb_data = update.callback_query.data  # 回调按钮中指定的 callback_data
    if cb_data == "youtube":
        text = "欢迎来到油管"
    elif cb_data == "bilibili":
        text = "欢迎来到 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)
)
# 处理回调的 Handler,否则点击按钮不会有效果
application.add_handler(
    CallbackQueryHandler(callback)
)
application.run_polling()

测试一下效果:

7ff82439e95c02dd0435fa0cf51f95d8.png

点击油管和 B站的时候会执行回调函数,结果没有问题。但是我们发现,这些文字是单独发送的,那可不可以本地修改呢,也就是将按钮上方的文字替换掉。答案是可以的,我们来测试一下。

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="古明地觉", 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 = "点击想要攻略的角色"
    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 = "你将要攻略古明地觉"
    elif cb_data == "koishi":
        img = "你将要攻略古明地恋"
    elif cb_data == "marisa":
        img = "你将要攻略雾雨魔理沙"
    elif cb_data == "cirno":
        img = "你将要攻略琪露诺"
    else:
        raise RuntimeError("Unreachable")
    # 点击按钮之后,要对上方的文字进行修改,替换成其它内容
    # 所以这相当于编辑已有消息,既然要编辑,那么除了 chat_id 之外还要指定 message_id
    # 因为是回调,所以要多调用一次 callback_query
    message_id = update.callback_query.message.message_id
    chat_id = update.callback_query.message.chat.id
    # 调用 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()

测试一下:

98626c4147a0b16fb391357c9965e2b3.png

然后点击按钮,看看文字内容有没有发生改变。

6d925ad47f819b91577f429513e591b6.png

点击按钮,文字的内容被替换了。所以当机器人回复一条消息时,只需知道 chat_id 即可。但如果是修改某条消息,那么除了 chat_id 之外,还要知道 message_id。

修改文字调用的方法是 edit_message_text,但除了修改文字之外,还可以修改其它内容。

8b800240b9dfc78b14db6bfba5e13669.png

比如修改媒体文件,修改媒体文件的 caption,修改按钮等等。


修改消息综合案例



关于修改消息我们已经知道怎么做了,下面来做一个综合案例。假设当前有 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):
    # 默认发送第一张图片
    await context.bot.send_photo(
        chat_id=update.message.chat.id,
        photo=IMAGES[0],
        caption=f"正在浏览第 1 / {len(IMAGES)} 张图片",
        reply_markup=get_navigation_buttons(0)
    )
async def callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # 点击按钮,触发回调
    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 之后,就是上一张图片和下一张图片的索引
    # 但这里又对 len(IMAGES) 进行取模,主要是为了实现循环浏览
    # 比如第一张的上一张会返回最后一张,最后一张的下一张会返回第一张
    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"),
            caption=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()

测试一下:

38734b401f314c66e477a492dfb0f674.png

此时点击按钮下一张,就会返回下一张图片,同理也可以返回上一张图片。如果已经是最后一张图片了,那么点击下一张,会返回第一张图片。

但问题来了,程序要如何得知用户正在浏览的是第几张图片呢?显然要借助于按钮。在创建按钮时,参数 callback_data 里面保存了 index,当点击下一张或上一张时,更新 index,返回新的图片,同时刷新按钮。

以上返回的是图片,你也可以换成视频,并增加一些点赞、是否喜欢等按钮。


小结



以上就是 Python 操作 Telegram 相关的内容,当然这里只介绍了一部分,还有一些更复杂的功能没有说,比如按钮的嵌套等等。另外目前是用户和机器人一对一私聊,但我们还可以创建一个组,让机器人回复组成员的消息。而关于这些内容,后续有空补上,本文就先到这儿,写的有点累了。

不知道什么原因,对技术的热情越来越冷淡,不像前几年刚毕业那会儿,看到有意思的,就会花时间去研究。但现在似乎没有当初的那种激情了,以前一篇文章三五万字轻轻松松,而现在刚写两万多字就感觉没有动力了,不过这也算是久违的长文了。

相关文章
|
2月前
|
JSON 机器人 API
详解如何使用 Python 操作 Telegram(电报)机器人(一)
详解如何使用 Python 操作 Telegram(电报)机器人(一)
260 8
|
2月前
|
人工智能 自然语言处理 机器人
用Python构建你的第一个聊天机器人
【10月更文挑战第7天】在这篇文章中,我们将一起探索如何利用Python编程语言和AI技术,一步步打造一个基础的聊天机器人。无论你是编程新手还是有一定经验的开发者,都能通过这个指南获得启发,并实现一个简单的对话系统。文章将引导你理解聊天机器人的工作原理,教你如何收集和处理用户输入,以及如何设计机器人的响应逻辑。通过动手实践,你不仅能够学习到编程技能,还能深入理解人工智能在语言处理方面的应用。
64 0
|
4月前
|
机器学习/深度学习 算法 机器人
使用Python实现深度学习模型:智能灾害响应与救援机器人
使用Python实现深度学习模型:智能灾害响应与救援机器人
81 16
|
4月前
|
机器学习/深度学习 人工智能 算法
用Python实现简单的聊天机器人
【8月更文挑战第31天】 本文将介绍如何使用Python语言和AIML库来实现一个简单的聊天机器人。我们将从基本的安装和配置开始,然后逐步深入到聊天机器人的实现过程。最后,我们将展示如何训练我们的机器人以使其更加智能。无论你是编程新手还是有经验的开发者,都可以从本文中获得实用的知识。
|
4月前
|
监控 机器人 Java
【python】调用钉钉机器人发起通知
【python】调用钉钉机器人发起通知
|
5月前
|
机器人
Telegram统计机器人源码/TG记账群发机器源码人/TG自动记账全开源版本
Telegram统计机器人源码/TG记账群发机器源码人/TG自动记账全开源版本
270 0
|
6月前
|
机器人 API 开发者
Python基于Mirai开发的QQ机器人保姆式教程(亲测可用)
Python基于Mirai开发的QQ机器人保姆式教程(亲测可用)