基于 MTProto 协议开发 Telegram 翻译机器人
本文档旨在指导开发者使用 MTProto 协议开发一个具备翻译功能的 Telegram 机器人。通过配置 Telegram 应用、添加必要的依赖项,并结合提供的 Java 代码示例,您可以实现一个高效且功能丰富的翻译机器人。
简介
Telegram 机器人是一种基于 Telegram 平台的自动化程序,能够与用户进行互动,实现各种功能。本文将介绍如何使用 MTProto 协议 开发一个支持翻译功能的 Telegram 机器人。与 HTTP 协议相比,MTProto 提供了更高的性能和安全性,适用于需要处理大量并发请求的应用场景。
准备工作
在开始开发 Telegram 机器人之前,需要完成一些前期准备工作,包括删除现有的 Webhook 地址和申请 Telegram API ID 及 Hash。
删除 Webhook 地址
如果您之前为机器人配置了 Webhook 地址,需要先将其删除,以便切换到 MTProto 协议进行开发。您可以通过以下步骤删除 Webhook:
- 打开 Telegram 应用,找到您的机器人。
- 发送
/deletewebhook
命令给 BotFather。 - BotFather 将确认删除 Webhook,并通知您操作结果。
申请 Telegram API ID 和 Hash
为了使用 MTProto 协议开发 Telegram 机器人,您需要申请 Telegram API ID 和 Hash。请按照以下步骤操作:
- 访问 Telegram API 网站。
- 使用您的 Telegram 账户登录。
- 填写应用名称、简短描述和您的应用平台等信息。
- 提交申请后,您将获得
api_id
和api_hash
,请妥善保存。
项目配置
添加依赖
为了开发 Telegram 机器人,您需要在项目中添加必要的依赖项。以下是使用 Maven 管理依赖的示例配置:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.litongjava.gpt</groupId>
<artifactId>translator</artifactId>
<version>1.0.0</version>
<properties>
<!-- 项目属性 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>
<dependencies>
<!-- Telegram4J 核心库 -->
<dependency>
<groupId>com.telegram4j</groupId>
<artifactId>telegram4j-core</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
<!-- Jackson Core 库,用于 JSON 处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.15.2</version>
</dependency>
<!-- Reactor Core 库,用于响应式编程 -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.5.0</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>s01.oss.sonatype.org-snapshot</id>
<url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</project>
依赖说明:
- telegram4j-core:Telegram4J 的核心库,用于与 Telegram API 进行交互。
- jackson-core:用于处理 JSON 数据。
- reactor-core:Reactor 项目的核心库,提供响应式编程支持。
确保您的项目使用 Java 17 及以上版本,以兼容 Telegram4J 和其他依赖库。
开发实现
本文档提供了三个关键的 Java 类,用于实现 Telegram 机器人的配置、消息过滤和翻译功能。
TelegramTranslateBot
TelegramTranslateBot
类负责初始化 Telegram 客户端,配置事件监听器,并管理客户端的生命周期。
package com.litongjava.gpt.translator.config;
import java.time.Duration;
import com.litongjava.annotation.AConfiguration;
import com.litongjava.annotation.Initialization;
import com.litongjava.gpt.translator.predicate.UserPredicate;
import com.litongjava.gpt.translator.tgfunc.TranslateFunction;
import com.litongjava.tio.boot.server.TioBootServer;
import com.litongjava.tio.utils.environment.EnvUtils;
import lombok.extern.slf4j.Slf4j;
import reactor.util.retry.Retry;
import telegram4j.core.MTProtoTelegramClient;
import telegram4j.core.event.domain.message.SendMessageEvent;
import telegram4j.mtproto.RpcException;
@Slf4j
@AConfiguration
public class TelegramTranslateBot {
@Initialization
public void config() {
// 从环境变量获取 Telegram API 配置信息
int apiId = EnvUtils.getInt("telegram.api.id");
String apiHash = EnvUtils.getStr("telegram.api.hash");
String botAuthToken = EnvUtils.getStr("telegram.bot.auth.token");
// 创建并连接 MTProto Telegram 客户端
MTProtoTelegramClient client = MTProtoTelegramClient.create(apiId, apiHash, botAuthToken).connect().block();
if (client == null) {
log.error("Failed to connect to Telegram MTProto client.");
return;
}
// 配置事件监听器:接收 SendMessageEvent,过滤用户消息,并应用翻译功能
client.on(SendMessageEvent.class)
//
.filter(new UserPredicate()::test).flatMap(new TranslateFunction()::apply)
//
.delayElements(Duration.ofSeconds(1)) // 每秒最多发送一条消息
//
.doOnError(e -> log.error("处理消息时发生错误", e))
//
.retryWhen(Retry.backoff(5, Duration.ofSeconds(1)).filter(e -> e instanceof RpcException))
//
.subscribe();
// 防止主线程阻塞,监控客户端断开连接
// TioThreadUtils.submit(() -> {
// client.onDisconnect().block();
// log.info("Telegram client disconnected.");
// });
// 在服务器关闭时,断开 Telegram 客户端连接
TioBootServer.me().addDestroyMethod(client::disconnect);
log.info("Telegram MTProto client configured and connected.");
}
}
代码解析:
初始化配置:
- 从环境变量中获取
api_id
、api_hash
和bot_auth_token
。 - 创建并连接 MTProto Telegram 客户端。
- 从环境变量中获取
事件监听与处理:
- 监听
SendMessageEvent
事件。 - 使用
UserPredicate
过滤非用户发送的消息,确保机器人只处理来自真实用户的消息。 - 使用
TranslateFunction
对消息进行翻译处理,并发送回复。
- 监听
客户端生命周期管理:
- 使用
TioThreadUtils
提交一个任务,监控客户端的断开连接事件。 - 在服务器关闭时,确保客户端正常断开连接,释放资源。
- 使用
UserPredicate
UserPredicate
类用于过滤消息事件,确保机器人只处理来自真实用户的消息,避免处理自己或其他机器人的消息。
package com.litongjava.gpt.translator.predicate;
import java.util.Optional;
import java.util.function.Predicate;
import lombok.extern.slf4j.Slf4j;
import telegram4j.core.event.domain.message.SendMessageEvent;
import telegram4j.core.object.MentionablePeer;
import telegram4j.core.object.chat.Chat;
@Slf4j
public class UserPredicate implements Predicate<SendMessageEvent> {
@Override
public boolean test(SendMessageEvent event) {
log.info("接收到事件: {}", event);
Optional<MentionablePeer> optional = event.getAuthor();
Optional<Chat> chat = event.getChat();
// 防止机器人处理自己发送的消息
// 如果消息的发送者信息不存在,说明可能是机器人自身发送的消息,忽略
if (optional.isEmpty()) {
log.info("消息发送者信息缺失,可能是机器人自身发送的消息。");
return false;
}
if (chat.isEmpty()) {
log.info("消息聊天信息缺失,可能是机器人自身发送的消息。");
return false;
}
return true;
}
}
TranslateFunction
TranslateFunction
类负责处理过滤后的消息,执行翻译操作,并将翻译结果发送回用户。
package com.litongjava.gpt.translator.tgfunc;
import org.reactivestreams.Publisher;
import com.litongjava.gpt.translator.services.TranslatorService;
import com.litongjava.gpt.translator.vo.TranslatorTextVo;
import com.litongjava.jfinal.aop.Aop;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;
import telegram4j.core.event.domain.message.SendMessageEvent;
import telegram4j.core.object.Message;
import telegram4j.core.object.chat.Chat;
import telegram4j.core.spec.SendMessageSpec;
@Slf4j
public class TranslateFunction {
/**
* 处理 SendMessageEvent,执行翻译并发送回复
*
* @param event 发送消息事件
* @return 翻译后的消息 Publisher
*/
public Publisher<? extends Message> apply(SendMessageEvent event) {
// 获取 Chat 对象,如果不存在则从消息中获取
Mono<Chat> chatMono = Mono.justOrEmpty(event.getChat()).switchIfEmpty(event.getMessage().getChat());
// 处理 Chat 和消息发送
return chatMono.flatMap(chat -> processAndSendMessage(chat, event.getMessage()));
}
/**
* 处理消息内容,执行翻译并发送回复
*
* @param chat 聊天对象
* @param message 原始消息对象
* @return 翻译后的消息 Publisher
*/
private Mono<Message> processAndSendMessage(Chat chat, Message message) {
String text = message.getContent();
// 根据文本内容设置源语言和目标语言
String srcLang = containsChinese(text) ? "Chinese" : "English";
String destLang = containsChinese(text) ? "English" : "Chinese";
// 创建翻译请求对象
TranslatorTextVo translatorTextVo = new TranslatorTextVo();
translatorTextVo.setSrcText(text);
translatorTextVo.setSrcLang(srcLang);
translatorTextVo.setDestLang(destLang);
String chatId = chat.getId().toString();
String responseText;
try {
// 调用翻译服务
responseText = Aop.get(TranslatorService.class).translate(chatId, translatorTextVo);
} catch (Exception e) {
log.error("翻译服务调用异常", e);
responseText = "翻译服务出现问题,请稍后再试。";
}
// 构建发送消息规范
SendMessageSpec messageSpec = SendMessageSpec.of(responseText);
// 发送翻译结果回 Telegram
Mono<Message> sendMessage = chat.sendMessage(messageSpec);
return sendMessage;
}
/**
* 判断文本中是否包含中文字符
*
* @param text 输入的文本
* @return 如果包含中文字符则返回 true,否则返回 false
*/
private boolean containsChinese(String text) {
if (text == null || text.isEmpty()) {
return false;
}
// 使用正则表达式检查是否包含中文字符
return text.matches(".*[\\u4e00-\\u9fa5]+.*");
}
}
异步处理的优势
在开发 Telegram 翻译机器人时,采用异步处理机制具有显著的优势,特别是在处理大量并发请求和提高应用性能方面。以下是异步处理的主要优势及其在本项目中的应用。
异步处理的优势
提高并发性和吞吐量: 异步处理允许程序在等待某些操作(如 I/O 操作、网络请求)完成时,继续执行其他任务。这种非阻塞的执行方式显著提高了应用的并发处理能力和整体吞吐量。
提升响应速度: 通过异步处理,机器人可以同时处理多个用户的请求,而不会因为单个请求的延迟而影响整体响应速度。这对于需要实时响应的聊天机器人尤为重要。
资源利用率更高: 异步模型避免了线程在等待期间的空闲状态,从而更高效地利用系统资源,减少不必要的线程切换和上下文切换开销。
增强系统的可扩展性: 异步架构使系统更容易扩展,能够处理更大规模的用户请求,而无需线性增加资源。
Mono 与 flatMap 的异步处理
在本项目中,使用了 Reactor 提供的 Mono
类来实现异步处理。Mono
是 Reactor 中的一个关键类型,代表一个异步计算过程,最终可能会产生一个结果或一个错误。
flatMap
的作用:
flatMap
方法用于将一个 Mono
的结果转换为另一个 Mono
,并将其展开。这在需要进行一系列异步操作时尤为有用,因为它允许您在前一个异步操作完成后,基于其结果执行下一个异步操作。
在本项目中的应用:
事件处理链: 在
TelegramTranslateBot
类中,事件监听器通过以下链式调用进行配置:client.on(SendMessageEvent.class) .filter(new UserPredicate()::test) .flatMap(new TranslateFunction()::apply) .subscribe();
这里,
client.on(SendMessageEvent.class)
返回一个Flux<SendMessageEvent>
,表示接收到的消息事件流。通过filter
方法筛选出符合条件的事件后,flatMap
将每个事件传递给TranslateFunction
的apply
方法进行处理。翻译操作的异步执行: 在
TranslateFunction
类中,apply
方法返回一个Publisher<? extends Message>
,具体来说,是一个Mono<Message>
。通过flatMap
,每个消息事件被异步处理,执行翻译并发送回复。public Publisher<? extends Message> apply(SendMessageEvent event) { Mono<Chat> chatMono = Mono.justOrEmpty(event.getChat()).switchIfEmpty(event.getMessage().getChat()); return chatMono.flatMap(chat -> processAndSendMessage(chat, event.getMessage())); }
processAndSendMessage
方法内部,同样采用Mono
来处理翻译服务的调用和消息发送:private Mono<Message> processAndSendMessage(Chat chat, Message message) { // ... 省略部分代码 ... Mono<Message> sendMessage = chat.sendMessage(messageSpec); return sendMessage; }
通过 flatMap
的使用,整个消息处理流程实现了非阻塞、异步的执行。这意味着机器人能够同时处理多个翻译请求,而不会因某个请求的延迟而阻塞其他请求的处理。这不仅提升了机器人的响应速度,还提高了系统的整体性能和稳定性。
消息格式
在开发和调试过程中,了解消息的结构有助于更好地处理和解析消息内容。以下是用户发送的消息和机器人回复的消息的格式化 JSON 示例。
用户发送的消息
格式化后的 JSON:
{
"timestamp": "2024-12-02T21:27:46.776",
"thread": "t4j-events-2",
"level": "INFO",
"logger": "c.l.g.t.p.UserPredicate.test",
"message": {
"type": "SendMessageEvent",
"message": {
"data": {
"variant": "Variant2",
"content": {
"type": "BaseMessage",
"id": 1247,
"flags": 0,
"fromId": null,
"peerId": {
"type": "PeerUser",
"userId": "user_id"
},
"fwdFrom": null,
"viaBotId": null,
"replyTo": null,
"date": 1733210883,
"message": "Function",
"media": null,
"replyMarkup": null,
"entities": null,
"views": null,
"forwards": null,
"replies": null,
"editDate": null,
"postAuthor": null,
"groupedId": null,
"reactions": null,
"restrictionReason": null,
"ttlPeriod": null
}
}
},
"chat": {
"type": "PrivateChat",
"user": {
"minData": {
"type": "BaseUser",
"id": "user_id",
"accessHash": -6745242867060598026,
"firstName": "user_name",
"lastName": null,
"username": "user_name",
"phone": null,
"photo": {
"type": "BaseUserProfilePhoto",
"flags": 0,
"photoId": 5129962608709971087,
"strippedThumb": null,
"dcId": 1
},
"status": {
"type": "UserStatusRecently"
},
"botInfoVersion": null,
"restrictionReason": null,
"botInlinePlaceholder": null,
"langCode": "zh-hans",
"emojiStatus": null,
"usernames": null,
"storiesMaxId": null
},
"fullData": null
},
"selfUser": {
"minData": {
"type": "BaseUser",
"id": "user_id",
"accessHash": -4262855346709871929,
"firstName": "my-translator",
"lastName": null,
"username": "litongjava_bot",
"phone": null,
"photo": null,
"status": null,
"botInfoVersion": 1,
"restrictionReason": null,
"botInlinePlaceholder": null,
"langCode": null,
"emojiStatus": null,
"usernames": null,
"storiesMaxId": null
},
"fullData": null
}
},
"author": {
"minData": {
"type": "BaseUser",
"id": "user_id",
"accessHash": -6745242867060598026,
"firstName": "",
"lastName": null,
"username": "user_name",
"phone": null,
"photo": {
"type": "BaseUserProfilePhoto",
"flags": 0,
"photoId": 5129962608709971087,
"strippedThumb": null,
"dcId": 1
},
"status": {
"type": "UserStatusRecently"
},
"botInfoVersion": null,
"restrictionReason": null,
"botInlinePlaceholder": null,
"langCode": "zh-hans",
"emojiStatus": null,
"usernames": null,
"storiesMaxId": null
},
"fullData": null
}
},
"additionalInfo": "Sql: select \"dst_text\" from \"max_kb_sentence_tanslate_cache\" where \"md5\" = ?"
}
机器人回复的消息
格式化后的 JSON:
{
"timestamp": "2024-12-02T21:27:46.936",
"thread": "t4j-events-2",
"level": "INFO",
"logger": "c.l.g.t.p.UserPredicate.test",
"message": {
"type": "SendMessageEvent",
"message": {
"data": {
"variant": "Variant2",
"content": {
"type": "BaseMessage",
"id": 1248,
"flags": 100000010,
"fromId": {
"type": "PeerUser",
"userId": "user_id"
},
"peerId": {
"type": "PeerUser",
"userId": "user_id"
},
"fwdFrom": null,
"viaBotId": null,
"replyTo": null,
"date": 1733210883,
"message": "功能",
"media": null,
"replyMarkup": null,
"entities": null,
"views": null,
"forwards": null,
"replies": null,
"editDate": null,
"postAuthor": null,
"groupedId": null,
"reactions": null,
"restrictionReason": null,
"ttlPeriod": null
}
}
},
"chat": null,
"author": null
}
}
对比与分析
通过上述格式化后的 JSON,可以更清晰地对比用户发送的消息和机器人回复的消息:
发送者 (
author
) 区别:- 用户发送的消息:
author
字段包含用户的详细信息。fromId
为null
,但通过author
可以获取到发送者的用户 ID。
- 机器人发送的消息:
author
字段为null
。fromId
包含机器人的用户 ID ("user_id"
)。
- 用户发送的消息:
聊天 (
chat
) 区别:- 用户发送的消息:
chat
字段包含PrivateChat
对象,详细描述了聊天双方的信息。
- 机器人发送的消息:
chat
字段为null
,这可能表示机器人发送的消息不关联特定的聊天上下文,或者在日志记录时未捕获到相关信息。
- 用户发送的消息:
消息内容 (
message
) 区别:- 用户发送的消息:
- 内容为
"Function"
。
- 内容为
- 机器人发送的消息:
- 内容为
"功能"
。
- 内容为
- 用户发送的消息:
其他字段:
- 用户发送的消息:
- 包含更多关于用户的详细信息,如
firstName
、username
、photo
等。
- 包含更多关于用户的详细信息,如
- 机器人发送的消息:
author
和chat
字段为null
,只包含基本的消息信息。
- 用户发送的消息:
测试与运行
完成上述配置和开发后,即可通过 Telegram 与机器人进行对话,测试翻译功能。以下是测试步骤:
启动服务器:
- 确保您的服务器正在运行,并且 MTProto Telegram 客户端已成功连接。
- 检查日志,确认客户端已连接且事件监听器已配置。
与机器人对话:
- 在 Telegram 中找到您的机器人,发送需要翻译的文本。
- 例如,发送
"你好,世界!"
或"Good morning!"
。
查看翻译结果:
机器人将根据文本内容自动检测源语言,并返回翻译后的文本。
例如:
用户发送:
你好,世界!
机器人回复:
Hello, World!
用户发送:
Good morning!
机器人回复:
早上好!
检查日志:
- 查看服务器日志,确保消息处理流程正常,没有异常或错误。
- 通过日志可以追踪消息的接收、翻译和回复过程。
示例
用户发送:
今天天气怎么样?
机器人回复:
How is the weather today?
用户发送:
What's the capital of France?
机器人回复:
法国的首都是哪里?
总结
本文档详细介绍了如何使用 MTProto 协议开发一个具备翻译功能的 Telegram 机器人。通过以下步骤,您可以快速搭建并测试自己的翻译机器人:
- 准备工作:删除现有 Webhook 地址,申请 Telegram API ID 和 Hash。
- 项目配置:添加必要的依赖项,确保项目使用 Java 17 及以上版本。
- 开发实现:编写配置类、消息过滤器和翻译功能模块,确保机器人能够正确处理和翻译消息。
- 异步处理:利用 Reactor 的
Mono
和flatMap
实现异步处理,提高机器人的并发处理能力和响应速度。 - 测试与运行:启动服务器,与机器人对话,验证翻译功能的有效性。
通过采用异步处理机制,您的 Telegram 翻译机器人将具备更高的性能和可扩展性,能够高效应对大量并发请求,提供稳定可靠的服务体验。希望本文档能够帮助您顺利开发出功能强大的翻译机器人。