## File: README.md

# PanSou 网盘搜索API

PanSou是一个高性能的网盘资源搜索API服务，支持TG搜索和自定义插件搜索。系统设计以性能和可扩展性为核心，支持并发搜索、结果智能排序和网盘类型分类。

## 特性（[详见系统设计文档](docs/%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91%E8%AE%BE%E8%AE%A1%E6%96%87%E6%A1%A3.md)）

- **高性能搜索**：并发执行多个TG频道及异步插件搜索，显著提升搜索速度；工作池设计，高效管理并发任务
- **网盘类型分类**：自动识别多种网盘链接，按类型归类展示
- **智能排序**：基于插件等级、时间新鲜度和优先关键词的多维度综合排序算法
- **异步插件系统**：支持通过插件扩展搜索来源，支持"尽快响应，持续处理"的异步搜索模式，解决了某些搜索源响应时间长的问题。详情参考[**插件开发指南**](docs/插件开发指南.md)，AI 辅助开发可参考[**PanSou 插件开发 Skill 使用说明**](docs/AI客户端使用PanSou插件开发Skill指南.md)
- **二级缓存**：分片内存+分片磁盘缓存机制，大幅提升重复查询速度和并发性能  

## 开发者文档

- [插件开发指南](docs/插件开发指南.md)：PanSou 插件接口、异步机制、优先级、过滤策略和开发流程。
- [PanSou 插件开发 Skill 使用说明](docs/AI客户端使用PanSou插件开发Skill指南.md)：在 Codex、Claude、Cursor、Windsurf、Copilot Chat、Cline/Roo Code 等 AI 客户端中复用插件开发规则。
- [PanSou 插件开发 Skill 原文](docs/pansou-plugin-developer-SKILL.md)：可直接安装到支持 Skill 的客户端，或作为项目规则引用。

## 支持的网盘类型

百度网盘 (`baidu`)、阿里云盘 (`aliyun`)、夸克网盘 (`quark`)、光鸭云盘 (`guangya`)、天翼云盘 (`tianyi`)、UC网盘 (`uc`)、移动云盘 (`mobile`)、115网盘 (`115`)、PikPak (`pikpak`)、迅雷网盘 (`xunlei`)、123网盘 (`123`)、磁力链接 (`magnet`)、电驴链接 (`ed2k`)、其他 (`others`)

## 快速开始

在 Github 上先[](https://github.com/fish2018/pansou/fork)
本项目，并点上 Star !!!

### 使用Docker部署
[qqpd搜索插件文档](plugin/qqpd/README.md)  
[gying搜索插件文档](plugin/gying/README.md)   
[weibo搜索插件文档](plugin/weibo/README.md)   
[常见问题总结](https://github.com/fish2018/pansou/issues/46)  
[TG/QQ频道/插件/微博](https://github.com/fish2018/pansou/issues/4)

#### **1、前后端集成版**

##### 直接使用Docker命令

一键启动，开箱即用

```
docker run -d --name pansou -p 80:80 ghcr.io/fish2018/pansou-web
```

##### 使用Docker Compose（推荐）
```
# 下载配置文件
curl -o docker-compose.yml https://raw.githubusercontent.com/fish2018/pansou-web/refs/heads/main/docker-compose.yml

# 启动服务
docker-compose up -d

# 查看日志
docker-compose logs -f
```

#### **2、纯后端API版**

##### 直接使用Docker命令

```bash
docker run -d --name pansou -p 8888:8888 ghcr.io/fish2018/pansou:latest
```

##### 使用Docker Compose（推荐）

```bash
# 下载配置文件
curl -o docker-compose.yml  https://raw.githubusercontent.com/fish2018/pansou/refs/heads/main/docker-compose.yml

# 启动服务
docker-compose up -d

# 访问服务
http://localhost:8888
```

### 从源码安装

#### 环境要求

- Go 1.18+
- 可选：SOCKS5代理（用于访问受限地区的Telegram站点）

1. 克隆仓库

```bash
git clone https://github.com/fish2018/pansou.git
cd pansou
```

2. 配置环境变量（可选）

#### 基础配置

| 环境变量 | 描述 | 默认值 | 说明 |
|----------|------|--------|------|
| **PORT** | 服务端口 | `8888` | 修改服务监听端口 |
| **PROXY** | SOCKS5代理 | 无 | 如：`PROXY=socks5://127.0.0.1:1080` |
| **HTTPS_PROXY/HTTP_PROXY** | HTTPS/HTTP代理 | 无 | 如：`HTTPS_PROXY=http://127.0.0.1:1080`,`HTTP_PROXY=http://127.0.0.1:1080` |
| **CHANNELS** | 默认搜索的TG频道 | `tgsearchers3` | 多个频道用逗号分隔 |
| **ENABLED_PLUGINS** | 指定启用插件，多个插件用逗号分隔 | 无 | 必须显式指定 |

#### 认证配置（可选）

PanSou支持可选的安全认证功能，默认关闭。开启后，所有API接口（除登录接口外）都需要提供有效的JWT Token。详见[认证系统设计文档](docs/认证系统设计.md)。

| 环境变量 | 描述 | 默认值 | 说明 |
|----------|------|--------|------|
| **AUTH_ENABLED** | 是否启用认证 | `false` | 设置为`true`启用认证功能 |
| **AUTH_USERS** | 用户账号配置 | 无 | 格式：`user1:pass1,user2:pass2` |
| **AUTH_TOKEN_EXPIRY** | Token有效期（小时） | `24` | JWT Token的有效时长 |
| **AUTH_JWT_SECRET** | JWT签名密钥 | 自动生成 | 用于签名Token，建议手动设置 |

**认证配置示例：**

```bash
# 启用认证并配置单个用户
docker run -d --name pansou -p 8888:8888 \
  -e AUTH_ENABLED=true \
  -e AUTH_USERS=admin:admin123 \
  -e AUTH_TOKEN_EXPIRY=24 \
  ghcr.io/fish2018/pansou:latest

# 配置多个用户
docker run -d --name pansou -p 8888:8888 \
  -e AUTH_ENABLED=true \
  -e AUTH_USERS=admin:pass123,user1:pass456,user2:pass789 \
  ghcr.io/fish2018/pansou:latest
```

**认证API接口：**

- `POST /api/auth/login` - 用户登录，获取Token
- `POST /api/auth/verify` - 验证Token有效性
- `POST /api/auth/logout` - 退出登录（客户端删除Token）

**使用Token调用API：**

```bash
# 1. 登录获取Token
curl -X POST http://localhost:8888/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"admin123"}'

# 响应：{"token":"eyJhbGc...","expires_at":1234567890,"username":"admin"}

# 2. 使用Token调用搜索API
curl -X POST http://localhost:8888/api/search \
  -H "Authorization: Bearer eyJhbGc..." \
  -H "Content-Type: application/json" \
  -d '{"kw":"速度与激情"}'
```

#### 高级配置（默认值即可）


点击展开高级配置选项（通常不需要修改）

| 环境变量 | 描述 | 默认值 |
|----------|------|--------|
| CONCURRENCY | 并发搜索数 | 自动计算 |
| CACHE_TTL | 缓存有效期（分钟） | `60` |
| CACHE_MAX_SIZE | 最大缓存大小(MB) | `100` |
| PLUGIN_TIMEOUT | 插件超时时间(秒) | `30` |
| ASYNC_RESPONSE_TIMEOUT | 快速响应超时(秒) | `4` |
| ASYNC_LOG_ENABLED | 异步插件详细日志 | `true` | 
| CACHE_PATH | 缓存文件路径 | `./cache` |
| SHARD_COUNT | 缓存分片数量 | `8` |
| CACHE_WRITE_STRATEGY | 缓存写入策略(immediate/hybrid) | `hybrid` |
| ENABLE_COMPRESSION | 是否启用压缩 | `false` |
| MIN_SIZE_TO_COMPRESS | 最小压缩阈值(字节) | `1024` |
| GC_PERCENT | Go GC触发百分比 | `50` |
| ASYNC_MAX_BACKGROUND_WORKERS | 最大后台工作者数量 | CPU核心数×5 |
| ASYNC_MAX_BACKGROUND_TASKS | 最大后台任务数量 | 工作者数×5 |
| ASYNC_CACHE_TTL_HOURS | 异步缓存有效期(小时) | `1` |
| ASYNC_PLUGIN_ENABLED | 异步插件是否启用 | `true` |
| HTTP_READ_TIMEOUT | HTTP读取超时(秒) | 自动计算 |
| HTTP_WRITE_TIMEOUT | HTTP写入超时(秒) | 自动计算 |
| HTTP_IDLE_TIMEOUT | HTTP空闲超时(秒) | `120` |
| HTTP_MAX_CONNS | HTTP最大连接数 | 自动计算 |



3. 构建

```linux
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -extldflags '-static'" -o pansou .
```

4. 运行

```bash
./pansou
```

### 其他配置参考


点击展开 supervisor 配置参考

```
[program:pansou]
environment=PORT=8888,CHANNELS="tgsearchers4,Aliyun_4K_Movies,bdbdndn11,yunpanx,bsbdbfjfjff,yp123pan,sbsbsnsqq,yunpanxunlei,tianyifc,BaiduCloudDisk,txtyzy,peccxinpd,gotopan,PanjClub,kkxlzy,baicaoZY,MCPH01,bdwpzhpd,ysxb48,jdjdn1111,yggpan,MCPH086,zaihuayun,Q66Share,Oscar_4Kmovies,ucwpzy,shareAliyun,alyp_1,dianyingshare,Quark_Movies,XiangxiuNBB,ydypzyfx,ucquark,xx123pan,yingshifenxiang123,zyfb123,tyypzhpd,tianyirigeng,cloudtianyi,hdhhd21,Lsp115,oneonefivewpfx,qixingzhenren,taoxgzy,Channel_Shares_115,tyysypzypd,vip115hot,wp123zy,yunpan139,yunpan189,yunpanuc,yydf_hzl,leoziyuan,pikpakpan,Q_dongman,yoyokuakeduanju",ENABLED_PLUGINS="labi,zhizhen,shandian,duoduo,muou"
command=/home/work/pansou/pansou
directory=/home/work/pansou
autostart=true
autorestart=true
startsecs=5
startretries=3
exitcodes=0
stopwaitsecs=10
stopasgroup=true
killasgroup=true
```




点击展开 nginx 配置参考

```
server {
    listen 80;
    server_name pansou.252035.xyz;

    # 将 HTTP 重定向到 HTTPS
    return 301 https://$host$request_uri;
}

limit_req_zone $binary_remote_addr zone=api_limit:10m rate=60r/m;

server {
    listen 443 ssl http2;
    server_name pansou.252035.xyz;

    access_log /home/work/logs/pansou.log;

    # 证书和密钥路径
    ssl_certificate /etc/letsencrypt/live/252035.xyz/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/252035.xyz/privkey.pem;

    # 增强 SSL 安全性
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH;
    ssl_prefer_server_ciphers on;

    # 后端代理，应用限流
    location / {
        # 应用限流规则
        limit_req zone=api_limit burst=10 nodelay;
        # 当超过限制时返回 429 状态码
        limit_req_status 429;

        proxy_pass http://127.0.0.1:8888;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```



## API文档

### 认证说明

当启用认证功能（`AUTH_ENABLED=true`）时，除登录和健康检测接口外的所有API接口都需要提供有效的JWT Token。

**请求头格式**：
```
Authorization: Bearer <your-jwt-token>
```

**获取Token**：

1. 调用登录接口获取Token（详见下方[认证API](#认证API)）
2. 在后续所有API请求的Header中添加`Authorization: Bearer <token>`
3. Token过期后需要重新登录获取新Token

**示例**：
```bash
# 未启用认证时
curl -X POST http://localhost:8888/api/search \
  -H "Content-Type: application/json" \
  -d '{"kw":"速度与激情"}'

# 启用认证时
curl -X POST http://localhost:8888/api/search \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer eyJhbGc..." \
  -d '{"kw":"速度与激情"}'
```

### 认证API

#### 用户登录

获取JWT Token用于后续API调用。

**接口地址**：`/api/auth/login`  
**请求方法**：`POST`  
**Content-Type**：`application/json`  
**是否需要认证**：否

**请求参数**：

| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| username | string | 是 | 用户名 |
| password | string | 是 | 密码 |

**请求示例**：
```bash
curl -X POST http://localhost:8888/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"admin123"}'
```

**成功响应**：
```json
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_at": 1234567890,
  "username": "admin"
}
```

**错误响应**：
```json
{
  "error": "用户名或密码错误"
}
```

#### 验证Token

验证当前Token是否有效。

**接口地址**：`/api/auth/verify`  
**请求方法**：`POST`  
**是否需要认证**：是

**请求示例**：
```bash
curl -X POST http://localhost:8888/api/auth/verify \
  -H "Authorization: Bearer eyJhbGc..."
```

**成功响应**：
```json
{
  "valid": true,
  "username": "admin"
}
```

#### 退出登录

退出当前登录（客户端删除Token即可）。

**接口地址**：`/api/auth/logout`  
**请求方法**：`POST`  
**是否需要认证**：否

**请求示例**：
```bash
curl -X POST http://localhost:8888/api/auth/logout
```

**成功响应**：
```json
{
  "message": "退出成功"
}
```

### 搜索API

搜索网盘资源。

**接口地址**：`/api/search`  
**请求方法**：`POST` 或 `GET`  
**Content-Type**：`application/json`（POST方法）  
**是否需要认证**：取决于`AUTH_ENABLED`配置

**POST请求参数**：

| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| kw | string | 是 | 搜索关键词 |
| channels | string[] | 否 | 搜索的频道列表，不提供则使用默认配置 |
| conc | number | 否 | 并发搜索数量，不提供则自动设置为频道数+插件数+10 |
| refresh | boolean | 否 | 强制刷新，不使用缓存，便于调试和获取最新数据 |
| res | string | 否 | 结果类型：all(返回所有结果)、results(仅返回results)、merge(仅返回merged_by_type)，默认为merge |
| src | string | 否 | 数据来源类型：all(默认，全部来源)、tg(仅Telegram)、plugin(仅插件) |
| plugins | string[] | 否 | 指定搜索的插件列表，不指定则搜索全部插件 |
| cloud_types | string[] | 否 | 指定返回的网盘类型列表，支持：baidu、aliyun、quark、guangya、tianyi、uc、mobile、115、pikpak、xunlei、123、magnet、ed2k，不指定则返回所有类型 |
| ext | object | 否 | 扩展参数，用于传递给插件的自定义参数，如{"title_en":"English Title", "is_all":true} |
| filter | object | 否 | 过滤配置，用于过滤返回结果。格式：{"include":["关键词1","关键词2"],"exclude":["排除词1","排除词2"]}。include为包含关键词列表（OR关系），exclude为排除关键词列表（OR关系） |

**GET请求参数**：

| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| kw | string | 是 | 搜索关键词 |
| channels | string | 否 | 搜索的频道列表，使用英文逗号分隔多个频道，不提供则使用默认配置 |
| conc | number | 否 | 并发搜索数量，不提供则自动设置为频道数+插件数+10 |
| refresh | boolean | 否 | 强制刷新，设置为"true"表示不使用缓存 |
| res | string | 否 | 结果类型：all(返回所有结果)、results(仅返回results)、merge(仅返回merged_by_type)，默认为merge |
| src | string | 否 | 数据来源类型：all(默认，全部来源)、tg(仅Telegram)、plugin(仅插件) |
| plugins | string | 否 | 指定搜索的插件列表，使用英文逗号分隔多个插件名，不指定则搜索全部插件 |
| cloud_types | string | 否 | 指定返回的网盘类型列表，使用英文逗号分隔多个类型，支持：baidu、aliyun、quark、guangya、tianyi、uc、mobile、115、pikpak、xunlei、123、magnet、ed2k，不指定则返回所有类型 |
| ext | string | 否 | JSON格式的扩展参数，用于传递给插件的自定义参数，如{"title_en":"English Title", "is_all":true} |
| filter | string | 否 | JSON格式的过滤配置，用于过滤返回结果。格式：{"include":["关键词1","关键词2"],"exclude":["排除词1","排除词2"]} |

**POST请求示例**：

```bash
# 未启用认证
curl -X POST http://localhost:8888/api/search \
  -H "Content-Type: application/json" \
  -d '{
    "kw": "速度与激情",
    "channels": ["tgsearchers3", "xxx"],
    "conc": 2,
    "refresh": true,
    "res": "merge",
    "src": "all",
    "plugins": ["jikepan"],
    "cloud_types": ["baidu", "quark"],
    "ext": {
      "title_en": "Fast and Furious",
      "is_all": true
    }
  }'

# 启用认证时（需要添加Authorization头）
curl -X POST http://localhost:8888/api/search \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -d '{
    "kw": "速度与激情",
    "res": "merge"
  }'

# 使用过滤器（只返回包含“合集”或“全集”，且不包含“预告”或“花絮”的结果）
curl -X POST http://localhost:8888/api/search \
  -H "Content-Type: application/json" \
  -d '{
    "kw": "唐朝诡事录",
    "filter": {
      "include": ["合集", "全集"],
      "exclude": ["预告", "花絮"]
    }
  }'
```

**GET请求示例**：

```bash
# 未启用认证
curl "http://localhost:8888/api/search?kw=速度与激情&res=merge&src=tg"

# 启用认证时（需要添加Authorization头）
curl "http://localhost:8888/api/search?kw=速度与激情&res=merge" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

# 使用过滤器（GET方式需要URL编码JSON）
curl "http://localhost:8888/api/search?kw=唐朝诡事录&filter=%7B%22include%22%3A%5B%22合集%22%2C%22全集%22%5D%2C%22exclude%22%3A%5B%22预告%22%5D%7D"
```

**成功响应**：

```json
{
  "total": 15,
  "results": [
    {
      "message_id": "12345",
      "unique_id": "channel-12345",
      "channel": "tgsearchers3",
      "datetime": "2023-06-10T14:23:45Z",
      "title": "速度与激情全集1-10",
      "content": "速度与激情系列全集，1080P高清...",
      "links": [
        {
          "type": "baidu",
          "url": "https://pan.baidu.com/s/1abcdef",
          "password": "1234",
          "datetime": "2023-06-10T14:23:45Z",
          "work_title": "速度与激情全集1-10"
        }
      ],
      "tags": ["电影", "合集"],
      "images": [
        "https://cdn1.cdn-telegram.org/file/xxx.jpg"
      ]
    },
    // 更多结果...
  ],
  "merged_by_type": {
    "baidu": [
      {
        "url": "https://pan.baidu.com/s/1abcdef",
        "password": "1234",
        "note": "速度与激情全集1-10",
        "datetime": "2023-06-10T14:23:45Z",
        "source": "tg:频道名称",
        "images": [
          "https://cdn1.cdn-telegram.org/file/xxx.jpg"
        ]
      },
      // 更多百度网盘链接...
    ],
    "quark": [
      {
        "url": "https://pan.quark.cn/s/xxxx",
        "password": "",
        "note": "凡人修仙传",
        "datetime": "2023-06-10T15:30:22Z",
        "source": "plugin:插件名",
        "images": []
      }
    ],
    "aliyun": [
      // 阿里云盘链接...
    ]
    // 更多网盘类型...
  }
}
```

**字段说明**：

**SearchResult对象**：
- `message_id`: 消息ID
- `unique_id`: 全局唯一标识符
- `channel`: 来源频道名称
- `datetime`: 消息发布时间
- `title`: 消息标题
- `content`: 消息内容
- `links`: 网盘链接数组
- `tags`: 标签数组（可选）
- `images`: TG消息中的图片链接数组（可选）

**Link对象**：
- `type`: 网盘类型（baidu、quark、aliyun等）
- `url`: 网盘链接地址
- `password`: 提取码/密码
- `datetime`: 链接更新时间（可选）
- `work_title`: 作品标题（可选）
  - 用于区分同一消息中多个作品的链接
  - 当一条消息包含≤4个链接时，所有链接使用相同的work_title
  - 当一条消息包含>4个链接时，系统会智能识别每个链接对应的作品标题

**MergedLink对象**：
- `url`: 网盘链接地址
- `password`: 提取码/密码
- `note`: 资源说明/标题
- `datetime`: 链接更新时间
- `source`: 数据来源标识
  - `tg:频道名称`: 来自Telegram频道
  - `plugin:插件名`: 来自指定插件
  - `unknown`: 未知来源
- `images`: TG消息中的图片链接数组（可选）
  - 仅在来源为Telegram频道且消息包含图片时出现


**错误响应**：

```json
// 参数错误
{
  "code": 400,
  "message": "关键词不能为空"
}

// 未授权（启用认证但未提供Token）
{
  "error": "未授权：缺少认证令牌",
  "code": "AUTH_TOKEN_MISSING"
}

// Token无效或过期
{
  "error": "未授权：令牌无效或已过期",
  "code": "AUTH_TOKEN_INVALID"
}
```

### 链接检测API

检测指定网盘分享链接当前是否有效，适合前端结果页按需做可见项检测，也支持批量调试和服务端缓存复用。

**接口地址**：`/api/check/links`  
**请求方法**：`POST`  
**Content-Type**：`application/json`  
**是否需要认证**：取决于`AUTH_ENABLED`配置

**请求参数**：

| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| items | object[] | 是 | 待检测链接数组，至少提供一项 |
| items[].disk_type | string | 是 | 网盘类型，支持：baidu、aliyun、quark、tianyi、uc、mobile、115、xunlei、123 |
| items[].url | string | 是 | 完整分享链接 |
| items[].password | string | 否 | 提取码/密码，未拼接在链接中时可传 |
| proxy_url | string | 否 | 本次检测请求使用的代理地址，位于请求根节点，支持 `http://`、`https://`、`socks5://`、`socks5h://` |
| proxy | string | 否 | `proxy_url` 的兼容别名，位于请求根节点；同时传入时以 `proxy_url` 为准 |
| view_token | string | 否 | 视图标识，用于区分当前前端检测批次 |

**代理行为说明**：

- `proxy_url`/`proxy` 只影响当前 `/api/check/links` 请求，不会修改服务进程的全局代理配置。
- 单次批量检测共用同一个代理；如需轮换出口IP，可由调用方在不同请求中传入不同代理。
- 未传 `proxy_url`/`proxy` 时，检测服务沿用启动时的全局HTTP客户端配置。
- 使用代理时，检测缓存会按代理地址隔离，避免不同出口IP复用同一检测结果。
- 代理地址格式非法或协议不支持时返回 `400`，不会静默降级为直连。

**请求示例**：

```bash
# 未启用认证
curl -X POST http://localhost:8888/api/check/links \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      {
        "disk_type": "quark",
        "url": "https://pan.quark.cn/s/abcdefg",
        "password": "1234"
      },
      {
        "disk_type": "xunlei",
        "url": "https://pan.xunlei.com/s/abcdefg?pwd=1234"
      },
      {
        "disk_type": "115",
        "url": "https://115cdn.com/s/abcdefg?password=1234"
      }
    ],
    "view_token": "quark-1710000000000"
  }'

# 指定本次检测使用代理，避免固定出口IP频繁触发风控
curl -X POST http://localhost:8888/api/check/links \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      {
        "disk_type": "quark",
        "url": "https://pan.quark.cn/s/abcdefg"
      }
    ],
    "proxy_url": "socks5://127.0.0.1:1080"
  }'

# 启用认证时
curl -X POST http://localhost:8888/api/check/links \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer eyJhbGc..." \
  -d '{
    "items": [
      {
        "disk_type": "baidu",
        "url": "https://pan.baidu.com/s/1abcdef?pwd=1234"
      }
    ]
  }'
```

**成功响应**：

```json
{
  "results": [
    {
      "disk_type": "quark",
      "url": "https://pan.quark.cn/s/abcdefg",
      "normalized_url": "https://pan.quark.cn/s/abcdefg?pwd=1234",
      "state": "ok",
      "cache_hit": false,
      "checked_at": 1710000000000,
      "expires_at": 1710086400000,
      "summary": "链接有效"
    },
    {
      "disk_type": "xunlei",
      "url": "https://pan.xunlei.com/s/abcdefg?pwd=1234",
      "normalized_url": "https://pan.xunlei.com/s/abcdefg?pwd=1234",
      "state": "bad",
      "cache_hit": true,
      "checked_at": 1710000100000,
      "expires_at": 1710021700000,
      "summary": "链接失效"
    }
  ]
}
```

**状态说明**：

- `ok`：链接有效
- `bad`：链接失效
- `locked`：需要提取码或密码错误
- `unsupported`：当前平台暂不支持检测
- `uncertain`：检测失败或结果不确定

**字段说明**：

- `results`: 检测结果数组
- `results[].disk_type`: 网盘类型
- `results[].url`: 原始传入链接
- `results[].normalized_url`: 规范化后的链接
- `results[].state`: 检测状态
- `results[].cache_hit`: 是否命中服务端检测缓存
- `results[].checked_at`: 最近一次检测时间戳（毫秒）
- `results[].expires_at`: 当前缓存过期时间戳（毫秒）
- `results[].summary`: 状态说明文本

**错误响应**：

```json
// 请求参数无效
{
  "code": 400,
  "message": "无效的检测请求: Key: 'CheckRequest.Items' Error:Field validation for 'Items' failed on the 'required' tag"
}

// items 为空
{
  "code": 400,
  "message": "items不能为空"
}

// 代理参数无效
{
  "code": 400,
  "message": "无效的代理参数: 不支持的代理协议: ftp"
}

// 未授权（启用认证但未提供Token）
{
  "error": "未授权：缺少认证令牌",
  "code": "AUTH_TOKEN_MISSING"
}
```

### 健康检查

检查API服务是否正常运行。

**接口地址**：`/api/health`  
**请求方法**：`GET`  
**是否需要认证**：否（公开接口）

**请求示例**：
```bash
curl http://localhost:8888/api/health
```

**成功响应**：

```json
{
  "status": "ok",
  "auth_enabled": true,
  "plugins_enabled": true,
  "plugin_count": 16,
  "plugins": [
    "pansearch",
    "panta", 
    "qupansou",
    "hunhepan",
    "jikepan",
    "pan666",
    "panyq",
    "susu",
    "xuexizhinan",
    "hdr4k",
    "labi",
    "shandian",
    "duoduo",
    "muou",
    "wanou",
    "ouge",
    "zhizhen",
    "huban"
  ],
  "channels_count": 1,
  "channels": [
    "tgsearchers3"
  ]
}
```

**字段说明**：
- `status`: 服务状态，"ok"表示正常
- `auth_enabled`: 是否启用认证功能
- `plugins_enabled`: 是否启用插件
- `plugin_count`: 已启用的插件数量
- `plugins`: 已启用的插件列表
- `channels_count`: 配置的频道数量
- `channels`: 配置的频道列表

## 📄 许可证

本项目采用 MIT 许可证。详情请见 [LICENSE](LICENSE) 文件。

## ⭐ Star 历史

[](https://star-history.dera.page/#fish2018/pansou&Date)

---

## File: plugin/gying/README.md

# Gying 搜索插件

## 📖 简介

Gying 是 PanSou 的搜索插件，用于抓取 Gying 站点的影视资源。当前实现里，`https://www.gying.net` 只是默认站点地址，实际支持在管理页里配置自定义域名 / 站点地址；搜索时会聚合所有有效账户的结果。

## ✨ 核心特性

- ✅ **多用户支持** - 每个用户独立配置，互不干扰
- ✅ **自定义域名** - 支持在管理页配置站点地址，登录/搜索/详情请求都基于当前 `base_url`
- ✅ **用户名密码登录** - 支持使用用户名和密码登录
- ✅ **智能去重** - 多用户搜索时自动去重
- ✅ **负载均衡** - 任务均匀分配，避免单用户限流
- ✅ **内存缓存** - 用户数据缓存到内存，搜索性能极高
- ✅ **持久化存储** - Cookie和用户配置自动保存，重启不丢失
- ✅ **Web管理界面** - 一站式配置，简单易用
- ✅ **RESTful API** - 支持程序化调用
- ✅ **默认账户自动登录** - 插件启动时自动使用默认账户登录
- ✅ **反爬挑战处理** - 内置 `cloudscraper` 和挑战页求解逻辑，可自动处理新版“浏览器安全验证”页面，并在登录失效时自动重登重试

## 🚀 快速开始

### 步骤1: 启动服务

```bash
cd /Users/macbookpro/Desktop/fish2018/pansou
go run main.go

# 或者编译后运行
go build -o pansou main.go
./pansou
```

### 步骤2: 访问管理页面

如果需要添加更多账户或管理现有账户，可以访问管理页面：

```
http://localhost:8888/gying/你的用户名
```

**示例**：
```
http://localhost:8888/gying/myusername
```

系统会自动：
1. 根据用户名生成专属64位hash（不可逆）
2. 重定向到专属管理页面：`http://localhost:8888/gying/{hash}`
3. 显示登录表单供手动登录

**📌 提示**：请收藏hash后的URL（包含你的专属hash），方便下次访问。

### 步骤3: 先配置站点地址（重要）

进入管理页后，建议先在“站点地址”区域配置你的自定义域名 / 站点地址，再进行登录。

当前代码逻辑是：

- 默认站点地址为 `https://www.gying.net`
- 初始会话页：`{base_url}`
- 登录接口：`{base_url}/user/login`
- 搜索页：`{base_url}/search?q={keyword}&type=0&mode=2`
- 详情接口：`{base_url}/res/downurl/{type}/{id}`

也就是说，插件不是固定写死抓某一个域名，而是所有核心请求都基于当前 `base_url` 动态拼接。

**📌 提示**：

- 站点地址会保存到 `cache/gying_users/gying_config.json`
- 修改站点地址后，插件会清空当前登录状态和搜索缓存，需要重新登录
- 站点地址只允许填写纯域名，例如 `https://your-gying-domain.com`

### 步骤4: 手动登录

在"登录状态"区域输入：
- 用户名
- 密码

点击"**登录**"按钮。

### 步骤5: 开始搜索

在PanSou主页搜索框输入关键词，系统会**自动聚合所有用户**的Gying搜索结果！

```bash
# 通过API搜索
curl "http://localhost:8888/api/search?kw=遮天"

# 只搜索插件（包括gying）
curl "http://localhost:8888/api/search?kw=遮天&src=plugin"
```

## 📡 API文档

### 统一接口

所有操作通过统一的POST接口：

```
POST /gying/{hash}
Content-Type: application/json

{
  "action": "操作类型",
  ...其他参数
}
```

### API列表

| Action | 说明 | 需要登录 |
|--------|------|---------|
| `get_status` | 获取状态 | ❌ |
| `get_config` | 获取当前站点地址 | ❌ |
| `update_config` | 更新站点地址 | ❌ |
| `login` | 登录 | ❌ |
| `logout` | 退出登录 | ✅ |
| `test_search` | 测试搜索 | ✅ |

---

### 1️⃣ get_status - 获取用户状态

**请求**：
```bash
curl -X POST "http://localhost:8888/gying/{hash}" \
  -H "Content-Type: application/json" \
  -d '{"action": "get_status"}'
```

**成功响应（已登录）**：
```json
{
  "success": true,
  "message": "获取成功",
  "data": {
    "hash": "abc123...",
    "logged_in": true,
    "status": "active",
    "username": "pansou",
    "login_time": "2025-10-28 12:00:00",
    "expire_time": "2026-02-26 12:00:00",
    "expires_in_days": 121
  }
}
```

**成功响应（未登录）**：
```json
{
  "success": true,
  "message": "获取成功",
  "data": {
    "hash": "abc123...",
    "logged_in": false,
    "status": "pending"
  }
}
```

---

### 2️⃣ login - 登录

**请求**：
```bash
curl -X POST "http://localhost:8888/gying/{hash}" \
  -H "Content-Type: application/json" \
  -d '{"action": "login", "username": "xxx", "password": "xxx"}'
```

**成功响应**：
```json
{
  "success": true,
  "message": "登录成功",
  "data": {
    "status": "active",
    "username": "xxx"
  }
}
```

**失败响应**：
```json
{
  "success": false,
  "message": "登录失败: 用户名或密码错误"
}
```

---

### 3️⃣ get_config - 获取站点地址

**请求**：
```bash
curl -X POST "http://localhost:8888/gying/{hash}" \
  -H "Content-Type: application/json" \
  -d '{"action": "get_config"}'
```

**成功响应**：
```json
{
  "success": true,
  "message": "获取成功",
  "data": {
    "base_url": "https://www.gying.net"
  }
}
```

---

### 4️⃣ update_config - 更新站点地址

**请求**：
```bash
curl -X POST "http://localhost:8888/gying/{hash}" \
  -H "Content-Type: application/json" \
  -d '{"action": "update_config", "base_url": "https://your-gying-domain.com"}'
```

**成功响应**：
```json
{
  "success": true,
  "message": "站点地址已保存，当前登录状态已清空，请重新登录",
  "data": {
    "base_url": "https://your-gying-domain.com"
  }
}
```

**说明**：

- 保存前会自动做域名标准化
- 不允许带路径、查询参数或锚点
- 如果域名发生变化，所有用户当前 Cookie 会被清空并回到 `pending`

---

### 5️⃣ logout - 退出登录

**请求**：
```bash
curl -X POST "http://localhost:8888/gying/{hash}" \
  -H "Content-Type: application/json" \
  -d '{"action": "logout"}'
```

**成功响应**：
```json
{
  "success": true,
  "message": "已退出登录",
  "data": {
    "status": "pending"
  }
}
```

---

### 6️⃣ test_search - 测试搜索

**请求**：
```bash
curl -X POST "http://localhost:8888/gying/{hash}" \
  -H "Content-Type: application/json" \
  -d '{"action": "test_search", "keyword": "遮天"}'
```

**成功响应**：
```json
{
  "success": true,
  "message": "找到 5 条结果",
  "data": {
    "keyword": "遮天",
    "total_results": 5,
    "results": [
      {
        "title": "遮天：禁区",
        "links": [
          {
            "type": "quark",
            "url": "https://pan.quark.cn/s/89f7aeef9681",
            "password": ""
          }
        ]
      }
    ]
  }
}
```

---

## 🔍 当前实现补充

下面这些是当前 `gying.go` 已经实现、但原始 README 没写全的部分：

### 1. 反爬挑战处理

所有关键请求都会经过 `requestWithChallengeRetry`。如果检测到站点返回的验证页，例如：

- `浏览器安全验证`
- `安全验证`
- `正在进行浏览器计算验证`
- 旧版 `正在确认你是不是机器人`

插件会进入 `solveBotChallenge` 自动求解后再重试原请求。

当前同时兼容三类验证结构：

- 远程 PoW：验证页只加载 `powSolve-*.js`，并通过 `Set-Cookie: browser_pow=...` 标记挑战；插件请求 `{base_url}/res/pow` 获取 `N/x/t`，计算后 `POST {base_url}/res/pow` 提交 `y`。
- 内嵌 PoW：`const json={id,N,x,t};const jss=...`，插件用 Go 的 `math/big` 直接计算 `y = y*y % N` 共 `t` 次，然后提交 `action=verify&id={id}&y={y}`。
- 旧版哈希枚举：`const json={id,challenge,diff,salt};const jss=...`，插件枚举 `nonce`，匹配 `sha256(strconv.Itoa(nonce)+salt)` 后提交 `nonce[]`。

PoW 求解不依赖 Playwright、Puppeteer、Selenium 或真实浏览器环境；它是对站点 worker 脚本的轻量算法复现。为贴近前端行为，计算完成不足 3 秒时会补齐等待后再提交验证。

验证通过后，站点通常会下发 `browser_verified` 这类验证态 Cookie。当前插件会：

- 在登录阶段导出并保存当前 Cookie 快照
- 在搜索阶段如果拿到新的验证态 Cookie，会自动回写到用户文件
- 在插件重启后优先使用已保存 Cookie 恢复会话，而不是默认先重登

这一步的目的是尽量复用浏览器验证结果，减少重复触发 challenge。

#### 1.1 2026-05 登录验证循环问题

曾出现过一种登录失败场景：日志里先显示 `Challenge验证成功`，但随后重试原请求仍然返回 `浏览器安全验证`，最终报错 `重试后仍然进入机器人验证页`。

排查结论：新版验证态不只依赖 `browser_verified` Cookie，还会受同一次客户端请求指纹影响。此前 `requestWithChallengeRetry` 里 GET 请求直接使用 cloudscraper 内部的 `http.Client.Do`，而验证提交使用 `scraper.Post`；这会让“访问验证页 / 提交验证 / 重试原请求”没有完整经过 cloudscraper 的同一套浏览器化请求处理，导致站点认为重试请求仍然需要验证。

当前修复：

- GET 请求统一使用 `scraper.Get`
- POST 请求继续使用 `scraper.Post`
- challenge 页、验证提交、重试原请求都保持在同一个 cloudscraper 会话与请求链路里
- 调试日志中的登录 POST 数据已对密码脱敏，只输出密码长度

验证方式：

1. 使用错误密码登录，应能通过 challenge 并返回站点的账号密码错误 JSON，而不是停在机器人验证页。
2. 使用正确密码登录，应返回 `登录成功`，并在 Cookie 快照中包含 `PHPSESSID`、`app_auth`、`browser_verified`。
3. 调用 `test_search` 搜索关键词，应能访问搜索页和详情接口并返回网盘链接。

#### 1.2 2026-05 新版 PoW 参数变更

新版验证页不再只返回 `challenge/diff/salt`，而是返回类似：

```javascript
const json={"id":"...","N":"...","x":"...","t":800000};const jss={...};
```

对应的 worker 逻辑等价于：

```text
N = BigInt("0x" + json.N)
y = BigInt("0x" + json.x)
repeat json.t times:
    y = (y * y) % N
```

验证提交表单为：

```text
action=verify&id={id}&y={hex(y)}
```

此前代码只识别旧版 `challenge/diff/salt`，会在登录或搜索进入新版验证页时报 `验证数据无效`。当前实现已在 `solveBotChallenge` 中自动分流新版 PoW 和旧版哈希枚举。

因为新版验证会增加登录耗时，配套前端 `pansou-web` 也把 Gying 登录 / 测试搜索请求超时放宽，并在登录请求超时后回查一次状态，避免后端已登录成功但前端误报“登录失败”。

#### 1.3 2026-05 远程 PoW 流程

站点后来又调整为“页面不再内嵌 `const json`”的流程。验证页只包含：

```javascript
const jss={"worker":"//static.filejin.ru/static/file/js/pow.worker-...js"};
```

同时响应头下发：

```text
Set-Cookie: browser_pow={challenge_id}; Max-Age=300; HttpOnly; SameSite=Lax
```

前端脚本的真实流程是：

1. `GET /res/pow` 获取 `N/x/t`
2. 本地计算 `y = y*y % N` 共 `t` 次
3. `POST /res/pow`，表单为 `y={hex(y)}`
4. 成功后服务端返回 `{"success":true,"challenge_id":"..."}` 并下发 `browser_verified`
5. 原请求重新加载

当前插件在识别到这类验证页时，会自动走 `solveRemotePowChallenge`，用同一个 `cloudscraper` 会话完成 `/res/pow` 获取、计算、提交，再重试搜索或详情请求。

### 2. 搜索链路不是单接口

当前搜索逻辑不是直接调一个公开 API，而是：

1. 访问搜索页 `/{base_url}/search?q={keyword}&type=0&mode=2`
2. 从 HTML 中提取 `_obj.search = {...}` 内嵌 JSON
3. 再并发请求详情接口 `/{base_url}/res/downurl/{type}/{id}`
4. 从详情里提取网盘链接和磁力链接

### 2.1 代理前提

当前目标站点在不同网络出口下行为差异很大。

实测中，如果直连当前 `gying` 域名，搜索入口有时不会进入 challenge，而是直接返回一个由 `Angie` 输出的 `404 Not Found` 假页；这类 404 不是插件参数错误，而是站点对当前网络出口的拒绝或伪装响应。

因此，`gying` 在需要海外网络时，必须确保插件自己的请求链也走代理。

当前实现会显式复用主程序的全局 `PROXY` 配置，并应用到 `cloudscraper` 内部 transport，包括：

- 启动时使用已保存 Cookie 恢复会话
- 用户名密码登录
- 搜索页请求
- 详情接口请求

例如：

```bash
PROXY=socks5://127.0.0.1:7897 ENABLED_PLUGINS=gying go run .
```

### 3. 自动重新登录

如果搜索页或详情接口返回下列任一失效信号，插件会尝试使用已保存的加密密码重新登录，然后用新的会话重试搜索：

- HTTP `403`
- `_BT.PC.HTML('login')`
- `_BT.PC.HTML('nologin')`
- 页面标题或正文出现“未登录，访问受限”
- 详情 JSON 里的 `code == 403`

### 4. 当前管理页的实际定位

管理页是“一个 hash 对应一个用户槽位”，不是统一多账号后台；但插件搜索时，仍然会把所有 `active` 用户的结果汇总后去重。

---

## 🔧 配置说明

### 站点地址配置（重要）

站点地址相关逻辑对应 `getBaseURL()`、`updateBaseURL()`、`getLoginPageURL()`、`getLoginAPIURL()` 等方法。

配置规则：

- 默认值：`https://www.gying.net`
- 支持通过管理页动态修改
- 自动补全 `https://`
- 只允许 `http://` 或 `https://`
- 不允许包含路径、参数、锚点

建议实际使用顺序：

1. 访问 `/gying/你的用户名`
2. 进入 hash 页面后先配置站点地址
3. 再登录账号
4. 最后再使用 `test_search` 或正式搜索

### Cookie 生命周期

当前 `gying` 的会话至少包含两类状态：

- 登录态 Cookie：例如 `PHPSESSID`、`app_auth`
- 验证态 Cookie：例如 `browser_verified`

两者作用不同：

- 没有登录态，搜索可能直接进入 `nologin`
- 没有验证态，即使已登录，也可能先进入“浏览器安全验证”

当前插件的处理链路是：

1. 登录或重登时保存完整 Cookie 快照
2. 搜索过程中如果站点补发新的验证态 Cookie，会同步回写到 `cache/gying_users/{hash}.json`
3. 插件重启后优先恢复这份 Cookie 快照，以复用验证结果

### 环境变量（可选）

```bash
# 缓存目录（默认 ./cache）
export CACHE_PATH="./cache"

# 目标站点如果需要海外网络，建议显式配置 PROXY；gying 会复用这条代理到自己的 cloudscraper 请求链
export PROXY="socks5://127.0.0.1:7897"

# Hash Salt（推荐自定义，增强安全性）
export GYING_HASH_SALT="your-custom-salt-here"

# 当前主流程的密码加解密未使用这个环境变量；它只影响可选的 Cookie 加密辅助函数
export GYING_ENCRYPTION_KEY="your-32-byte-key-here!!!!!!!!!!"
```

### 代码内配置

在 `gying.go` 中修改：

```go
const (
    MaxConcurrentUsers   = 10    // 最多使用的用户数（搜索时）
    MaxConcurrentDetails = 50    // 最大并发详情请求数
    DebugLog             = false // 调试日志开关
)
```

### 默认账户配置

在 `gying.go` 中修改默认账户：

```go
var DefaultAccounts = []struct {
    Username string
    Password string
}{
    // 可以添加更多默认账户
    // {"user2", "password2"},
}
```

**参数说明**：

| 参数 | 默认值 | 说明 | 建议 |
|------|--------|------|------|
| `MaxConcurrentUsers` | 10 | 单次搜索最多使用的用户数 | 10-20足够 |
| `MaxConcurrentDetails` | 50 | 最大并发详情请求数 | 50-100 |
| `DebugLog` | false | 是否开启调试日志 | 生产环境false |

## 📂 数据存储

### 存储位置

```
cache/gying_users/gying_config.json
cache/gying_users/{hash}.json
```

### 数据结构

**站点配置文件**：

```json
{
  "base_url": "https://your-gying-domain.com",
  "updated_at": "2026-04-03T12:00:00+08:00"
}
```

**用户文件**：

```json
{
  "hash": "abc123...",
  "username": "pansou",
  "encrypted_password": "base64-aes-gcm",
  "cookie": "PHPSESSID=xxx; app_auth=xxx; browser_verified=xxx",
  "status": "active",
  "created_at": "2025-10-28T12:00:00+08:00",
  "login_at": "2025-10-28T12:00:00+08:00",
  "expire_at": "2026-02-26T12:00:00+08:00",
  "last_access_at": "2025-10-28T13:00:00+08:00"
}
```

**字段说明**：
- `hash`: 用户唯一标识（SHA256，不可逆推用户名）
- `username`: 原始用户名（存储）
- `encrypted_password`: 加密后的密码，用于搜索失效或 403 后自动重新登录
- `cookie`: 登录 Cookie 快照，常见值包括 `PHPSESSID`、`app_auth`、`browser_verified`
- `status`: 用户状态（`pending`/`active`/`expired`）
- `expire_at`: Cookie过期时间（121天）

---

## File: plugin/qqpd/README.md

# QQ频道搜索插件 (QQPD)

## 📖 简介

QQPD是PanSou的QQ频道搜索插件，支持多用户登录QQ频道并配置频道列表，在搜索时自动聚合所有用户的频道资源。

## ✨ 核心特性

- ✅ **多用户支持** - 每个用户独立配置，互不干扰
- ✅ **扫码登录** - 手机QQ扫码，自动获取Cookie
- ✅ **Session保活** - 自动定期访问保持Cookie活跃，防止失效 🆕
- ✅ **智能去重** - 多用户配置相同频道时自动去重
- ✅ **负载均衡** - 任务均匀分配，避免单用户限流
- ✅ **内存缓存** - 用户数据和guild_id缓存到内存，搜索性能极高
- ✅ **持久化存储** - Cookie和频道配置自动保存，重启不丢失
- ✅ **Web管理界面** - 一站式配置，简单易用
- ✅ **RESTful API** - 支持程序化调用

## 🚀 快速开始

### 步骤1: 启动服务

```bash
cd /Users/macbookpro/Desktop/fish2018/pansou
ENABLED_PLUGINS=qqpd go run main.go

# 或者编译后运行
go build -o pansou main.go
ENABLED_PLUGINS=qqpd ./pansou
```

### 步骤2: 访问管理页面

浏览器打开：
```
http://localhost:8888/qqpd/你的QQ号
```

**示例**：
```
http://localhost:8888/qqpd/1234567
```

系统会自动：
1. 根据QQ号生成专属64位hash（不可逆）
2. 重定向到专属管理页面：`http://localhost:8888/qqpd/{hash}`
3. 显示二维码供扫码登录

**📌 提示**：请收藏hash后的URL（包含你的专属hash），方便下次访问。

### 步骤3: 扫码登录

1. 页面会自动显示QQ登录二维码
2. 使用**手机QQ**扫描二维码
3. 扫码后系统会**自动检测登录状态**（每2秒检查一次）
4. 登录成功后自动显示用户信息

### 步骤4: 配置频道

在"频道管理"区域输入频道号，**每行一个**：

```
pd97631607
languan8K115
m250319e25
```

**支持格式**：
- ✅ 纯频道号：`pd97631607`
- ✅ 完整URL：`https://pd.qq.com/g/pd97631607`

点击"**保存频道配置**"按钮。

### 步骤5: 开始搜索

在PanSou主页搜索框输入关键词，系统会**自动聚合所有用户**的QQ频道结果！

```bash
# 通过API搜索
curl "http://localhost:8888/api/search?kw=遮天"

# 只搜索插件（包括qqpd）
curl "http://localhost:8888/api/search?kw=遮天&src=plugin"
```

## 📡 API文档

### 统一接口

所有操作通过统一的POST接口：

```
POST /qqpd/{hash}
Content-Type: application/json

{
  "action": "操作类型",
  ...其他参数
}
```

### API列表

| Action | 说明 | 需要登录 | 前端调用时机 |
|--------|------|---------|-------------|
| `get_status` | 获取状态 | ❌ | 每3秒自动调用 |
| `refresh_qrcode` | 刷新二维码 | ❌ | 用户点击刷新按钮 |
| `check_login` | 检查登录状态 | ❌ | 未登录时每2秒调用 |
| `logout` | 退出登录 | ✅ | 用户点击退出按钮 |
| `set_channels` | 设置频道列表 | ✅ | 用户点击保存按钮 |
| `test_search` | 测试搜索 | ✅ | 用户点击搜索按钮 |

---

### 1️⃣ get_status - 获取用户状态

**作用**：获取当前用户的登录状态、频道配置等信息

**请求**：
```bash
curl -X POST "http://localhost:8888/qqpd/{hash}" \
  -H "Content-Type: application/json" \
  -d '{"action": "get_status"}'
```

**成功响应（已登录）**：
```json
{
  "success": true,
  "message": "获取成功",
  "data": {
    "hash": "1dd868cc...",
    "logged_in": true,
    "status": "active",
    "qq_masked": "1851****32",
    "login_time": "2025-10-24 12:00:00",
    "expire_time": "2035-10-24 12:00:00",
    "expires_in_days": 3650,
    "channels": ["pd97631607", "kuake12345"],
    "channel_count": 2,
    "qrcode_base64": ""
  }
}
```

**成功响应（未登录）**：
```json
{
  "success": true,
  "message": "获取成功",
  "data": {
    "hash": "1dd868cc...",
    "logged_in": false,
    "status": "pending",
    "qq_masked": "",
    "channels": [],
    "channel_count": 0,
    "qrcode_base64": "data:image/png;base64,iVBORw0KGgo..."  // Base64二维码
  }
}
```

---

### 2️⃣ refresh_qrcode - 刷新二维码

**作用**：强制生成新的二维码（当二维码过期时）

**请求**：
```bash
curl -X POST "http://localhost:8888/qqpd/{hash}" \
  -H "Content-Type: application/json" \
  -d '{"action": "refresh_qrcode"}'
```

**成功响应**：
```json
{
  "success": true,
  "message": "二维码已刷新",
  "data": {
    "qrcode_base64": "data:image/png;base64,iVBORw0KGgo..."
  }
}
```

**说明**：
- 二维码有效期约2分钟
- 系统会自动缓存30秒，避免频繁生成
- 过期后需要点击刷新

---

### 3️⃣ check_login - 检查登录状态

**作用**：检查二维码是否被扫描，登录是否成功（扫码后轮询调用）

**请求**：
```bash
curl -X POST "http://localhost:8888/qqpd/{hash}" \
  -H "Content-Type: application/json" \
  -d '{"action": "check_login"}'
```

**响应（等待扫码）**：
```json
{
  "success": true,
  "message": "等待扫码",
  "data": {
    "login_status": "waiting"
  }
}
```

**响应（登录成功）**：
```json
{
  "success": true,
  "message": "登录成功",
  "data": {
    "login_status": "success",
    "qq_masked": "1851****32"
  }
}
```

**响应（二维码过期）**：
```json
{
  "success": false,
  "message": "二维码已失效，请刷新"
}
```

**说明**：
- 前端未登录时每2秒自动调用
- 登录成功后前端会停止轮询
- 后端会自动获取完整Cookie并保存

---

### 4️⃣ logout - 退出登录

**作用**：清除Cookie，退出登录状态

**请求**：
```bash
curl -X POST "http://localhost:8888/qqpd/{hash}" \
  -H "Content-Type: application/json" \
  -d '{"action": "logout"}'
```

**成功响应**：
```json
{
  "success": true,
  "message": "已退出登录",
  "data": {
    "status": "pending"
  }
}
```

---

### 5️⃣ set_channels - 设置频道列表

**作用**：配置或更新频道列表（覆盖式更新）

**请求**：
```bash
curl -X POST "http://localhost:8888/qqpd/{hash}" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "set_channels",
    "channels": ["pd97631607", "kuake12345", "https://pd.qq.com/g/languan8K115"]
  }'
```

**成功响应**：
```json
{
  "success": true,
  "message": "频道列表已更新",
  "data": {
    "channels": ["pd97631607", "kuake12345", "languan8K115"],
    "channel_count": 3,
    "invalid_channels": [],
    "guild_ids_cached": 3
  }
}
```

**说明**：
- 自动提取频道号（支持URL格式）
- 自动去重
- 自动获取并缓存guild_id（首次添加频道时）
- guild_id永久缓存，搜索时0网络请求

---

### 6️⃣ test_search - 测试搜索

**作用**：在管理页面测试搜索功能

**请求**：
```bash
curl -X POST "http://localhost:8888/qqpd/{hash}" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "test_search",
    "keyword": "遮天",
    "max_results": 10
  }'
```

**参数**：
- `keyword`（必需）：搜索关键词
- `max_results`（可选）：最大返回数量，默认10

**成功响应**：
```json
{
  "success": true,
  "message": "找到 5 条结果",
  "data": {
    "keyword": "遮天",
    "total_results": 5,
    "channels_searched": ["pd97631607", "kuake12345", "languan8K115"],
    "results": [
      {
        "unique_id": "qqpd-pd97631607-0",
        "title": "遮天 (2023) 臻彩4K.更新至109集",
        "links": [
          {
            "type": "quark",
            "url": "https://pan.quark.cn/s/779d98f49e88",
            "password": ""
          }
        ]
      },
      ...
    ]
  }
}
```

---

## 🔧 配置说明

### 环境变量（可选）

```bash
# Hash Salt（推荐自定义，增强安全性）
export QQPD_HASH_SALT="your-custom-salt-here"

# Cookie加密密钥（32字节，推荐自定义）
export QQPD_ENCRYPTION_KEY="your-32-byte-key-here!!!!!!!!!!"
```

### 代码内配置

在 `qqpd.go` 第33-37行修改：

```go
const (
    MaxConcurrentUsers    = 10              // 最多使用的用户数（搜索时）
    MaxConcurrentChannels = 50              // 最大并发频道数
    KeepAliveInterval     = 3 * time.Minute // Session保活间隔
    DebugLog              = false           // 调试日志开关
)
```

**参数说明**：

| 参数 | 默认值 | 说明 | 建议 |
|------|--------|------|------|
| `MaxConcurrentUsers` | 10 | 单次搜索最多使用的用户数 | 10-20足够 |
| `MaxConcurrentChannels` | 50 | 最大并发频道数 | 50-100 |
| `KeepAliveInterval` | 3分钟 | Session保活间隔 | 2-5分钟 |
| `DebugLog` | false | 是否开启调试日志 | 生产环境false |

## 📂 数据存储

### 存储位置

```
cache/qqpd_users/{hash}.json
```

### 数据结构

```json
{
  "hash": "1dd868cc97f5540db170bb3208a4ad737cd7aea3e8df85535178dcbacfa46300",
  "qq_masked": "123**67",
  "cookie": "p_skey=xxx; uin=xxx; ...",
  "status": "active",
  "channels": ["pd97631607", "kuake12345", "languan8K115"],
  "channel_guild_ids": {
    "pd97631607": "592843764045681811",
    "kuake12345": "987654321098765432",
    "languan8K115": "612109904026776189"
  },
  "created_at": "2025-10-24T12:00:00+08:00",
  "login_at": "2025-10-24T12:05:00+08:00",
  "expire_at": "2035-10-24T12:05:00+08:00",
  "last_access_at": "2025-10-24T13:00:00+08:00"
}
```

**字段说明**：
- `hash`: 用户唯一标识（SHA256，不可逆推QQ号）
- `qq_masked`: 脱敏QQ号（如`1851****32`）
- `cookie`: QQ登录Cookie（明文存储，建议配置加密）
- `status`: 用户状态（`pending`/`active`/`expired`）
- `channels`: 频道号列表
- `channel_guild_ids`: 频道号→guild_id映射（性能优化缓存）
- `expire_at`: Cookie过期时间

## 🔒 安全特性

### 1. QQ号隐私保护

- ✅ **不存储明文QQ号**：只存储SHA256 hash（64位十六进制）
- ✅ **不可逆**：无法从hash反推QQ号
- ✅ **加盐hash**：支持自定义salt，进一步增强安全性

### 2. Cookie安全

- ⚠️ **当前**：明文存储到JSON（方便调试）
- ✅ **可选**：通过环境变量配置加密密钥
- ✅ **建议**：生产环境配置`QQPD_ENCRYPTION_KEY`

### 3. 自动清理

**定期清理任务**（每24小时）：
- 删除：状态为`expired`且30天未访问的用户
- 标记：90天未访问的用户标记为`expired`

### 4. 二维码安全

- ✅ 每次生成新的qrsig
- ✅ 30秒缓存，减少暴露
- ✅ 2分钟自动过期

## ⚙️ 高级特性

### 1. Session保活机制 🆕

**问题**：QQ频道的Cookie会在2天左右失效，导致搜索失败。

**解决方案**：自动保活机制

```
插件启动后:
  ↓ 延迟3分钟
  ↓
定期执行保活 (每3分钟):
  ↓
遍历所有active用户:
  ↓ 异步执行
  访问 https://pd.qq.com/ (带Cookie)
  ↓
刷新服务器端session
  ↓
Cookie保持活跃状态 ✅
```

**工作原理**：
- 🔄 每3分钟访问一次QQ频道首页
- 🍪 携带用户Cookie发送请求
- 💓 让QQ服务器知道session还活跃
- ⚡ 异步执行，不阻塞搜索功能

**日志示例**（DebugLog=true时）：
```
[QQPD] 💓 Session保活: 已为 2 个用户执行保活任务
[QQPD] 💓 Session保活成功: 1851****32 (状态码: 200)
[QQPD] 💓 Session保活成功: 1234****56 (状态码: 200)
```

**配置建议**：
- 默认间隔：3分钟（推荐）
- 可调整范围：2-5分钟
- 太频繁：可能被视为异常
- 太慢：可能无法防止超时

### 2. 多用户支持

**场景**：多个用户各自配置不同的频道

```
用户A (QQ: 111111111)
  ↓
配置频道: [频道1, 频道2, 频道3]

用户B (QQ: 222222222)
  ↓
配置频道: [频道2, 频道4, 频道5]

用户C (QQ: 333333333)
  ↓
配置频道: [频道3, 频道5, 频道6]

搜索时:
  ↓
去重后的频道: [频道1, 频道2, 频道3, 频道4, 频道5, 频道6]
  ↓
负载均衡分配:
  - 用户A: 搜索频道1, 频道4
  - 用户B: 搜索频道2, 频道5
  - 用户C: 搜索频道3, 频道6
```

### 3. guild_id缓存优化

**性能提升**：
```
首次保存频道:
  pd97631607 → 访问 https://pd.qq.com/g/pd97631607
              → 提取 guild_id: 592843764045681811
              → 缓存到JSON

搜索时:
  pd97631607 → 从内存读取 guild_id: 592843764045681811
              → 0网络请求 ⚡
              → 直接调用搜索API
```

**效果**：
- 首次配置：稍慢（需要获取guild_id）
- 后续搜索：极快（从内存读取）
- 性能提升：每个频道节省100-200ms

### 4. 智能去重

```
用户A配置: [频道1, 频道2, 频道3]
用户B配置: [频道2, 频道3, 频道4]

去重后:
  频道1 → 分配给用户A
  频道2 → 分配给用户A（负载均衡）
  频道3 → 分配给用户B（负载均衡）
  频道4 → 分配给用户B
```

### 5. 负载均衡

```
任务分配算法:
  for each 频道:
    选择当前任务数最少的用户来执行
  
效果:
  - 避免单用户请求过多被限流
  - 任务均匀分配
  - 提高成功率
```

---

## File: plugin/weibo/README.md

# 微博搜索插件 (Weibo)

## 📖 简介

Weibo是PanSou的微博搜索插件，支持多用户登录微博并配置要搜索的微博用户，在搜索时自动聚合所有配置的微博用户发布的资源链接（从微博正文和评论中提取）。

## ✨ 核心特性

- ✅ **多账户支持** - 每个微博账户独立配置，互不干扰
- ✅ **扫码登录** - 手机微博扫码，自动获取Cookie
- ✅ **多微博用户** - 每个账户可配置多个要搜索的微博用户
- ✅ **评论提取** - 自动提取微博正文和评论中的网盘链接
- ✅ **智能去重** - 多账户配置相同微博用户时自动去重
- ✅ **负载均衡** - 任务均匀分配，避免单账户限流
- ✅ **内存缓存** - 用户数据缓存到内存，搜索性能极高
- ✅ **持久化存储** - Cookie和配置自动保存，重启不丢失
- ✅ **Web管理界面** - 一站式配置，简单易用
- ✅ **RESTful API** - 支持程序化调用

## 🚀 快速开始

### 步骤1: 启动服务

```bash
cd /path/to/pansou
ENABLED_PLUGINS=weibo go run main.go

# 或者编译后运行
go build -o pansou main.go
ENABLED_PLUGINS=weibo ./pansou
```

### 步骤2: 访问管理页面

浏览器打开：
```
http://localhost:8888/weibo/你的微博用户名
```

**示例**：
```
http://localhost:8888/weibo/pansou123
```

系统会自动：
1. 根据用户名生成专属64位hash（不可逆）
2. 重定向到专属管理页面：`http://localhost:8888/weibo/{hash}`
3. 显示二维码供扫码登录

**📌 提示**：请收藏hash后的URL（包含你的专属hash），方便下次访问。

### 步骤3: 扫码登录

1. 页面会自动显示微博登录二维码
2. 使用**手机微博APP**扫描二维码
3. 扫码后系统会**自动检测登录状态**（每2秒检查一次）
4. 登录成功后自动显示用户信息

### 步骤4: 配置微博用户

在"微博用户管理"区域输入要搜索的微博用户ID，**每行一个**：

```
1234567890
2345678901
3456789012
```

**支持格式**：
- ✅ 纯用户ID：`1234567890`
- ✅ 完整URL：`https://weibo.com/u/1234567890`

点击"**保存配置**"按钮。

**📌 如何获取微博用户ID？**
1. 访问目标微博用户主页
2. 查看URL：`https://weibo.com/u/1234567890`
3. 其中 `1234567890` 就是用户ID

### 步骤5: 开始搜索

在PanSou主页搜索框输入关键词，系统会**自动搜索所有配置的微博用户**的微博内容！

```bash
# 通过API搜索
curl "http://localhost:8888/api/search?kw=唐朝诡事录"

# 只搜索插件（包括weibo）
curl "http://localhost:8888/api/search?kw=唐朝诡事录&src=plugin"
```

## 📡 API文档

### 统一接口

所有操作通过统一的POST接口：

```
POST /weibo/{hash}
Content-Type: application/json

{
  "action": "操作类型",
  ...其他参数
}
```

### API列表

| Action | 说明 | 需要登录 | 前端调用时机 |
|--------|------|---------|-------------|
| `get_status` | 获取状态 | ❌ | 每3秒自动调用 |
| `refresh_qrcode` | 刷新二维码 | ❌ | 用户点击刷新按钮 |
| `check_login` | 检查登录状态 | ❌ | 未登录时每2秒调用 |
| `logout` | 退出登录 | ✅ | 用户点击退出按钮 |
| `set_users` | 设置微博用户列表 | ✅ | 用户点击保存按钮 |
| `test_search` | 测试搜索 | ✅ | 用户点击搜索按钮 |

---

### 1️⃣ get_status - 获取账户状态

**作用**：获取当前账户的登录状态、配置的微博用户等信息

**请求**：
```bash
curl -X POST "http://localhost:8888/weibo/{hash}" \
  -H "Content-Type: application/json" \
  -d '{"action": "get_status"}'
```

**成功响应（已登录）**：
```json
{
  "success": true,
  "message": "获取成功",
  "data": {
    "hash": "abc123...",
    "logged_in": true,
    "status": "active",
    "username_masked": "pa****ou",
    "login_time": "2025-10-28 12:00:00",
    "expire_time": "2026-10-28 12:00:00",
    "expires_in_days": 365,
    "weibo_users": ["1234567890", "2345678901"],
    "user_count": 2,
    "qrcode_base64": ""
  }
}
```

**成功响应（未登录）**：
```json
{
  "success": true,
  "message": "获取成功",
  "data": {
    "hash": "abc123...",
    "logged_in": false,
    "status": "pending",
    "username_masked": "",
    "weibo_users": [],
    "user_count": 0,
    "qrcode_base64": "data:image/png;base64,iVBORw0KGgo..."
  }
}
```

---

### 2️⃣ refresh_qrcode - 刷新二维码

**作用**：强制生成新的二维码（当二维码过期时）

**请求**：
```bash
curl -X POST "http://localhost:8888/weibo/{hash}" \
  -H "Content-Type: application/json" \
  -d '{"action": "refresh_qrcode"}'
```

**成功响应**：
```json
{
  "success": true,
  "message": "二维码已刷新",
  "data": {
    "qrcode_base64": "data:image/png;base64,iVBORw0KGgo..."
  }
}
```

**说明**：
- 二维码有效期约2-3分钟
- 过期后需要点击刷新

---

### 3️⃣ check_login - 检查登录状态

**作用**：检查二维码是否被扫描，登录是否成功（扫码后轮询调用）

**请求**：
```bash
curl -X POST "http://localhost:8888/weibo/{hash}" \
  -H "Content-Type: application/json" \
  -d '{"action": "check_login"}'
```

**响应（等待扫码）**：
```json
{
  "success": true,
  "message": "等待扫码",
  "data": {
    "login_status": "waiting"
  }
}
```

**响应（登录成功）**：
```json
{
  "success": true,
  "message": "登录成功",
  "data": {
    "login_status": "success",
    "username_masked": "pa****ou"
  }
}
```

**响应（二维码过期）**：
```json
{
  "success": false,
  "message": "二维码已失效，请刷新"
}
```

**说明**：
- 前端未登录时每2秒自动调用
- 登录成功后前端会停止轮询
- 后端会自动获取完整Cookie并保存

---

### 4️⃣ logout - 退出登录

**作用**：清除Cookie，退出登录状态

**请求**：
```bash
curl -X POST "http://localhost:8888/weibo/{hash}" \
  -H "Content-Type: application/json" \
  -d '{"action": "logout"}'
```

**成功响应**：
```json
{
  "success": true,
  "message": "已退出登录",
  "data": {
    "status": "pending"
  }
}
```

---

### 5️⃣ set_users - 设置微博用户列表

**作用**：配置或更新要搜索的微博用户列表（覆盖式更新）

**请求**：
```bash
curl -X POST "http://localhost:8888/weibo/{hash}" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "set_users",
    "users": ["1234567890", "2345678901", "https://weibo.com/u/3456789012"]
  }'
```

**成功响应**：
```json
{
  "success": true,
  "message": "微博用户列表已更新",
  "data": {
    "weibo_users": ["1234567890", "2345678901", "3456789012"],
    "user_count": 3,
    "invalid_users": []
  }
}
```

**说明**：
- 自动提取用户ID（支持URL格式）
- 自动去重
- 只保存数字格式的用户ID

---

### 6️⃣ test_search - 测试搜索

**作用**：在管理页面测试搜索功能

**请求**：
```bash
curl -X POST "http://localhost:8888/weibo/{hash}" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "test_search",
    "keyword": "唐朝诡事录"
  }'
```

**参数**：
- `keyword`（必需）：搜索关键词

**成功响应**：
```json
{
  "success": true,
  "message": "找到 3 条结果",
  "data": {
    "keyword": "唐朝诡事录",
    "total_results": 3,
    "users_searched": ["1234567890", "2345678901"],
    "results": [
      {
        "unique_id": "weibo-1234567890-M_Pqs5eOb",
        "title": "唐朝诡事录 全集",
        "content": "唐朝诡事录更新至40集...",
        "links": [
          {
            "type": "quark",
            "url": "https://pan.quark.cn/s/xxxxx",
            "password": ""
          }
        ]
      }
    ]
  }
}
```

---

## 🔧 配置说明

### 环境变量（可选）

```bash
# Hash Salt（推荐自定义，增强安全性）
export WEIBO_HASH_SALT="your-custom-salt-here"

# Cookie加密密钥（32字节，推荐自定义）
export WEIBO_ENCRYPTION_KEY="your-32-byte-key-here!!!!!!!!!!"
```

### 代码内配置

在 `weibo.go` 第28-33行修改：

```go
const (
    MaxConcurrentUsers = 10  // 最多同时搜索多少个微博账户
    MaxConcurrentWeibo = 30  // 最多同时处理多少条微博（获取评论）
    MaxComments        = 1   // 每条微博最多获取多少条评论
    DebugLog           = false
)
```

**参数说明**：

| 参数 | 默认值 | 说明 | 建议 |
|------|--------|------|------|
| `MaxConcurrentUsers` | 10 | 单次搜索最多使用的微博账户数 | 5-10足够 |
| `MaxConcurrentWeibo` | 30 | 最大并发处理微博数（获取评论） | 20-50 |
| `MaxComments` | 1 | 每条微博最多获取多少条评论 | 1-3条 |
| `DebugLog` | false | 是否开启调试日志 | 生产环境false |

## 📂 数据存储

### 存储位置

```
cache/weibo_users/{hash}.json
```

### 数据结构

```json
{
  "hash": "abc123...",
  "username_masked": "pa****ou",
  "cookie": "SUB=xxx; SUBP=xxx; ...",
  "status": "active",
  "weibo_users": ["1234567890", "2345678901", "3456789012"],
  "created_at": "2025-11-19T12:00:00+08:00",
  "login_at": "2025-11-19T12:00:00+08:00",
  "expire_at": "2026-11-19T12:00:00+08:00",
  "last_access_at": "2025-11-19T13:00:00+08:00"
}
```

**字段说明**：
- `hash`: 账户唯一标识（SHA256，不可逆）
- `username_masked`: 脱敏用户名（如`pa****ou`）
- `cookie`: 微博登录Cookie（明文存储，建议配置加密）
- `status`: 账户状态（`pending`/`active`/`expired`）
- `weibo_users`: 要搜索的微博用户ID列表
- `expire_at`: Cookie过期时间

## 🔒 安全特性

### 1. 用户名隐私保护

- ✅ **不存储明文用户名**：只存储SHA256 hash（64位十六进制）
- ✅ **不可逆**：无法从hash反推用户名
- ✅ **加盐hash**：支持自定义salt，进一步增强安全性

### 2. Cookie安全

- ⚠️ **当前**：明文存储到JSON（方便调试）
- ✅ **可选**：通过环境变量配置加密密钥
- ✅ **建议**：生产环境配置`WEIBO_ENCRYPTION_KEY`

### 3. 自动清理

**定期清理任务**（每24小时）：
- 删除：状态为`expired`且30天未访问的账户
- 标记：90天未访问的账户标记为`expired`

## ⚙️ 工作原理

### 搜索流程

```
用户搜索关键词 "唐朝诡事录"
  ↓
加载所有active状态的微博账户
  ↓
取最近访问的前10个账户（负载均衡）
  ↓
为每个账户分配要搜索的微博用户
  ↓
并发执行:
  账户A → 搜索微博用户 1234567890
  账户B → 搜索微博用户 2345678901
  账户C → 搜索微博用户 3456789012
  ↓
  对每个微博用户:
    1. 获取前3页微博列表
    2. 过滤包含关键词的微博
    3. 提取微博正文中的网盘链接
    4. 获取第1条评论（可配置）
    5. 提取评论中的网盘链接
  ↓
合并所有账户的搜索结果
  ↓
去重（基于微博ID）
  ↓
返回最终结果
```

### 链接提取

**支持的网盘类型**：
- 夸克网盘：`https://pan.quark.cn/s/xxxxx`
- 阿里云盘：`https://www.alipan.com/s/xxxxx`
- 百度网盘：`https://pan.baidu.com/s/xxxxx`
- 其他常见网盘

**提取位置**：
1. 微博正文
2. 微博评论（默认取第1条）

### 负载均衡

```
账户A配置: [用户1, 用户2, 用户3, 用户4]
账户B配置: [用户2, 用户3, 用户5, 用户6]
账户C配置: [用户1, 用户5, 用户7]

去重后要搜索的微博用户:
  [用户1, 用户2, 用户3, 用户4, 用户5, 用户6, 用户7]

任务分配（轮询）:
  用户1 → 账户A
  用户2 → 账户B
  用户3 → 账户C
  用户4 → 账户A
  用户5 → 账户B
  用户6 → 账户C
  用户7 → 账户A
```

## 🎯 使用场景

### 场景1: 追剧更新

配置几个经常分享资源的微博用户，自动获取最新更新的剧集链接。

### 场景2: 资源聚合

配置多个不同领域的资源分享博主，一次搜索聚合所有相关资源。

### 场景3: 团队协作

团队成员各自配置自己的微博账户和关注的资源博主，共享搜索结果。

## 📝 注意事项

1. **Cookie有效期**：微博Cookie约1年有效，过期需要重新登录
2. **请求限制**：单个账户请求过快可能被限流，建议配置多个账户
3. **评论获取**：默认只获取每条微博的第1条评论，可通过`MaxComments`调整
4. **用户ID格式**：必须是纯数字格式（如`1234567890`），不支持个性化域名

## 🔍 故障排查

### 问题1: 搜索无结果

**可能原因**：
- 配置的微博用户没有发布包含关键词的内容
- Cookie已过期，需要重新登录
- 微博用户ID配置错误

**解决方法**：
1. 检查管理页面的登录状态
2. 使用"测试搜索"功能验证
3. 确认微博用户ID格式正确

### 问题2: 登录失败

**可能原因**：
- 二维码已过期
- 网络问题
- 微博安全策略限制

**解决方法**：
1. 点击"刷新二维码"重试
2. 检查网络连接
3. 尝试更换网络环境

### 问题3: Cookie频繁失效

**可能原因**：
- 账户在其他设备登录
- 账户安全策略
- 请求频率过高

**解决方法**：
1. 减少请求频率
2. 配置多个账户分散请求
3. 检查账户安全设置

## 📚 更多信息

- [PanSou 项目主页](https://github.com/fish2018/pansou)
- [插件开发指南](../../docs/插件开发指南.md)
- [常见问题](https://github.com/fish2018/pansou/issues)