# Telegram Media Downloader — 产品需求文档（PRD）

**文档版本：** 1.0  
**项目版本：** 2.2.5  
**撰写日期：** 2026-04-06  
**项目地址：** https://github.com/tangyoha/telegram_media_downloader

---

## 目录

1. [产品概述](#1-产品概述)
2. [目标用户](#2-目标用户)
3. [使用场景](#3-使用场景)
4. [核心功能](#4-核心功能)
5. [项目架构](#5-项目架构)
6. [目录结构](#6-目录结构)
7. [核心模块详解](#7-核心模块详解)
8. [配置项完整说明](#8-配置项完整说明)
9. [下载过滤器语法](#9-下载过滤器语法)
10. [Web 界面 API 接口](#10-web-界面-api-接口)
11. [数据结构](#11-数据结构)
12. [下载目录结构](#12-下载目录结构)
13. [安装与运行](#13-安装与运行)
14. [依赖说明](#14-依赖说明)
15. [当前限制与已知问题](#15-当前限制与已知问题)
16. [优化建议与迭代方向](#16-优化建议与迭代方向)

---

## 1. 产品概述

### 1.1 产品定位

Telegram Media Downloader 是一款基于 Python 的 Telegram 媒体批量下载工具。它通过 Telegram 官方 MTProto API（pyrogram 客户端）连接 Telegram，能够从指定频道、群组或私聊中批量下载多种媒体类型（视频、图片、音频、文档、语音等），并提供配套的 Web 可视化管理界面和 Telegram Bot 控制接口。

### 1.2 核心价值

- **批量高效**：支持对多个频道并发处理，断点续传，不重复下载已有文件。
- **精细过滤**：通过类 SQL 表达式对消息日期、文件大小、文件名、视频分辨率、标题内容等进行精确筛选。
- **多端控制**：提供 Web 界面（浏览器访问）和 Telegram Bot 两种控制方式，无需每次修改配置文件。
- **可扩展存储**：下载完成后可自动上传到 Telegram 指定聊天，或通过 Rclone 上传到 100+ 种云存储（Google Drive、OneDrive、阿里云盘等）。
- **容器化支持**：完整的 Docker/Docker-Compose 支持，易于部署和隔离运行。

### 1.3 版本说明

当前版本为 **2.2.5**（见 `utils/__init__.py`）。该版本已经历多次重构，核心下载引擎稳定，Web 界面为近期新增功能，仍处于持续迭代阶段。

---

## 2. 目标用户

| 用户类型 | 使用场景 | 技术门槛 |
|---------|---------|---------|
| 内容收藏者 | 批量下载 Telegram 频道的学习资料、影视资源、图片合集 | 低（Web 界面操作） |
| 内容创作者 | 从特定群组归档历史素材 | 低 |
| 开发者/研究者 | 数据采集、内容存档 | 中（需配置 API） |
| 运营人员 | 多频道内容监控、素材备份 | 中 |
| 服务器管理员 | 无头服务器 + Docker 自动化归档 | 高 |

**前提要求：**

1. 拥有 Telegram 账号
2. 在 [my.telegram.org/apps](https://my.telegram.org/apps) 申请个人 API 凭证（免费，约 1 分钟）
3. 目标频道/群组可访问（公开频道或已加入的私有群组）
4. 中国大陆用户需配置代理（Telegram 在大陆被屏蔽）

---

## 3. 使用场景

### 场景 A：一次性归档某个频道的历史内容

用户希望把某个影视资源 Telegram 频道自 2023 年以来发布的所有 MP4 视频下载到本地。

**操作流程：**
1. 打开 Web 界面，在顶部表单输入频道名（如 `@example_channel`）
2. 设置开始日期 `2023-01-01`，验证频道有效性
3. 在 `config.yaml` 中配置 `file_formats.video: [mp4]`（或在界面中设置）
4. 点击「保存并重启下载」，程序自动遍历频道历史消息，过滤并下载所有 MP4 文件
5. 下载完成后，文件按 `频道名/年月/` 目录结构整理好

---

### 场景 B：持续监听新频道更新

用户希望每次运行都只下载上次未下载的新内容（增量同步）。

**机制：** 每次下载完成后，程序自动把当前最大消息 ID 写入 `config.yaml` 的 `last_read_message_id` 字段，下次运行从该 ID 之后继续，不重复下载。

---

### 场景 C：通过 Telegram Bot 远程控制

用户在服务器上部署程序，配置 `bot_token`，通过向 Bot 发送命令来添加下载任务、查看进度，无需 SSH 进服务器。

---

### 场景 D：高清视频筛选下载

频道中混合了低清和高清视频，用户只想要 1080p 及以上的内容。

**配置过滤器：**
```yaml
download_filter: media_width >= 1920 and media_height >= 1080
```

---

### 场景 E：大文件上传至云盘

本地磁盘空间有限，下载后自动通过 Rclone 上传到 Google Drive，并删除本地文件。

---

## 4. 核心功能

### 4.1 媒体批量下载

- **支持媒体类型：** audio（音频）、photo（图片）、video（视频）、document（文档/PDF/压缩包等）、voice（语音消息）、video_note（圆形视频消息）、animation（GIF）
- **文件格式过滤：** 对每种媒体类型可配置允许的格式列表，`all` 表示不限制
- **并发下载：** 通过 asyncio 队列管理，支持配置最大并发任务数（`max_download_task`）和最大并发传输数（`max_concurrent_transmissions`）
- **断点续传：** 通过 `last_read_message_id` 记录上次处理到的消息位置，下次启动自动从该位置继续
- **去重跳过：** 已存在于目标路径且大小一致的文件自动跳过，不重复下载
- **临时目录机制：** 下载先写入 `temp/` 目录，完成后移动到最终位置，避免半完成文件污染

### 4.2 下载过滤器

基于 PLY（Python Lex-Yacc）实现的表达式过滤引擎，支持：
- 按消息日期筛选（最常用）
- 按文件大小筛选
- 按视频分辨率筛选
- 按文件名正则匹配
- 按消息标题关键字筛选
- 按视频时长筛选
- 逻辑组合：`and`、`or`

详见 [第9节：下载过滤器语法](#9-下载过滤器语法)。

### 4.3 Web 管理界面

- **顶部操作栏（横排）：** 频道输入+验证、开始/结束日期、保存路径编辑、保存并重启按钮
- **实时统计头部：** 下载速度、正在下载数、已完成数、已跳过数
- **当前任务横幅：** 显示正在处理的频道名及当前状态
- **历史频道网格：** 记录历史使用的频道，点击可快速填入表单
- **下载进度列表：** 实时展示每个文件的下载进度条、速度
- **已完成列表：** 展示已完成下载的文件名和大小
- **暂停/继续控制：** 随时暂停或恢复下载任务
- **新用户引导向导：** 首次使用时显示 3 步引导（API 凭证获取、代理配置、确认），无需手动编辑配置文件

### 4.4 Telegram Bot 控制

通过配置 `bot_token` 启用机器人模式：
- 向 Bot 发送频道链接，Bot 自动添加下载任务
- 发送命令查看当前下载进度
- 转发消息给 Bot，Bot 自动下载其中的媒体
- 权限控制（可限制只有特定用户能使用）

### 4.5 云存储上传

| 功能 | 说明 |
|------|------|
| 上传到 Telegram 聊天 | 下载完成后转发到指定 Telegram 聊天/频道 |
| Rclone 上传 | 支持所有 Rclone 兼容的云存储（Google Drive、OneDrive、S3、阿里云盘等） |
| 阿里云盘（Aligo）| 专用适配器，支持阿里云盘 API |
| 上传前压缩 | 可配置上传前对文件进行 ZIP 压缩 |
| 上传后删除本地 | 上传成功后自动删除本地文件，节省磁盘空间 |

### 4.6 多语言支持

支持界面/日志语言：英文（EN）、中文（ZH）、俄文（RU）、乌克兰文（UA），通过 `config.yaml` 中 `language` 字段配置。

### 4.7 容器化部署

提供完整 Dockerfile 和 docker-compose.yaml，支持：
- 单命令部署 `docker-compose up -d`
- 数据卷挂载（配置文件、下载目录）
- 端口映射（Web 界面）

---

## 5. 项目架构

### 5.1 技术栈

| 层级 | 技术 |
|------|------|
| Telegram 通信 | pyrogram（自定义 fork，支持最新 TDLib） |
| 异步框架 | asyncio（Python 原生） |
| Web 服务 | Flask 2.2.2 + Flask-Login |
| 配置存储 | YAML（ruamel.yaml 保持格式） |
| 过滤器引擎 | PLY（Python Lex-Yacc） |
| 前端 | 原生 HTML/CSS/JS（无框架，深色主题） |
| 加密 | pycryptodome（AES-128-CBC） |
| 日志 | loguru + rich |
| 云存储 | Rclone（子进程调用）、aligo |

### 5.2 整体架构图

```
┌─────────────────────────────────────────────────────────┐
│                   media_downloader.py                    │
│                     （主进程入口）                        │
│                                                         │
│  ┌──────────────┐  ┌──────────────┐  ┌───────────────┐ │
│  │  Application │  │  asyncio     │  │  Flask Web    │ │
│  │  配置管理     │  │  事件循环     │  │  Server       │ │
│  └──────────────┘  └──────┬───────┘  └───────────────┘ │
│                           │                             │
│          ┌────────────────┼──────────────────┐          │
│          ▼                ▼                  ▼          │
│   ┌─────────────┐  ┌────────────┐  ┌──────────────┐   │
│   │  下载主流程  │  │  Worker    │  │  Bot 处理     │   │
│   │  (每个聊天)  │  │  队列处理  │  │  (可选)       │   │
│   └──────┬──────┘  └─────┬──────┘  └──────────────┘   │
│          │               │                             │
│          ▼               ▼                             │
│   ┌─────────────────────────────────┐                  │
│   │         pyrogram.Client          │                 │
│   │      (Telegram MTProto API)      │                 │
│   └────────────────┬────────────────┘                  │
└────────────────────┼────────────────────────────────────┘
                     │
              ┌──────┴──────┐
              │  Telegram   │
              │  服务器      │
              └─────────────┘
```

### 5.3 核心数据流

```
程序启动
  │
  ├─ 加载 config.yaml → Application 初始化
  │
  ├─ 启动 Flask Web Server（端口 5001）
  │
  ├─ 建立 Telegram 连接（pyrogram.Client.start()）
  │    └─ 首次运行：提示手机号 + 验证码（终端交互）
  │
  └─ download_all_chat() 主循环
       │
       ├─ 对每个 chat（config.yaml 中的 chat 列表）：
       │    ├─ get_chat_history_v2() → 逐批获取历史消息（每批 100 条）
       │    ├─ exec_filter() → 应用过滤表达式
       │    ├─ 通过时 → add_download_task() → 压入 asyncio.Queue
       │    └─ 记录 last_read_message_id
       │
       └─ Worker 并发处理队列
            ├─ _get_media_meta() → 提取文件名、大小等元数据
            ├─ 检查文件是否已存在 → 跳过
            ├─ download_media() → 下载到 temp/ 目录
            ├─ _check_download_finish() → 校验完整性
            ├─ _move_to_download_path() → 移动到最终路径
            ├─ [可选] 上传到 Telegram 或云盘
            └─ update_download_status() → 更新 Web 统计数据

程序结束
  ├─ 等待所有队列任务完成
  ├─ 更新 config.yaml 中的 last_read_message_id
  └─ 如 restart_program=True → 重启进程（os.execv）
```

---

## 6. 目录结构

```
telegram_media_downloader/
├── media_downloader.py         # 主入口，程序启动和下载编排
├── config.yaml                 # 用户配置文件（核心）
├── requirements.txt            # Python 依赖列表
├── setup.py                    # 打包配置
├── Makefile                    # 快捷命令（install/test/lint）
├── Dockerfile                  # Docker 镜像构建文件
├── docker-compose.yaml         # Docker 编排配置
├── gen_filter_cache.py         # 过滤器缓存预生成脚本
├── README.md                   # 英文文档
├── README_CN.md                # 中文文档
├── PRD.md                      # 本文档
│
├── module/                     # 核心业务模块
│   ├── __init__.py
│   ├── app.py                  # Application 类、任务节点、配置管理
│   ├── web.py                  # Flask Web 服务、所有 HTTP API
│   ├── bot.py                  # Telegram Bot 处理逻辑
│   ├── filter.py               # PLY 词法/语法分析过滤引擎
│   ├── download_stat.py        # 下载速度统计、任务进度追踪
│   ├── get_chat_history_v2.py  # 聊天历史异步生成器（优化版）
│   ├── pyrogram_extension.py   # pyrogram 扩展（钩子、进度、上传）
│   ├── send_media_group_v2.py  # 媒体组批量转发
│   ├── cloud_drive.py          # 云盘上传适配器（Rclone/Aligo）
│   ├── language.py             # 多语言翻译字典
│   ├── parsetab.py             # PLY 自动生成的解析表（勿手动修改）
│   ├── parser.out              # PLY 解析调试信息
│   ├── templates/              # Flask HTML 模板
│   │   ├── index.html          # 主界面（含向导、下载控制）
│   │   ├── login.html          # 登录页面
│   │   └── control.html        # 旧版控制页面（已由 index.html 统一）
│   └── static/                 # Web 静态资源
│       ├── css/
│       ├── layui/              # Layui 前端框架（旧版残留）
│       ├── request/            # JS 请求库
│       ├── login/              # 登录页样式
│       └── aes/                # AES 加密 JS 库
│
├── utils/                      # 通用工具类库
│   ├── __init__.py             # 版本号定义（__version__ = "2.2.5"）
│   ├── crypto.py               # AES-128-CBC 加密解密
│   ├── file_management.py      # 文件操作辅助函数
│   ├── format.py               # 字节格式化、日期时间处理、URL 解析
│   ├── log.py                  # werkzeug 日志过滤器
│   ├── meta.py                 # 元数据打印工具
│   ├── meta_data.py            # MetaData 类（过滤器属性集合）
│   ├── platform.py             # 跨平台（Windows/Linux/macOS）检测
│   └── updates.py              # 版本更新检查
│
├── tests/                      # 单元测试
│   ├── test_common.py
│   ├── test_media_downloader.py
│   ├── module/
│   │   └── test_app.py
│   └── utils/
│       ├── test_cypto.py
│       ├── test_filter.py
│       ├── test_file_management.py
│       ├── test_format.py
│       ├── test_log.py
│       ├── test_meta.py
│       └── test_updates.py
│
└── temp/                       # 下载临时目录（运行时自动创建）
```

---

## 7. 核心模块详解

### 7.1 `media_downloader.py` — 主入口

**职责：** 程序生命周期管理、异步任务编排、下载流程控制。

| 函数 | 说明 |
|------|------|
| `main()` | 程序主入口，创建 pyrogram 客户端，启动 Web/Bot 服务，进入下载主循环 |
| `run_with_restart()` | 包装 main()，支持程序自动重启（`_app.restart_program = True` 时触发） |
| `download_all_chat()` | 遍历 config 中所有聊天，依次调用 `download_chat_task` |
| `download_chat_task()` | 单个聊天的完整下载流程：获取历史 → 过滤 → 入队 |
| `download_task()` | 处理单条消息的下载：元数据提取 → 去重 → 下载 → 移动 → 上传 |
| `download_media()` | 调用 pyrogram API 执行实际文件下载，写入 temp/ |
| `worker()` | asyncio Queue 消费者，循环从队列取任务并调用 `download_task` |
| `add_download_task()` | 将消息包装为任务节点，压入 asyncio.Queue |
| `_get_media_meta()` | 从 pyrogram Message 对象提取文件名、大小、扩展名等元数据 |
| `save_msg_to_file()` | 将纯文本消息保存为 .txt 文件（需配置 `enable_download_txt`） |

**重启机制：**
```python
# 任意地方设置此标志：
_app.restart_program = True

# run_with_restart() 检测到此标志后执行：
os.execv(sys.executable, [sys.executable] + sys.argv)
```

---

### 7.2 `module/app.py` — 应用配置管理

**职责：** 配置文件的读取/写入、任务节点数据结构定义、过滤器执行。

#### 核心枚举

```python
class DownloadState(Enum):
    Downloading = 1     # 正在下载
    StopDownload = 2    # 已停止（暂停）

class DownloadStatus(Enum):
    SkipDownload = 1    # 已跳过（文件已存在）
    SuccessDownload = 2 # 下载成功
    FailedDownload = 3  # 下载失败
    Downloading = 4     # 下载中
```

#### 核心类

**`ChatDownloadConfig`** — 单个聊天的下载配置：

| 属性 | 类型 | 说明 |
|------|------|------|
| `chat_id` | str/int | 聊天标识符 |
| `last_read_message_id` | int | 最后读取的消息ID（断点续传依据） |
| `download_filter` | str | 下载过滤表达式 |
| `ids_to_retry` | list | 失败需重试的消息ID列表 |

**`TaskNode`** — 单次下载任务的运行时状态：

| 属性 | 类型 | 说明 |
|------|------|------|
| `chat_id` | str/int | 所属聊天 |
| `total_task` | int | 本次任务总消息数 |
| `download_status` | dict | {message_id: DownloadStatus} |
| `is_running` | bool | 是否正在运行 |
| `upload_success_count` | int | 上传成功计数 |

**`Application`** — 全局应用实例：

| 属性 | 说明 |
|------|------|
| `config` | 完整的 YAML 配置字典 |
| `chat_download_config` | {chat_id: ChatDownloadConfig} |
| `save_path` | 下载根目录路径 |
| `api_id / api_hash` | Telegram API 凭证 |
| `media_types` | 要下载的媒体类型列表 |
| `file_formats` | 各媒体类型的格式白名单 |
| `file_path_prefix` | 目录结构组件列表 |
| `file_name_prefix` | 文件名前缀组件列表 |
| `proxy` | 代理配置字典 |
| `restart_program` | 重启标志（设为 True 触发重启） |
| `cloud_drive_config` | 云盘配置 |

---

### 7.3 `module/web.py` — Web 服务

**职责：** Flask HTTP 服务、用户认证、前端 API、配置管理、频道历史。

**初始化方式（依赖注入）：**

```python
def init_web(app, client, add_download_task_func, download_chat_task_func):
    """
    由 media_downloader.py 在启动时调用，注入全局依赖：
    - app: Application 实例
    - client: pyrogram.Client 实例
    - add_download_task_func: 添加下载任务的回调
    - download_chat_task_func: 下载聊天任务的回调
    """
```

**频道历史存储：** 保存在 `module/channel_history.json`，结构如下：

```json
[
  {
    "chat_id": "example_channel",
    "chat_title": "示例频道",
    "chat_type": "CHANNEL",
    "last_used": "2026-04-06T12:00:00"
  }
]
```

**登录认证：**
- 密码在 `config.yaml` 的 `web_login_secret` 字段配置（明文，传输时 AES 加密）
- 未配置 `web_login_secret` 时自动跳过登录（本地开发模式）
- 使用 Flask-Login 管理会话

---

### 7.4 `module/filter.py` — 过滤引擎

**职责：** 将过滤表达式字符串解析并编译为可执行的过滤函数。

**技术实现：** 使用 PLY（Python Lex-Yacc）构建词法分析器和语法分析器，将表达式 AST 转换为 Python 可调用对象。

**支持的可过滤属性（`utils/meta_data.py` 中 `MetaData` 类定义）：**

| 属性名 | 数据类型 | 说明 |
|--------|---------|------|
| `message_date` | datetime | 消息发送时间 |
| `message_id` | int | 消息 ID |
| `media_file_size` | int | 文件大小（字节） |
| `media_width` | int | 图片/视频宽度（像素） |
| `media_height` | int | 图片/视频高度（像素） |
| `media_file_name` | str | 文件名（含扩展名） |
| `message_caption` | str | 消息说明文字 |
| `media_duration` | int | 视频/音频时长（秒） |
| `sender_id` | int | 发送者用户 ID |
| `sender_name` | str | 发送者名称 |
| `reply_to_message_id` | int | 被回复的消息 ID |

---

### 7.5 `module/download_stat.py` — 下载统计

**职责：** 全局下载速度计算、任务进度数据的维护（供 Web API 消费）。

**核心全局数据（`_download_result`）：**

```python
_download_result: dict = {
    "chat_id_1": {
        message_id_1: {
            "down_byte":      已下载字节数（int）,
            "total_size":     文件总字节数（int）,
            "file_name":      完整文件路径（str）,
            "download_speed": 当前速度（bytes/s，int）,
            "start_time":     开始时间戳（float）,
        }
    }
}
```

**任务进度数据（`_task_progress`）：**

```python
_task_progress: dict = {
    "current_chat":       当前处理的聊天 ID（str）,
    "current_chat_title": 当前聊天标题（str）,
    "checked_messages":   已检查的消息总数（int）,
    "skipped_files":      已跳过的文件数（int）,
    "downloading_files":  正在下载的文件数（int）,
    "completed_files":    已完成的文件数（int）,
    "failed_files":       失败的文件数（int）,
    "is_checking":        是否正在扫描消息（bool）,
}
```

---

### 7.6 `module/pyrogram_extension.py` — pyrogram 扩展

**职责：** 扩展 pyrogram 客户端功能，包括下载进度回调、上传功能、权限检查。

**核心功能：**

1. **`HookClient`** — 继承 `pyrogram.Client`，支持自定义钩子：
   - `on_download_start` — 下载开始时回调
   - `on_download_progress` — 下载进度更新回调
   - `on_download_finish` — 下载完成回调

2. **`record_download_status`** — 装饰器，自动将下载进度写入 `download_stat.py` 的统计数据。

3. **上传功能：**
   - `upload_telegram_chat()` — 上传文件到指定 Telegram 聊天
   - 通过 cloud_drive.py 上传到云存储

4. **`get_extension(file_id, mime_type)`** — 根据 Telegram 文件 ID 或 MIME 类型推断文件扩展名。

5. **`check_user_permission(user_id)`** — 检查用户是否在白名单中（Bot 模式权限控制）。

---

### 7.7 `module/cloud_drive.py` — 云盘上传

**支持适配器：**

| 适配器 | 说明 | 配置字段 |
|--------|------|---------|
| `rclone` | 通用云存储（100+ 服务） | `upload_adapter: rclone` |
| `aligo` | 阿里云盘专用 | `upload_adapter: aligo` |

**配置示例（config.yaml）：**

```yaml
cloud_drive_config:
  enable_upload_file: true
  upload_adapter: rclone         # 或 aligo
  remote_dir: "gdrive:/telegram" # Rclone 远程路径
  before_upload_file_zip: false  # 上传前是否压缩
  after_upload_file_delete: true # 上传成功后删除本地文件
```

---

## 8. 配置项完整说明

配置文件路径：`config.yaml`（项目根目录）

### 8.1 必填项

| 字段 | 类型 | 示例 | 说明 |
|------|------|------|------|
| `api_id` | int | `12345678` | Telegram API ID，从 my.telegram.org 获取 |
| `api_hash` | str | `"abcdef1234..."` | Telegram API Hash，32位字符串 |
| `chat` | list | 见下方 | 下载目标配置列表 |
| `save_path` | str | `"/Users/xx/Downloads/tg"` | 文件保存根目录（绝对路径） |

### 8.2 聊天配置（`chat` 列表元素）

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `chat_id` | str/int | — | 频道/群组用户名（`@xxx`或`xxx`）或数字 ID |
| `last_read_message_id` | int | `0` | 上次读取到的消息 ID，断点续传依据，程序自动更新 |
| `download_filter` | str | 无 | 过滤表达式，见第9节 |

### 8.3 媒体类型和格式

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `media_types` | list | `[audio, photo, video, document]` | 要下载的媒体类型 |
| `file_formats.audio` | list | `[all]` | 允许的音频格式，`all` 不限制 |
| `file_formats.video` | list | `[all]` | 允许的视频格式 |
| `file_formats.document` | list | `[all]` | 允许的文档格式 |

**有效媒体类型值：** `audio`、`photo`、`video`、`document`、`voice`、`video_note`、`animation`

### 8.4 文件路径配置

| 字段 | 类型 | 示例 | 说明 |
|------|------|------|------|
| `file_path_prefix` | list | `[chat_title, media_datetime]` | 目录层级结构组件，顺序即目录嵌套顺序 |
| `file_name_prefix` | list | `[message_id]` | 文件名前缀组件 |

**`file_path_prefix` 可用值：**
- `chat_title` — 频道/群组名称
- `media_datetime` — 媒体发布的年月（格式 `2024_01`）
- `media_type` — 媒体类型（audio/video/photo/document）

**`file_name_prefix` 可用值：**
- `message_id` — 消息 ID
- `caption` — 消息说明文字（截断处理）
- `chat_title` — 聊天标题

### 8.5 Web 服务配置

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `web_host` | str | `"127.0.0.1"` | Web 服务绑定地址，服务器部署改为 `"0.0.0.0"` |
| `web_port` | int | `5000` | Web 服务端口 |
| `web_login_secret` | str | 无（跳过登录） | Web 界面登录密码，AES 加密传输 |

### 8.6 代理配置

| 字段 | 类型 | 说明 |
|------|------|------|
| `proxy.scheme` | str | 代理类型：`socks5` 或 `http` |
| `proxy.hostname` | str | 代理服务器地址，本地代理填 `"127.0.0.1"` |
| `proxy.port` | int | 代理端口（Clash SOCKS5: 7891，HTTP: 7890；V2Ray: 10808/10809） |

**常见代理软件对应配置：**

| 代理软件 | SOCKS5 端口 | HTTP 端口 | 推荐配置 |
|---------|------------|----------|---------|
| Clash | 7891 | 7890 | `scheme: socks5, port: 7891` |
| 狗子云（VPN模式） | 无 | 7890 | `scheme: http, port: 7890` |
| V2Ray | 10808 | 10809 | `scheme: socks5, port: 10808` |
| Shadowsocks | 1080 | — | `scheme: socks5, port: 1080` |

### 8.7 性能配置

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `max_download_task` | int | `5` | 最大并发下载任务数 |
| `max_concurrent_transmissions` | int | `1` | 单个文件的并发分片传输数 |

### 8.8 其他配置

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `language` | str | `"EN"` | 界面语言：`EN`/`ZH`/`RU`/`UA` |
| `log_level` | str | `"INFO"` | 日志级别：`DEBUG`/`INFO`/`WARNING`/`ERROR` |
| `enable_download_txt` | bool | `false` | 是否将纯文本消息保存为 .txt 文件 |
| `hide_file_name` | bool | `false` | Web 界面是否隐藏下载文件名（隐私模式） |
| `bot_token` | str | 无 | Telegram Bot Token，启用 Bot 模式 |
| `allowed_user_ids` | list | 无（不限制） | 允许使用 Bot 的用户 ID 白名单 |

---

## 9. 下载过滤器语法

过滤器配置在 `config.yaml` 的 `chat[i].download_filter` 字段中。

### 9.1 基本语法

```
<属性名> <操作符> <值> [and/or <条件2>]
```

### 9.2 操作符

| 操作符 | 适用类型 | 示例 |
|--------|---------|------|
| `>=` | 数字、日期 | `media_file_size >= 1024` |
| `<=` | 数字、日期 | `message_date <= 2024-12-31 23:59:59` |
| `>` | 数字、日期 | `media_width > 1920` |
| `<` | 数字、日期 | `media_duration < 3600` |
| `==` | 数字、字符串 | `sender_id == 123456` |
| `!=` | 数字、字符串 | `sender_id != 123456` |
| `matches` | 字符串 | `media_file_name matches ".*\.mp4$"` |
| `contains` | 字符串 | `message_caption contains "4K"` |

### 9.3 逻辑组合

```
# AND：两个条件都满足
message_date >= 2024-01-01 00:00:00 and message_date <= 2024-12-31 23:59:59

# OR：任一条件满足
media_file_name matches ".*\.mp4$" or media_file_name matches ".*\.mkv$"

# 复合条件
message_date >= 2024-01-01 00:00:00 and media_file_size >= 10485760
```

### 9.4 完整示例

```yaml
# 示例1：只下载 2024 年的内容
download_filter: message_date >= 2024-01-01 00:00:00 and message_date <= 2024-12-31 23:59:59

# 示例2：只下载大于 10MB 的文件
download_filter: media_file_size >= 10485760

# 示例3：只下载 1080p 及以上的视频
download_filter: media_width >= 1920 and media_height >= 1080

# 示例4：只下载文件名包含特定字符串的文件
download_filter: media_file_name matches ".*\\.720p.*"

# 示例5：只下载标题含关键字的消息
download_filter: message_caption contains "完整版"

# 示例6：下载特定时间段内的大文件视频
download_filter: message_date >= 2023-06-01 00:00:00 and media_file_size >= 52428800 and media_duration >= 600
```

### 9.5 日期格式规范

日期必须使用格式：`YYYY-MM-DD HH:MM:SS`，例如：
- `2024-01-18 00:00:00`（起始日期，时间设 00:00:00）
- `2024-12-31 23:59:59`（结束日期，时间设 23:59:59）

---

## 10. Web 界面 API 接口

Base URL：`http://127.0.0.1:5001`（默认，端口可配置）

### 10.1 无需认证的接口

#### `GET /api/setup_status`
检测当前配置完成度，用于判断是否需要显示新用户引导向导。

**响应：**
```json
{
  "has_api_credentials": true,   // api_id 和 api_hash 是否已配置且有效
  "has_session": true,            // 是否已有 Telegram 登录会话文件
  "has_chat": true,               // 是否已配置了至少一个聊天
  "proxy": {
    "scheme": "http",
    "hostname": "127.0.0.1",
    "port": 7890
  },
  "save_path": "/Users/xx/Downloads/tg"
}
```

#### `POST /api/save_initial_config`
保存初始配置（新用户引导第3步使用），无需登录。

**请求体：**
```json
{
  "api_id": 12345678,
  "api_hash": "abcdef...",
  "proxy_enabled": true,
  "proxy_scheme": "http",
  "proxy_hostname": "127.0.0.1",
  "proxy_port": 7890,
  "save_path": "/Users/xx/Downloads/tg"
}
```

**响应：**
```json
{ "success": true, "message": "配置已保存，程序将重启" }
```

#### `GET /get_app_version`
**响应：** `"2.2.5"`（纯文本）

---

### 10.2 需要认证的接口

所有以下接口在设置了 `web_login_secret` 时需要先登录，否则返回 302 重定向到 `/login`。

#### `GET /get_download_status`
获取实时下载速度和状态。

**响应：**
```json
{
  "download_speed": "1.23 MB/s",
  "upload_speed": "0.00 B/s",
  "state": "pause"          // "pause" = 下载中，"continue" = 已暂停
}
```

---

#### `GET /api/task_progress`
获取当前任务详细进度。

**响应：**
```json
{
  "current_chat": "example_channel",
  "current_chat_title": "示例频道",
  "checked_messages": 1234,
  "skipped_files": 567,
  "downloading_files": 3,
  "completed_files": 890,
  "failed_files": 2,
  "is_checking": false
}
```

---

#### `POST /set_download_state?state=pause`
暂停或继续下载。

| state 参数 | 效果 |
|-----------|------|
| `pause` | 暂停下载 |
| `continue` | 继续下载 |

**响应：** `"pause"` 或 `"continue"`（纯文本，表示操作后的新状态）

---

#### `GET /api/get_config`
获取当前配置信息（供 Web 界面初始化表单）。

**响应：**
```json
{
  "chat_id": "example_channel",
  "download_filter": "message_date >= 2024-01-01 00:00:00",
  "save_path": "/Users/xx/Downloads/tg"
}
```

---

#### `POST /api/validate_chat`
验证频道/群组是否可访问（连通 Telegram 服务器查询）。

**请求体：**
```json
{ "chat_id": "example_channel" }
```

**响应（成功）：**
```json
{
  "valid": true,
  "chat_id": "example_channel",
  "chat_title": "示例频道",
  "chat_type": "CHANNEL"    // CHANNEL / SUPERGROUP / GROUP / PRIVATE
}
```

**响应（失败）：**
```json
{
  "valid": false,
  "error": "Chat not found"
}
```

---

#### `POST /api/save_path`
修改文件保存路径（立即生效，无需重启）。

**请求体：**
```json
{ "save_path": "/new/save/path" }
```

**响应：**
```json
{ "success": true, "save_path": "/new/save/path" }
```

---

#### `POST /api/save_and_restart`
保存下载配置并重启程序（最常用操作）。

**请求体：**
```json
{
  "chat_id": "example_channel",
  "start_date": "2024-01-01",
  "end_date": "2024-12-31",       // 可为空
  "chat_title": "示例频道",
  "chat_type": "CHANNEL"
}
```

**响应：**
```json
{ "success": true, "message": "配置已保存，程序正在重启" }
```

---

#### `POST /api/start_download`
动态启动下载任务（**不重启程序**，直接将任务加入当前运行队列）。

**请求体（同 save_and_restart）**

**响应：**
```json
{ "success": true, "message": "下载任务已启动" }
```

---

#### `GET /api/channel_history`
获取频道历史记录列表（按最近使用排序）。

**响应：**
```json
{
  "success": true,
  "history": [
    {
      "chat_id": "example_channel",
      "chat_title": "示例频道",
      "chat_type": "CHANNEL",
      "last_used": "2026-04-06T12:00:00"
    }
  ]
}
```

---

#### `DELETE /api/channel_history/<chat_id>`
删除指定频道的历史记录。

**响应：** `{ "success": true, "message": "已删除" }`

---

#### `POST /api/channel_history/clear`
清空所有频道历史记录。

**响应：** `{ "success": true, "message": "已清空" }`

---

#### `GET /get_download_list?already_down=false`
获取下载文件列表。

| 参数 | 值 | 说明 |
|-----|-----|------|
| `already_down` | `false` | 返回正在下载的文件列表 |
| `already_down` | `true` | 返回已完成下载的文件列表 |

**响应（JSON 数组）：**
```json
[
  {
    "chat": "example_channel",
    "id": "12345",
    "filename": "video.mp4",
    "total_size": "1.23 GB",
    "download_progress": "45.6",
    "download_speed": "2.34 MB/s",
    "save_path": "/Users/xx/Downloads/tg/示例频道/2024_01/video.mp4"
  }
]
```

---

## 11. 数据结构

### 11.1 `config.yaml` 完整示例

```yaml
# ── Telegram API 凭证（必填）──
api_id: 12345678
api_hash: abcdef1234567890abcdef1234567890

# ── 下载目标列表（至少一个）──
chat:
  - chat_id: example_channel
    last_read_message_id: 0       # 程序运行后自动更新，勿手动修改
    download_filter: message_date >= 2024-01-01 00:00:00

# ── 媒体类型 ──
media_types:
  - audio
  - photo
  - video
  - document
  - voice
  - video_note

# ── 文件格式过滤 ──
file_formats:
  audio:
    - all
  document:
    - all
  video:
    - all

# ── 目录/文件名结构 ──
file_path_prefix:
  - chat_title
  - media_datetime

# ── 保存路径（必填）──
save_path: /Users/username/Downloads/telegram_downloads

# ── Web 界面 ──
web_host: 127.0.0.1
web_port: 5001
# web_login_secret: your_password   # 注释掉则不需要密码

# ── 代理（大陆必填）──
proxy:
  scheme: http        # 或 socks5
  hostname: 127.0.0.1
  port: 7890

# ── 可选高级配置 ──
language: ZH
max_download_task: 5
enable_download_txt: false

# ── Bot 模式（可选）──
# bot_token: 1234567890:AAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# allowed_user_ids: [123456789]

# ── 云盘上传（可选）──
# cloud_drive_config:
#   enable_upload_file: true
#   upload_adapter: rclone
#   remote_dir: "gdrive:/Telegram"
#   after_upload_file_delete: true
```

### 11.2 `channel_history.json` 结构

```json
[
  {
    "chat_id": "example_channel",
    "chat_title": "示例频道",
    "chat_type": "CHANNEL",
    "last_used": "2026-04-06T12:34:56.789000"
  }
]
```

---

## 12. 下载目录结构

下载文件的存储结构由 `file_path_prefix` 和 `file_name_prefix` 两个配置项控制。

### 12.1 默认结构（`file_path_prefix: [chat_title, media_datetime]`）

```
save_path/
├── 示例频道/                         ← chat_title
│   ├── 2024_01/                      ← media_datetime (YYYY_MM)
│   │   ├── 1001 - video_001.mp4      ← message_id - 文件名
│   │   ├── 1002 - photo_001.jpg
│   │   └── 1003 - document.pdf
│   └── 2024_02/
│       └── ...
├── 另一个频道/
│   └── ...
└── temp/                             ← 下载中的临时文件
    └── ...（下载完成后自动清理）
```

### 12.2 按媒体类型分目录（`file_path_prefix: [chat_title, media_type]`）

```
save_path/
└── 示例频道/
    ├── video/
    │   ├── video_001.mp4
    │   └── video_002.mkv
    ├── photo/
    │   └── photo_001.jpg
    ├── audio/
    └── document/
```

---

## 13. 安装与运行

### 13.1 环境要求

- Python 3.7+
- 网络可访问 Telegram 服务器（大陆需代理）
- 已申请 Telegram API 凭证

### 13.2 本地安装

```bash
# 克隆仓库
git clone https://github.com/tangyoha/telegram_media_downloader.git
cd telegram_media_downloader

# 安装依赖
pip3 install -r requirements.txt
# 或使用 Makefile
make install

# 编辑配置文件（必须）
# 填写 api_id, api_hash, save_path, chat（或使用 Web 向导）
nano config.yaml

# 启动
python3 media_downloader.py
```

### 13.3 首次运行登录流程

```
程序启动
  ↓
提示输入手机号：Enter phone number: +86138...
  ↓
提示输入验证码（Telegram App 内收件箱）：Enter OTP: 12345
  ↓
（可能需要）输入两步验证密码：Enter password: ****
  ↓
登录成功，生成 session 文件（项目根目录，*.session）
  ↓
后续运行无需重新登录（session 文件保存登录状态）
```

**注意：** session 文件是登录凭证，请妥善保管，不要提交到代码仓库。

### 13.4 Docker 部署

```bash
# 方式一：使用 docker-compose（推荐）
# 修改 docker-compose.yaml 中的卷挂载路径
docker-compose up -d

# 方式二：手动运行容器
docker pull tangyoha/telegram_media_downloader:latest
docker run -d \
  -p 5000:5000 \
  -v $(pwd)/config.yaml:/app/config.yaml \
  -v /your/download/path:/downloads \
  tangyoha/telegram_media_downloader:latest
```

### 13.5 访问 Web 界面

启动后访问：`http://localhost:5001`（端口以 config.yaml 中 `web_port` 为准）

---

## 14. 依赖说明

### 14.1 核心依赖

| 库 | 版本 | 用途 | 说明 |
|----|------|------|------|
| pyrogram | 自定义 fork | Telegram MTProto 客户端 | 使用 tangyoha 的修补版，修复了若干上游问题 |
| PyTgCrypto | 1.2.6 | Telegram 加密 | pyrogram 的加密加速依赖 |
| ruamel.yaml | 0.17.21 | YAML 读写 | 保持 YAML 注释和格式，用于写回 config.yaml |
| PyYAML | 5.3.1 | YAML 解析 | 兼容性保留 |
| flask | 2.2.2 | Web 框架 | 提供 HTTP 服务和模板渲染 |
| flask-login | 0.6.2 | 会话认证 | Web 界面登录管理 |
| Werkzeug | 2.2.2 | WSGI 工具 | Flask 依赖，HTTP 服务底层 |
| pycryptodome | 3.18.0 | AES 加密 | Web 密码加密传输 |
| ply | 3.11 | 词法/语法分析 | 下载过滤器表达式引擎 |
| loguru | 0.6.0 | 日志 | 结构化彩色日志输出 |
| rich | 12.5.1 | 终端输出 | 彩色进度显示 |
| requests | 2.32.3 | HTTP 请求 | 版本更新检查 |

### 14.2 可选依赖

| 库 | 用途 |
|----|------|
| rclone | 云存储上传（外部命令，非 Python 包） |
| aligo | 阿里云盘上传 |

---

## 15. 当前限制与已知问题

### 15.1 功能限制

| 限制 | 描述 | 影响 |
|------|------|------|
| 终端首次登录 | 首次运行必须在终端手动输入手机号+验证码，不支持纯 Web 引导完成登录 | 服务器无头部署不便 |
| 无内置调度 | 没有定时任务功能，需借助外部 cron 实现周期性运行 | 无法自动增量同步 |
| 频道列表硬编码 | config.yaml 中的 chat 列表需手动编辑或通过 Web 界面覆盖，不支持多频道并存管理 | 多频道管理不便 |
| 单进程架构 | 所有下载在一个进程中，重启会中断当前任务 | 更新配置代价大 |
| 无下载队列持久化 | 程序重启后，未完成的队列任务丢失 | 中断恢复能力弱 |
| Web 界面无 HTTPS | Web 服务仅 HTTP，暴露在公网有安全风险 | 服务器部署需额外加固 |

### 15.2 已知技术问题

| 问题 | 原因 | 临时解决方案 |
|------|------|------------|
| 代理配置敏感 | pyrogram 直连 Telegram DC IP，不走系统代理，必须在 config.yaml 中显式配置代理 | 确保代理软件开启了 SOCKS5/HTTP 端口 |
| Flask 模板缓存 | 非 debug 模式下 Jinja2 不自动重载模板，修改 HTML 后需重启服务 | 重启服务进程 |
| PLY 解析表文件 | `parsetab.py` 是自动生成的，不应手动修改，但提交到了版本库中 | 保持不修改此文件 |
| 私有频道下载受限 | 必须以账号身份加入该私有频道才能下载，Bot Token 无法访问普通私有频道 | 用账号加入频道后再配置下载 |

### 15.3 性能特性

- 历史消息获取速度受 Telegram API 频率限制（大频道可能需要数分钟至数小时扫描）
- 并发下载数默认 5，可通过 `max_download_task` 调高，但过高可能触发 Telegram 限流
- 临时文件在 `temp/` 目录，建议 `temp/` 和最终保存路径在同一磁盘分区（避免跨分区移动开销）

---

## 16. 优化建议与迭代方向

### 16.1 高优先级（影响核心体验）

#### 1. Web 引导完成 Telegram 登录
**现状：** 首次登录必须在终端交互完成，对服务器部署和非技术用户极不友好。  
**建议：** 在 Web 向导第4步中，通过轮询 `/api/login_status` 接口感知登录状态，或通过 WebSocket 推送终端输出，在 Web 界面内完成手机号+验证码输入。

#### 2. 下载任务持久化
**现状：** 程序重启后，正在进行的任务从队列消失（只有 `last_read_message_id` 之前的已读记录）。  
**建议：** 将 asyncio Queue 的待处理任务序列化到 SQLite 或 JSON 文件，重启后自动恢复。

#### 3. 多频道独立配置管理
**现状：** `save_and_restart` 接口会覆盖 config.yaml 中的 `chat` 列表为单个频道，不支持同时配置多频道。  
**建议：** Web 界面支持频道列表管理，增删改每个频道的配置，而不是每次覆盖。

---

### 16.2 中优先级（提升使用效率）

#### 4. 内置定时任务（增量同步调度器）
**建议：** 在 Web 界面添加"定时同步"功能（如每天凌晨 2 点自动运行），内置 APScheduler 或 cron 风格调度，无需依赖外部 cron。

#### 5. 下载统计与历史报告
**建议：** 增加下载历史记录（SQLite 存储），Web 界面展示：
- 按日期的下载量图表
- 按频道的下载统计
- 磁盘空间使用情况

#### 6. 实时进度推送（WebSocket）
**现状：** Web 界面通过轮询（每 2 秒 fetch 一次）获取进度，有延迟且浪费请求。  
**建议：** 改用 WebSocket 或 SSE（Server-Sent Events）推送实时进度，降低延迟和服务器压力。

#### 7. 文件去重优化
**现状：** 通过比较文件路径和大小判断是否已下载。  
**建议：** 增加基于消息 ID 的已下载记录（SQLite），即使文件被移动或重命名，也不会重复下载同一消息。

---

### 16.3 低优先级（长期演进）

#### 8. HTTPS 支持
**建议：** Web 服务支持 SSL/TLS 配置（提供证书路径配置项），或提供 Nginx 反向代理配置示例，供服务器部署时使用。

#### 9. 移动端适配
**现状：** Web 界面在小屏幕（480px 以下）有布局问题。  
**建议：** 完善响应式布局，优化移动端操作体验（触摸友好的表单控件、大按钮）。

#### 10. 过滤器可视化编辑器
**现状：** 过滤器需手动输入表达式字符串，语法不直观。  
**建议：** Web 界面提供图形化过滤条件构建器（类似 Notion filter），自动生成过滤表达式字符串。

#### 11. 多账号支持
**建议：** 支持配置多个 Telegram 账号（多个 `api_id/api_hash`），不同频道使用不同账号下载，规避单账号频率限制。

#### 12. API 文档自动生成
**建议：** 使用 Flask-RESTX 或 flasgger 为 Web API 生成 Swagger 文档，方便二次开发和集成。

---

### 16.4 代码质量建议

| 类别 | 建议 |
|------|------|
| 测试覆盖率 | 补充 web.py API 端点的集成测试，当前测试主要覆盖工具函数 |
| 类型注解 | 核心函数补充完整的 Python 类型注解（typing），提升 IDE 支持和可读性 |
| 配置校验 | 启动时对 config.yaml 进行 Schema 校验（使用 pydantic 或 jsonschema），提前发现配置错误 |
| 错误处理 | media_downloader.py 中部分 except 过于宽泛（`except Exception`），建议细化为具体异常类型 |
| 日志规范 | 统一使用 loguru，移除部分混用 print 和 logging 的情况 |
| 文档字符串 | 核心函数（尤其是 web.py 的路由函数）补充 docstring |

---

*文档结束*

> 本文档基于项目版本 2.2.5 编写（2026-04-06）。如项目代码有更新，部分细节可能与实际不符，请以代码为准。
