Metadata-Version: 2.4
Name: revQianWen
Version: 0.1.1
Summary: Reverse client for QianWen web chat API.
Author: lhc
License-Expression: MIT
Project-URL: Homepage, https://github.com/WhyPilotXia/revQianWen
Project-URL: Repository, https://github.com/WhyPilotXia/revQianWen
Keywords: qianwen,tongyi,chatbot,reverse-engineering
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.28.0
Dynamic: license-file

# revQianWen

`revQianWen` 是一个 Qwen Web 端逆向 Python 库，由于本人从 2023 年用到 2026 年 8 月的 [revTongYi](https://github.com/leeeduke/revTongYi) 已长期停更，且 2026 年 8 月 Qwen 接口更新后旧库不再可用，因此开发此库，用于对接千问新版 Web 接口。

支持流式和非流式聊天接口，以及会话列表、历史消息、改标题、删除会话、图片上传相关接口。图片上传还有鉴权 todo，由于本人不用暂不深挖，欢迎 PR。覆盖新版 `chat2.qianwen.com/api/v2/chat` SSE 聊天协议。

## 安装

在本地开发目录中安装：

```bash
pip install revQianWen
```

## 快速开始

```python
import revQianWen as qwen

bot = qwen.Chatbot(cookies_str="从浏览器复制的 Cookie")
resp = bot.ask("你好呀，请只回复 ok")
print(resp.contents[-1].content)
```

流式调用：

```python
for resp in bot.ask("写一段自我介绍", stream=True):
    if resp.contents:
        print(resp.contents[-1].content)
```

cookie获取方式：
<img width="3366" height="1876" alt="image" src="https://github.com/user-attachments/assets/a83cb415-2eab-4357-ae27-53a61d81db51" />


## 基础接口

主要通过顶层包直接使用：

```python
import revQianWen as qwen

bot = qwen.Chatbot(cookies={...})
bot = qwen.Chatbot(cookies_str="...")
resp = bot.ask(prompt="...", parentId="0", sessionId="", stream=False)
```

返回对象支持属性访问和字典式访问：

```python
print(resp.sessionId)
print(resp.msgId)
print(resp.contents[-1].content)
print(resp["sessionId"])
print(resp.to_dict())
```

## 会话接口

创建本地会话上下文：

```python
session = bot.create_session("测试会话")
print(session.session_id)
print(session.topic_id)

resp = bot.ask("第一句话", sessionId=session.session_id, parentId="0")
```

说明：抓包中千问没有独立的新建会话 REST 请求。Web 端是在第一次 `/api/v2/chat` 请求里携带新的 `session_id` 和 `topic_id`，服务端随后生成远端会话。因此 `create_session(...)` 当前只是准备本地会话上下文，真正远端可见需要调用第一轮 `ask(...)`。

列出会话：

```python
sessions = bot.list_session(limit=50)
for item in sessions:
    print(item.session_id, item.title)

print(bot.last_session_page.have_next_page)
print(bot.last_session_page.next_token)
```

读取历史消息：

```python
history = bot.get_session_history(sessionId="会话 ID", page_size=10)
for item in history:
    print(item.request_messages)
    print(item.qwen_response_messages)
```

如果需要完整原始结构：

```python
raw = bot.get_session_history(sessionId="会话 ID", raw=True)
print(raw.list)
```

修改标题：

```python
bot.update_session(sessionId="会话 ID", summary="新的标题")
```

删除会话：

```python
bot.delete_session("会话 ID")
bot.delete_sessions(["会话 ID 1", "会话 ID 2"])
```

同步变更：

```python
changes = bot.sync_changes(last_sync_time=0, limit=100)
print(changes.to_dict())
```

## 图片接口当前形态

当前已经按抓包补齐图片上传链路的代码结构：

```text
1. POST https://workspace-res.qianwen.com/1/oss_token
2. PUT  图片二进制到返回的 Aliyun OSS object URL
3. POST https://workspace-res.qianwen.com/1/oss/callback
4. POST https://chat-side.qianwen.com/api/v2/file/record/add
5. POST https://chat2.qianwen.com/api/v2/chat，messages 里带 image/url + resource_infos
```

已确认可直接跑到的部分：

```text
_get_upload_token(...)
_put_oss_object(...)
```

当前已知限制：

```text
oss/callback 可能返回 code=10009，msg=签名验证失败。
```

原因是 callback、file record、带图 chat 请求在浏览器里会带动态安全头，例如：

```text
clt-acs-sign
clt-acs-bfg
clt-acs-reqt
clt-acs-request-params
eo-clt-actkn
eo-clt-dvidn
eo-clt-sacsft
eo-clt-snver
bx-ua
bx-umidtoken
bx_et
```

这些值看起来由Qwen前端 JS 或风控 SDK 动态生成，不能简单复用固定字符串。因此目前图片上传接口是“协议形态已实现，完整可用性取决于能否提供有效动态安全头”。

### 传入动态安全头

如果已经从浏览器抓包拿到了同一时刻可用的安全头，可以在初始化时传入：

```python
bot = qianwen.Chatbot(
    cookies_str="从浏览器复制的 Cookie",
    user_id="1715511985223686",
    device_id="01c75faf-0831-aedc-e49f-58ca08f126fa",
    security_headers={
        "clt-acs-caer": "vrad",
        "clt-acs-reqt": "...",
        "clt-acs-request-params": "...",
        "clt-acs-sign": "...",
        "eo-clt-actkn": "...",
        "eo-clt-dvidn": "...",
        "eo-clt-sacsft": "...",
        "eo-clt-snver": "lv",
    },
)
```

也可以在运行过程中更新：

```python
bot.set_security_headers({
    "clt-acs-sign": "...",
    "eo-clt-actkn": "...",
})

bot.clear_security_headers()
```

### 上传本地图片并提问

```python
with open("image.png", "rb") as f:
    image_bytes = f.read()

resp = bot.ask("这张图里有什么？", image=image_bytes)
print(resp.contents[-1].content)
```

注意：如果没有有效动态安全头，这一步可能会在 `oss/callback` 返回签名验证失败。

### 复用已上传图片资源

如果你已经通过浏览器或抓包拿到了 `material_id` 和 CDN URL，可以绕过上传阶段，直接构造 `image_info` 发带图聊天：

```python
image_info = bot.make_image_info(
    url="https://workspace-zb-cdn.qianwen.com/xxx.png?auth_key=...",
    material_id="f38851819ef6490fb4f1023e53ae8ca9",
    file_format="PNG",
    file_name="image.png",
    file_size=435722,
    width=1840,
    height=872,
)

resp = bot.ask("哪些是压缩文件", image_info=image_info)
print(resp.contents[-1].content)
```

## 已实现接口

```text
ask(...)
create_session(...)
list_session(...)
sync_changes(...)
update_session(...)
delete_session(...)
delete_sessions(...)
get_session_history(...)
_get_upload_token(...)
_put_oss_object(...)
_get_download_link(...)
_add_file_record(...)
upload_image(...)
make_image_info(...)
set_security_headers(...)
clear_security_headers(...)
```

## 已知仍缺或不完整

```text
1. 浏览器动态安全签名生成逻辑尚未逆向，因此图片 callback/file record/带图 chat 在无有效安全头时可能失败。
2. 文件上传目前按图片链路整理，普通文档、音频、视频等文件类型尚未单独适配。
3. 分享、置顶、归档、收藏、标签筛选细节等会话管理接口还没有抓包确认。
4. 多模型切换、深度搜索、联网搜索开关等高级参数只保留默认值，尚未系统化封装。
```

## 注意事项

本项目依赖用户从浏览器中复制的有效Qwen Cookie，不绕过登录或鉴权。请只用于个人自动化、调试和学习用途。Qwen Web 接口可能随时变更，如果出现协议错误，需要重新抓包确认参数和 header。
