<!-- Source: https://telegram-bot.io.nexus/telegram4j/03 -->

# 基于 MTProto 协议开发 Telegram 翻译机器人

::: danger 不推荐使用
笔者不推荐使用 Telegram4J：它有并发问题，服务总是挂掉。新项目请使用 [telegram-bot-base](<https://telegram-bot.io.nexus/telegram-bot-base/>)。本章仅作为历史记录保留。
:::


[[toc]]

本文档旨在指导开发者使用 MTProto 协议开发一个具备翻译功能的 Telegram 机器人。通过配置 Telegram 应用、添加必要的依赖项，并结合提供的 Java 代码示例，您可以实现一个高效且功能丰富的翻译机器人。

## 简介

Telegram 机器人是一种基于 Telegram 平台的自动化程序，能够与用户进行互动，实现各种功能。本文将介绍如何使用 **MTProto 协议** 开发一个支持翻译功能的 Telegram 机器人。与 HTTP 协议相比，MTProto 提供了更高的性能和安全性，适用于需要处理大量并发请求的应用场景。

## 准备工作

在开始开发 Telegram 机器人之前，需要完成一些前期准备工作，包括删除现有的 Webhook 地址和申请 Telegram API ID 及 Hash。

### 删除 Webhook 地址

如果您之前为机器人配置了 Webhook 地址，需要先将其删除，以便切换到 MTProto 协议进行开发。您可以通过以下步骤删除 Webhook：

1. 打开 Telegram 应用，找到您的机器人。
2. 发送 `/deletewebhook` 命令给 [BotFather](https://t.me/BotFather)。
3. BotFather 将确认删除 Webhook，并通知您操作结果。

### 申请 Telegram API ID 和 Hash

为了使用 MTProto 协议开发 Telegram 机器人，您需要申请 Telegram API ID 和 Hash。请按照以下步骤操作：

1. 访问 [Telegram API 网站](https://core.telegram.org/api/obtaining_api_id)。
2. 使用您的 Telegram 账户登录。
3. 填写应用名称、简短描述和您的应用平台等信息。
4. 提交申请后，您将获得 `api_id` 和 `api_hash`，请妥善保存。

## 项目配置

### 添加依赖

为了开发 Telegram 机器人，您需要在项目中添加必要的依赖项。以下是使用 Maven 管理依赖的示例配置：

```xml
<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>nexus.io.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 客户端，配置事件监听器，并管理客户端的生命周期。

```java
package nexus.io.gpt.translator.config;

import java.time.Duration;

import nexus.io.annotation.AConfiguration;
import nexus.io.annotation.Initialization;
import nexus.io.gpt.translator.predicate.UserPredicate;
import nexus.io.gpt.translator.tgfunc.TranslateFunction;
import nexus.io.tio.boot.server.TioBootServer;
import nexus.io.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 客户端连接
    HookCan.me().addDestroyMethod(client::disconnect);

    log.info("Telegram MTProto client configured and connected.");
  }
}
```

**代码解析：**

1. **初始化配置**：

   - 从环境变量中获取 `api_id`、`api_hash` 和 `bot_auth_token`。
   - 创建并连接 MTProto Telegram 客户端。

2. **事件监听与处理**：

   - 监听 `SendMessageEvent` 事件。
   - 使用 `UserPredicate` 过滤非用户发送的消息，确保机器人只处理来自真实用户的消息。
   - 使用 `TranslateFunction` 对消息进行翻译处理，并发送回复。

3. **客户端生命周期管理**：

   - 使用 `TioThreadUtils` 提交一个任务，监控客户端的断开连接事件。
   - 在服务器关闭时，确保客户端正常断开连接，释放资源。

### UserPredicate

`UserPredicate` 类用于过滤消息事件，确保机器人只处理来自真实用户的消息，避免处理自己或其他机器人的消息。

```java
package nexus.io.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` 类负责处理过滤后的消息，执行翻译操作，并将翻译结果发送回用户。

```java
package nexus.io.gpt.translator.tgfunc;

import org.reactivestreams.Publisher;

import nexus.io.gpt.translator.services.TranslatorService;
import nexus.io.gpt.translator.vo.TranslatorTextVo;
import nexus.io.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 翻译机器人时，采用异步处理机制具有显著的优势，特别是在处理大量并发请求和提高应用性能方面。以下是异步处理的主要优势及其在本项目中的应用。

#### 异步处理的优势

1. **提高并发性和吞吐量**：
   异步处理允许程序在等待某些操作（如 I/O 操作、网络请求）完成时，继续执行其他任务。这种非阻塞的执行方式显著提高了应用的并发处理能力和整体吞吐量。

2. **提升响应速度**：
   通过异步处理，机器人可以同时处理多个用户的请求，而不会因为单个请求的延迟而影响整体响应速度。这对于需要实时响应的聊天机器人尤为重要。

3. **资源利用率更高**：
   异步模型避免了线程在等待期间的空闲状态，从而更高效地利用系统资源，减少不必要的线程切换和上下文切换开销。

4. **增强系统的可扩展性**：
   异步架构使系统更容易扩展，能够处理更大规模的用户请求，而无需线性增加资源。

#### Mono 与 flatMap 的异步处理

在本项目中，使用了 Reactor 提供的 `Mono` 类来实现异步处理。`Mono` 是 Reactor 中的一个关键类型，代表一个异步计算过程，最终可能会产生一个结果或一个错误。

**`flatMap` 的作用**：

`flatMap` 方法用于将一个 `Mono` 的结果转换为另一个 `Mono`，并将其展开。这在需要进行一系列异步操作时尤为有用，因为它允许您在前一个异步操作完成后，基于其结果执行下一个异步操作。

**在本项目中的应用**：

1. **事件处理链**：
   在 `TelegramTranslateBot` 类中，事件监听器通过以下链式调用进行配置：

   ```java
   client.on(SendMessageEvent.class)
         .filter(new UserPredicate()::test)
         .flatMap(new TranslateFunction()::apply)
         .subscribe();
   ```

   这里，`client.on(SendMessageEvent.class)` 返回一个 `Flux<SendMessageEvent>`，表示接收到的消息事件流。通过 `filter` 方法筛选出符合条件的事件后，`flatMap` 将每个事件传递给 `TranslateFunction` 的 `apply` 方法进行处理。

2. **翻译操作的异步执行**：
   在 `TranslateFunction` 类中，`apply` 方法返回一个 `Publisher<? extends Message>`，具体来说，是一个 `Mono<Message>`。通过 `flatMap`，每个消息事件被异步处理，执行翻译并发送回复。

   ```java
   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` 来处理翻译服务的调用和消息发送：

   ```java
   private Mono<Message> processAndSendMessage(Chat chat, Message message) {
     // ... 省略部分代码 ...

     Mono<Message> sendMessage = chat.sendMessage(messageSpec);
     return sendMessage;
   }
   ```

通过 `flatMap` 的使用，整个消息处理流程实现了非阻塞、异步的执行。这意味着机器人能够同时处理多个翻译请求，而不会因某个请求的延迟而阻塞其他请求的处理。这不仅提升了机器人的响应速度，还提高了系统的整体性能和稳定性。

## 常见问题

---

### 问题一：`event.getChat()` 已经可以获取 `Chat`，为什么还需要使用 `event.getMessage().getChat()`？

在代码中，我们看到以下行：

```java
Mono<Chat> chatMono = Mono.justOrEmpty(event.getChat())
                          .switchIfEmpty(event.getMessage().getChat());
```

**解释：**

1. **`event.getChat()` 可能为空：**

   虽然 `event.getChat()` 通常可以直接获取到消息所在的 `Chat` 对象，但在某些情况下，它可能返回空值（`null` 或 `Optional.empty()`）。这是因为 `SendMessageEvent` 中的 `getChat()` 方法并不总是保证有值，具体取决于事件的来源和 Telegram 的数据结构。

   在 Telegram4J 的事件模型中，`SendMessageEvent` 可能来自不同的上下文，有时 `Chat` 信息可能不可用或未初始化。例如，对于某些类型的消息（如来自频道或匿名群组的消息），`event.getChat()` 可能无法直接获取到 `Chat` 对象。

2. **使用 `event.getMessage().getChat()` 作为备用：**

   为了确保我们始终能够获取到 `Chat` 对象，代码中使用了 `switchIfEmpty` 方法：

   ```java
   Mono.justOrEmpty(event.getChat())
       .switchIfEmpty(event.getMessage().getChat());
   ```

   - **`Mono.justOrEmpty(event.getChat())`：** 尝试从事件中直接获取 `Chat` 对象。
   - **`switchIfEmpty(...)`：** 如果上一步返回了空（即 `event.getChat()` 为空），则调用 `event.getMessage().getChat()` 尝试从消息对象中获取 `Chat`。

   这样做的目的是增加获取 `Chat` 对象的可靠性，确保无论 `event.getChat()` 是否为空，我们都有机会从 `event.getMessage().getChat()` 中获取到 `Chat`。

   event.getMessage().getChat()的返回类型是 "Mono Chat",因为这方法会执行网络请求获取 chat 信息

**总结：**

- **可靠性和健壮性：** 使用这种方式可以提高代码的健壮性，防止因 `Chat` 对象为空而导致的空指针异常（`NullPointerException`）。
- **兼容性：** 由于 Telegram 的消息事件可能来自不同的上下文，确保在各种情况下都能正确获取 `Chat` 对象是良好实践。

---

### 问题二：`chat.sendMessage` 为什么要放到 `flatMap` 中？

在代码中，`processAndSendMessage` 方法中有以下部分：

```java
return chatMono.flatMap(chat -> processAndSendMessage(chat, event.getMessage()));
```

并且在 `processAndSendMessage` 方法中：

```java
Mono<Message> sendMessage = chat.sendMessage(messageSpec);
return sendMessage;
```

**解释：**

1. **Reactive Streams 与异步操作：**

   在响应式编程（Reactive Programming）中，`Mono` 和 `Flux` 用于表示异步的数据流。`Mono<T>` 表示一个包含单个元素的异步数据流，而 `Flux<T>` 表示包含多个元素的异步数据流。

2. **`flatMap` 的作用：**

   - **异步序列的转换：** `flatMap` 方法用于将一个元素转换为另一个包含异步结果的 `Mono` 或 `Flux`，然后将这些异步序列扁平化，生成一个新的 `Mono` 或 `Flux`。
   - **链式异步操作：** 当有一个依赖于前一个异步操作结果的后续异步操作时，使用 `flatMap` 可以将它们串联起来，确保前一个操作完成后再执行下一个操作。

3. **为什么不能用 `map`：**

   - 如果使用 `map`，返回的类型将是 `Mono<Mono<Message>>`，这是一个嵌套的 `Mono`，不符合我们的需求。
   - `flatMap` 会自动帮我们解除一层嵌套，将 `Mono<Mono<Message>>` 扁平化为 `Mono<Message>`。

4. **在本例中的应用：**

   - **获取 `Chat` 对象后发送消息：** 我们需要先获取到 `Chat` 对象，然后才能调用 `chat.sendMessage(messageSpec)` 方法发送消息。
   - **`chat.sendMessage(messageSpec)` 返回 `Mono<Message>`：** 发送消息本身是一个异步操作，返回一个 `Mono<Message>`，表示消息发送完成后返回的消息对象。
   - **使用 `flatMap` 链接操作：** 通过 `chatMono.flatMap(...)`，我们可以在获取到 `Chat` 对象后，继续执行发送消息的异步操作，并将最终结果作为一个 `Mono<Message>` 返回。

**代码执行流程：**

1. **`chatMono`：** 一个异步的 `Mono Chat`，表示获取 `Chat` 对象的过程。
2. **`flatMap`：** 在 `Chat` 对象获取成功后，执行 `processAndSendMessage` 方法。
3. **`processAndSendMessage` 方法：**
   - 处理消息内容，执行翻译逻辑。
   - 调用 `chat.sendMessage(messageSpec)` 发送翻译后的消息，返回 `Mono<Message>`。

**示意图：**

```
Mono<Chat> --flatMap--> Mono<Message>
```

**为什么需要这样做：**

- **保持异步执行：** 因为上面的获取的是 Mono Chat,确保所有操作都是非阻塞和异步的，充分利用响应式编程的优势，提高性能和资源利用率。
- **正确处理依赖关系：** 由于发送消息需要依赖于前一步获取的 `Chat` 对象，使用 `flatMap` 可以确保在 `Chat` 对象可用后再执行发送消息的操作。
- **避免嵌套和回调地狱：** 使用 `flatMap` 可以保持代码的简洁和可读性，避免嵌套过深的回调。

**示例：**

假设不用 `flatMap`，而是直接写：

```java
Mono<Mono<Message>> messageMono = chatMono.map(chat -> chat.sendMessage(messageSpec));
```

这将导致 `messageMono` 的类型是 `Mono<Mono<Message>>`，这并不是我们想要的，因为我们需要的是 `Mono<Message>`。

---

### 问题 1 和问题 2 总结

- **关于 `event.getChat()` 和 `event.getMessage().getChat()`：**

  - 为了提高代码的健壮性和可靠性，我们在尝试获取 `Chat` 对象时，先尝试使用 `event.getChat()`，如果获取失败（为空），再使用 `event.getMessage().getChat()` 作为备用。
  - 这是为了确保在各种可能的事件上下文中，都能正确获取到 `Chat` 对象，防止因空值导致的异常。

- **关于在 `flatMap` 中使用 `chat.sendMessage`：**
  - `flatMap` 用于将依赖于前一个异步结果的后续异步操作串联起来，确保前一个操作完成后再执行下一个操作。
  - 在本例中，我们需要在获取到 `Chat` 对象后，才能发送消息，而发送消息本身是一个异步操作，返回 `Mono<Message>`。
  - 使用 `flatMap` 可以将整个操作链保持为异步和非阻塞的，同时避免嵌套的 `Mono`，保持代码简洁和可读性。

## 消息格式

在开发和调试过程中，了解消息的结构有助于更好地处理和解析消息内容。以下是用户发送的消息和机器人回复的消息的格式化 JSON 示例。

### 用户发送的消息

**格式化后的 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：**

```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，可以更清晰地对比用户发送的消息和机器人回复的消息：

1. **发送者 (`author`) 区别**：

   - **用户发送的消息**：
     - `author` 字段包含用户的详细信息。
     - `fromId` 为 `null`，但通过 `author` 可以获取到发送者的用户 ID。
   - **机器人发送的消息**：
     - `author` 字段为 `null`。
     - `fromId` 包含机器人的用户 ID (`"user_id"`)。

2. **聊天 (`chat`) 区别**：

   - **用户发送的消息**：
     - `chat` 字段包含 `PrivateChat` 对象，详细描述了聊天双方的信息。
   - **机器人发送的消息**：
     - `chat` 字段为 `null`，这可能表示机器人发送的消息不关联特定的聊天上下文，或者在日志记录时未捕获到相关信息。

3. **消息内容 (`message`) 区别**：

   - **用户发送的消息**：
     - 内容为 `"Function"`。
   - **机器人发送的消息**：
     - 内容为 `"功能"`。

4. **其他字段**：

   - **用户发送的消息**：
     - 包含更多关于用户的详细信息，如 `firstName`、`username`、`photo` 等。
   - **机器人发送的消息**：
     - `author` 和 `chat` 字段为 `null`，只包含基本的消息信息。

## 测试与运行

完成上述配置和开发后，即可通过 Telegram 与机器人进行对话，测试翻译功能。以下是测试步骤：

1. **启动服务器**：

   - 确保您的服务器正在运行，并且 MTProto Telegram 客户端已成功连接。
   - 检查日志，确认客户端已连接且事件监听器已配置。

2. **与机器人对话**：

   - 在 Telegram 中找到您的机器人，发送需要翻译的文本。
   - 例如，发送 `"好，世界！"` 或 `"Good morning!"`。

3. **查看翻译结果**：

   - 机器人将根据文本内容自动检测源语言，并返回翻译后的文本。
   - 例如：

     - **用户发送**：`好，世界！`
     - **机器人回复**：`Hello, World!`

     - **用户发送**：`Good morning!`
     - **机器人回复**：`早上好！`

4. **检查日志**：

   - 查看服务器日志，确保消息处理流程正常，没有异常或错误。
   - 通过日志可以追踪消息的接收、翻译和回复过程。

### 示例

- **用户发送**：`今天天气怎么样？`
- **机器人回复**：`How is the weather today?`

- **用户发送**：`What's the capital of France?`
- **机器人回复**：`法国的首都是哪里？`

## 总结

本文档详细介绍了如何使用 MTProto 协议开发一个具备翻译功能的 Telegram 机器人。通过以下步骤，您可以快速搭建并测试自己的翻译机器人：

1. **准备工作**：删除现有 Webhook 地址，申请 Telegram API ID 和 Hash。
2. **项目配置**：添加必要的依赖项，确保项目使用 Java 17 及以上版本。
3. **开发实现**：编写配置类、消息过滤器和翻译功能模块，确保机器人能够正确处理和翻译消息。
4. **异步处理**：利用 Reactor 的 `Mono` 和 `flatMap` 实现异步处理，提高机器人的并发处理能力和响应速度。
5. **测试与运行**：启动服务器，与机器人对话，验证翻译功能的有效性。

通过采用异步处理机制，您的 Telegram 翻译机器人将具备更高的性能和可扩展性，能够高效应对大量并发请求，提供稳定可靠的服务体验。希望本文档能够帮助您顺利开发出功能强大的翻译机器人。
