mirror of
https://github.com/Evil0ctal/WeChat-Channels-Video-File-Decryption.git
synced 2026-04-06 04:24:16 +08:00
Add Files
This commit is contained in:
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# 基于编辑器的 HTTP 客户端请求
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
10
.idea/WeChat-Channels-Video-File-Decryption.iml
generated
Normal file
10
.idea/WeChat-Channels-Video-File-Decryption.iml
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3.12 (WeChat-Channels-Video-File-Decryption)" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
6
.idea/copilot.data.migration.agent.xml
generated
Normal file
6
.idea/copilot.data.migration.agent.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AgentMigrationStateService">
|
||||||
|
<option name="migrationStatus" value="COMPLETED" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/copilot.data.migration.ask.xml
generated
Normal file
6
.idea/copilot.data.migration.ask.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AskMigrationStateService">
|
||||||
|
<option name="migrationStatus" value="COMPLETED" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/copilot.data.migration.ask2agent.xml
generated
Normal file
6
.idea/copilot.data.migration.ask2agent.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Ask2AgentMigrationStateService">
|
||||||
|
<option name="migrationStatus" value="COMPLETED" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/copilot.data.migration.edit.xml
generated
Normal file
6
.idea/copilot.data.migration.edit.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="EditMigrationStateService">
|
||||||
|
<option name="migrationStatus" value="COMPLETED" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
154
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
154
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<Languages>
|
||||||
|
<language minSize="2008" name="Python" />
|
||||||
|
</Languages>
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="HttpUrlsUsage" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoredUrls">
|
||||||
|
<list>
|
||||||
|
<option value="http://0.0.0.0" />
|
||||||
|
<option value="http://127.0.0.1" />
|
||||||
|
<option value="http://activemq.apache.org/schema/" />
|
||||||
|
<option value="http://cxf.apache.org/schemas/" />
|
||||||
|
<option value="http://java.sun.com/" />
|
||||||
|
<option value="http://javafx.com/fxml" />
|
||||||
|
<option value="http://javafx.com/javafx/" />
|
||||||
|
<option value="http://json-schema.org/draft" />
|
||||||
|
<option value="http://localhost" />
|
||||||
|
<option value="http://maven.apache.org/POM/" />
|
||||||
|
<option value="http://maven.apache.org/xsd/" />
|
||||||
|
<option value="http://primefaces.org/ui" />
|
||||||
|
<option value="http://schema.cloudfoundry.org/spring/" />
|
||||||
|
<option value="http://schemas.xmlsoap.org/" />
|
||||||
|
<option value="http://tiles.apache.org/" />
|
||||||
|
<option value="http://www.ibm.com/webservices/xsd" />
|
||||||
|
<option value="http://www.jboss.com/xml/ns/" />
|
||||||
|
<option value="http://www.jboss.org/j2ee/schema/" />
|
||||||
|
<option value="http://www.nbdatamarket.top" />
|
||||||
|
<option value="http://www.springframework.org/schema/" />
|
||||||
|
<option value="http://www.springframework.org/security/tags" />
|
||||||
|
<option value="http://www.springframework.org/tags" />
|
||||||
|
<option value="http://www.thymeleaf.org" />
|
||||||
|
<option value="http://www.w3.org/" />
|
||||||
|
<option value="http://xmlns.jcp.org/" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoredPackages">
|
||||||
|
<list>
|
||||||
|
<option value="aiohttp" />
|
||||||
|
<option value="aiomysql" />
|
||||||
|
<option value="aiosignal" />
|
||||||
|
<option value="anyio" />
|
||||||
|
<option value="async-timeout" />
|
||||||
|
<option value="attrs" />
|
||||||
|
<option value="bcrypt" />
|
||||||
|
<option value="beautifulsoup4" />
|
||||||
|
<option value="blinker" />
|
||||||
|
<option value="Brotli" />
|
||||||
|
<option value="certifi" />
|
||||||
|
<option value="charset-normalizer" />
|
||||||
|
<option value="click" />
|
||||||
|
<option value="colorama" />
|
||||||
|
<option value="crypto" />
|
||||||
|
<option value="fastapi" />
|
||||||
|
<option value="Flask" />
|
||||||
|
<option value="frozenlist" />
|
||||||
|
<option value="h11" />
|
||||||
|
<option value="h2" />
|
||||||
|
<option value="hpack" />
|
||||||
|
<option value="httpcore" />
|
||||||
|
<option value="httpx" />
|
||||||
|
<option value="hyperframe" />
|
||||||
|
<option value="idna" />
|
||||||
|
<option value="itsdangerous" />
|
||||||
|
<option value="Jinja2" />
|
||||||
|
<option value="Markdown" />
|
||||||
|
<option value="MarkupSafe" />
|
||||||
|
<option value="multidict" />
|
||||||
|
<option value="Naked" />
|
||||||
|
<option value="openai" />
|
||||||
|
<option value="passlib" />
|
||||||
|
<option value="pycryptodome" />
|
||||||
|
<option value="pydantic" />
|
||||||
|
<option value="PyExecJS" />
|
||||||
|
<option value="PyJWT" />
|
||||||
|
<option value="PyMySQL" />
|
||||||
|
<option value="python-multipart" />
|
||||||
|
<option value="pywebio" />
|
||||||
|
<option value="pywebio-battery" />
|
||||||
|
<option value="PyYAML" />
|
||||||
|
<option value="requests" />
|
||||||
|
<option value="rfc3986" />
|
||||||
|
<option value="shellescape" />
|
||||||
|
<option value="six" />
|
||||||
|
<option value="sniffio" />
|
||||||
|
<option value="socksio" />
|
||||||
|
<option value="soupsieve" />
|
||||||
|
<option value="starlette" />
|
||||||
|
<option value="tenacity" />
|
||||||
|
<option value="tornado" />
|
||||||
|
<option value="tqdm" />
|
||||||
|
<option value="typing_extensions" />
|
||||||
|
<option value="ua-parser" />
|
||||||
|
<option value="URLDecoder" />
|
||||||
|
<option value="urllib3" />
|
||||||
|
<option value="user-agents" />
|
||||||
|
<option value="uvicorn" />
|
||||||
|
<option value="Werkzeug" />
|
||||||
|
<option value="yarl" />
|
||||||
|
<option value="opencv-python" />
|
||||||
|
<option value="numpy" />
|
||||||
|
<option value="Pillow" />
|
||||||
|
<option value="sqlalchemy" />
|
||||||
|
<option value="asyncpg" />
|
||||||
|
<option value="redis" />
|
||||||
|
<option value="aiokafka" />
|
||||||
|
<option value="celery" />
|
||||||
|
<option value="zstandard" />
|
||||||
|
<option value="python-jose" />
|
||||||
|
<option value="python-dotenv" />
|
||||||
|
<option value="pydantic-settings" />
|
||||||
|
<option value="aiofiles" />
|
||||||
|
<option value="structlog" />
|
||||||
|
<option value="prometheus-client" />
|
||||||
|
<option value="python-dateutil" />
|
||||||
|
<option value="pytest" />
|
||||||
|
<option value="pytest-asyncio" />
|
||||||
|
<option value="black" />
|
||||||
|
<option value="isort" />
|
||||||
|
<option value="flake8" />
|
||||||
|
<option value="psutil" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PyPep8Inspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoredErrors">
|
||||||
|
<list>
|
||||||
|
<option value="E501" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoredErrors">
|
||||||
|
<list>
|
||||||
|
<option value="N803" />
|
||||||
|
<option value="N802" />
|
||||||
|
<option value="N806" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PyStubPackagesAdvertiser" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoredPackages">
|
||||||
|
<list>
|
||||||
|
<option value="pandas-stubs==2.3.2.250926" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.12 (WeChat-Channels-Video-File-Decryption)" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (WeChat-Channels-Video-File-Decryption)" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/WeChat-Channels-Video-File-Decryption.iml" filepath="$PROJECT_DIR$/.idea/WeChat-Channels-Video-File-Decryption.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
416
README.md
416
README.md
@@ -1,2 +1,414 @@
|
|||||||
# WeChat-Channels-Video-File-Decryption
|
# 微信视频号解密工具 / WeChat Channels Video Decryption Tool
|
||||||
微信视频号文件解密工具
|
|
||||||
|
一个完整的微信视频号加密视频解密解决方案,基于逆向工程分析实现。本项目使用微信官方的 WebAssembly (WASM) 模块来生成 Isaac64 PRNG 密钥流,并通过 XOR 运算完成视频解密。
|
||||||
|
|
||||||
|
## 📖 项目简介
|
||||||
|
|
||||||
|
微信视频号使用 **Isaac64**(Indirection, Shift, Accumulate, Add, and Count)密码学伪随机数生成器对视频文件的前 128 KB 数据进行加密。本项目通过以下方式实现完整的解密流程:
|
||||||
|
|
||||||
|
- 🔬 **算法分析**:通过逆向工程分析微信客户端,确认使用 Isaac64 PRNG 算法
|
||||||
|
- 🧩 **WASM 模块**:直接使用微信官方的 `wasm_video_decode.wasm` 模块,保证 100% 兼容性
|
||||||
|
- 🔑 **密钥流生成**:基于 API 响应中的 `decode_key` 种子值生成 131,072 字节的密钥流
|
||||||
|
- 🔄 **关键步骤**:密钥流必须经过 `reverse()` 操作(这是成功解密的关键)
|
||||||
|
- ⚡ **XOR 解密**:对视频前 128 KB 执行 XOR 运算,还原原始 MP4 数据
|
||||||
|
- 🎯 **多平台支持**:提供在线网页版、命令行工具、图形界面三种使用方式
|
||||||
|
|
||||||
|
**技术栈:** JavaScript (WASM), Python 3.x, tkinter, HTML5
|
||||||
|
|
||||||
|
## ✨ 特性
|
||||||
|
|
||||||
|
- ✅ 使用微信官方 WASM 模块(保证兼容性)
|
||||||
|
- ✅ 支持完整视频解密
|
||||||
|
- ✅ 提供三种使用方式:在线网页版、命令行版、图形界面版
|
||||||
|
- ✅ 支持交互模式和命令行参数模式
|
||||||
|
- ✅ 包含示例文件和测试数据
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 前置要求
|
||||||
|
|
||||||
|
- Python 3.x
|
||||||
|
- 现代浏览器 (Chrome/Edge/Safari/Firefox)
|
||||||
|
|
||||||
|
### 方式一:图形界面(推荐新手)
|
||||||
|
|
||||||
|
最简单的使用方式,无需命令行操作:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 decrypt_wechat_video_gui.py
|
||||||
|
```
|
||||||
|
|
||||||
|
在图形界面中:
|
||||||
|
1. 选择或粘贴密钥流
|
||||||
|
2. 选择加密视频文件
|
||||||
|
3. 点击"开始解密"按钮
|
||||||
|
4. 等待解密完成
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 方式二:命令行(推荐进阶用户)
|
||||||
|
|
||||||
|
#### 交互模式(推荐)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 decrypt_wechat_video_cli.py
|
||||||
|
```
|
||||||
|
|
||||||
|
按提示操作即可,支持:
|
||||||
|
- 从文件加载密钥流
|
||||||
|
- 直接粘贴十六进制密钥流
|
||||||
|
- 自动验证和引导
|
||||||
|
|
||||||
|
#### 命令行参数模式
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用密钥流文件解密
|
||||||
|
python3 decrypt_wechat_video_cli.py -i wx_encrypted.mp4 -k keystream_131072_bytes.txt -o wx_decrypted.mp4
|
||||||
|
|
||||||
|
# 静默模式(脚本调用)
|
||||||
|
python3 decrypt_wechat_video_cli.py -i encrypted.mp4 -k keystream.txt -o decrypted.mp4 -q
|
||||||
|
|
||||||
|
# 查看帮助
|
||||||
|
python3 decrypt_wechat_video_cli.py --help
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方式三:在线网页版
|
||||||
|
|
||||||
|
#### 步骤 1:生成密钥流
|
||||||
|
|
||||||
|
访问在线页面或本地服务器:
|
||||||
|
```bash
|
||||||
|
# 本地模式
|
||||||
|
python3 -m http.server 8888
|
||||||
|
open http://localhost:8888/index.html
|
||||||
|
|
||||||
|
# 或直接访问 GitHub Pages
|
||||||
|
# https://evil0ctal.github.io/WeChat-Channels-Video-File-Decryption/
|
||||||
|
```
|
||||||
|
|
||||||
|
在网页中:
|
||||||
|
1. 输入 `decode_key`(从 API 响应中获取)
|
||||||
|
2. 点击 "🚀 生成密钥流"
|
||||||
|
3. 点击 "💾 导出密钥流" 下载文件
|
||||||
|
|
||||||
|
#### 步骤 2:解密视频
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 移动密钥流文件到项目目录
|
||||||
|
mv ~/Downloads/keystream_131072_bytes.txt .
|
||||||
|
|
||||||
|
# 使用 CLI 或 GUI 工具解密
|
||||||
|
python3 decrypt_wechat_video_cli.py
|
||||||
|
# 或
|
||||||
|
python3 decrypt_wechat_video_gui.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📁 文件说明
|
||||||
|
|
||||||
|
```
|
||||||
|
WeChat-Channels-Video-File-Decryption/
|
||||||
|
├── index.html # 在线密钥流生成器(GitHub Pages)
|
||||||
|
├── decrypt_wechat_video_cli.py # 命令行解密工具(推荐)
|
||||||
|
├── decrypt_wechat_video_gui.py # 图形界面解密工具(新手友好)
|
||||||
|
├── wx_response.json # API 响应示例(包含 decode_key)
|
||||||
|
├── wx_encrypted.mp4 # 示例加密文件
|
||||||
|
├── wx_decrypted.mp4 # 示例解密文件
|
||||||
|
├── wechat_files/ # 微信官方 WASM 模块
|
||||||
|
│ ├── wasm_video_decode.wasm # Isaac64 WASM 模块
|
||||||
|
│ ├── wasm_video_decode.js # WASM 加载器
|
||||||
|
│ └── ...
|
||||||
|
├── LICENSE # MIT 许可证
|
||||||
|
└── README.md # 本文件
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔑 工作原理
|
||||||
|
|
||||||
|
### 加密方式
|
||||||
|
|
||||||
|
微信视频号使用 **Isaac64 PRNG** 生成密钥流,然后:
|
||||||
|
|
||||||
|
1. 只加密视频的前 **131,072 bytes** (128 KB)
|
||||||
|
2. 使用 **XOR** 进行加密:`encrypted = original ^ keystream`
|
||||||
|
3. **关键步骤**:密钥流必须 **reverse()** 后才能使用
|
||||||
|
|
||||||
|
### 解密流程
|
||||||
|
|
||||||
|
```
|
||||||
|
decode_key → Isaac64 WASM → 生成密钥流 → Reverse → XOR 解密 → MP4 视频
|
||||||
|
```
|
||||||
|
|
||||||
|
### decode_key 获取
|
||||||
|
|
||||||
|
从微信视频号 API 响应中提取:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"object_desc": {
|
||||||
|
"media": [{
|
||||||
|
"decode_key": "2136343393", // 这就是解密种子
|
||||||
|
"url": "https://...",
|
||||||
|
"file_size": 14088528
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 使用示例
|
||||||
|
|
||||||
|
### 示例 1: GUI 图形界面(最简单)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 启动 GUI
|
||||||
|
python3 decrypt_wechat_video_gui.py
|
||||||
|
```
|
||||||
|
|
||||||
|
在图形界面中:
|
||||||
|
1. 如果有 `keystream_131072_bytes.txt` 文件,会自动加载
|
||||||
|
2. 或者点击"选择文件"加载密钥流文件
|
||||||
|
3. 或者直接粘贴十六进制密钥流到文本框
|
||||||
|
4. 选择加密视频文件 `wx_encrypted.mp4`
|
||||||
|
5. 点击"🚀 开始解密"
|
||||||
|
6. 等待完成后点击"📂 打开文件夹"查看结果
|
||||||
|
|
||||||
|
### 示例 2: CLI 交互模式
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 decrypt_wechat_video_cli.py
|
||||||
|
```
|
||||||
|
|
||||||
|
按照提示操作:
|
||||||
|
```
|
||||||
|
🎬 微信视频号解密工具 - 交互模式
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
⚠️ 未找到密钥流文件: keystream_131072_bytes.txt
|
||||||
|
|
||||||
|
请选择输入方式:
|
||||||
|
1. 输入密钥流文件路径
|
||||||
|
2. 直接粘贴十六进制密钥流
|
||||||
|
3. 退出
|
||||||
|
|
||||||
|
请选择 (1/2/3): 1
|
||||||
|
请输入密钥流文件路径: keystream_131072_bytes.txt
|
||||||
|
✅ 密钥流大小: 131,072 bytes (128.00 KB)
|
||||||
|
|
||||||
|
请输入加密视频文件路径: wx_encrypted.mp4
|
||||||
|
|
||||||
|
请输入输出文件名 (默认: wx_decrypted.mp4):
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例 3: CLI 命令行模式(自动化)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 基本用法
|
||||||
|
python3 decrypt_wechat_video_cli.py \
|
||||||
|
-i wx_encrypted.mp4 \
|
||||||
|
-k keystream_131072_bytes.txt \
|
||||||
|
-o wx_decrypted.mp4
|
||||||
|
|
||||||
|
# 静默模式(用于脚本)
|
||||||
|
python3 decrypt_wechat_video_cli.py \
|
||||||
|
-i encrypted.mp4 \
|
||||||
|
-k keystream.txt \
|
||||||
|
-o decrypted.mp4 \
|
||||||
|
-q
|
||||||
|
|
||||||
|
# 使用十六进制字符串
|
||||||
|
python3 decrypt_wechat_video_cli.py \
|
||||||
|
-i encrypted.mp4 \
|
||||||
|
-H "0a1b2c3d4e5f..." \
|
||||||
|
-o decrypted.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例 4: 解密已提供的测试文件
|
||||||
|
|
||||||
|
项目已包含测试文件:
|
||||||
|
- `wx_encrypted.mp4` (加密文件)
|
||||||
|
- `wx_response.json` (包含 decode_key: 2136343393)
|
||||||
|
|
||||||
|
**使用 GUI:**
|
||||||
|
```bash
|
||||||
|
python3 decrypt_wechat_video_gui.py
|
||||||
|
```
|
||||||
|
|
||||||
|
**使用 CLI:**
|
||||||
|
```bash
|
||||||
|
python3 decrypt_wechat_video_cli.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例 5: 解密新视频(完整流程)
|
||||||
|
|
||||||
|
1. **获取视频信息**
|
||||||
|
```bash
|
||||||
|
# 抓包获取 API 响应
|
||||||
|
# 提取 decode_key 和视频 URL
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **下载加密视频**
|
||||||
|
```bash
|
||||||
|
curl -o my_encrypted_video.mp4 "视频URL"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **生成密钥流**
|
||||||
|
|
||||||
|
访问在线页面:
|
||||||
|
- https://evil0ctal.github.io/WeChat-Channels-Video-File-Decryption/
|
||||||
|
|
||||||
|
或本地启动:
|
||||||
|
```bash
|
||||||
|
python3 -m http.server 8888
|
||||||
|
open http://localhost:8888/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
在页面中:
|
||||||
|
- 输入你的 `decode_key`
|
||||||
|
- 点击 "生成密钥流"
|
||||||
|
- 点击 "导出密钥流" 下载文件
|
||||||
|
|
||||||
|
4. **解密视频**
|
||||||
|
```bash
|
||||||
|
# GUI 方式
|
||||||
|
python3 decrypt_wechat_video_gui.py
|
||||||
|
|
||||||
|
# 或 CLI 方式
|
||||||
|
python3 decrypt_wechat_video_cli.py \
|
||||||
|
-i my_encrypted_video.mp4 \
|
||||||
|
-k keystream_131072_bytes.txt \
|
||||||
|
-o my_decrypted_video.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 命令行参数说明
|
||||||
|
|
||||||
|
### CLI 工具参数
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 decrypt_wechat_video_cli.py [OPTIONS]
|
||||||
|
```
|
||||||
|
|
||||||
|
**参数列表:**
|
||||||
|
|
||||||
|
| 参数 | 说明 | 示例 |
|
||||||
|
|------|------|------|
|
||||||
|
| `-i, --input` | 加密视频文件路径 | `-i wx_encrypted.mp4` |
|
||||||
|
| `-o, --output` | 输出文件路径 | `-o wx_decrypted.mp4` |
|
||||||
|
| `-k, --keystream-file` | 密钥流文件路径 | `-k keystream_131072_bytes.txt` |
|
||||||
|
| `-H, --keystream-hex` | 十六进制密钥流字符串 | `-H "0a1b2c3d..."` |
|
||||||
|
| `-q, --quiet` | 静默模式 | `-q` |
|
||||||
|
| `--version` | 显示版本信息 | `--version` |
|
||||||
|
| `-h, --help` | 显示帮助信息 | `--help` |
|
||||||
|
|
||||||
|
**使用技巧:**
|
||||||
|
|
||||||
|
- 不带任何参数运行进入交互模式(推荐新手)
|
||||||
|
- 使用 `-q` 参数进行静默输出,适合脚本调用
|
||||||
|
- 可以使用 `-H` 直接传入密钥流,无需文件
|
||||||
|
- 输出文件默认为 `wx_decrypted.mp4`
|
||||||
|
|
||||||
|
## 🔍 验证解密
|
||||||
|
|
||||||
|
成功解密的视频应该:
|
||||||
|
|
||||||
|
✅ 文件类型:`ISO Media, MP4 Base Media v1`
|
||||||
|
✅ 文件头包含 `ftyp` 签名(offset 4)
|
||||||
|
✅ 可以正常播放
|
||||||
|
|
||||||
|
验证命令:
|
||||||
|
```bash
|
||||||
|
file wx_decrypted.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
应该显示:
|
||||||
|
```
|
||||||
|
wx_decrypted.mp4: ISO Media, MP4 Base Media v1 [ISO 14496-12:2003]
|
||||||
|
```
|
||||||
|
|
||||||
|
或使用 `xxd` 查看文件头:
|
||||||
|
```bash
|
||||||
|
xxd -l 32 wx_decrypted.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
应该看到类似:
|
||||||
|
```
|
||||||
|
00000000: 0000 0020 6674 7970 6973 6f6d 0000 0200 ... ftypisom....
|
||||||
|
00000010: 6973 6f6d 6973 6f32 6165 7631 6d70 3431 isomiso2aev1mp41
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚠️ 重要提示
|
||||||
|
|
||||||
|
1. **必须使用 reverse() 操作**
|
||||||
|
- 密钥流必须反转才能正确解密
|
||||||
|
- HTML 页面已自动处理此步骤
|
||||||
|
|
||||||
|
2. **decode_key 必须匹配**
|
||||||
|
- 每个视频有唯一的 decode_key
|
||||||
|
- 使用错误的 key 会导致解密失败
|
||||||
|
|
||||||
|
3. **只加密前 128KB**
|
||||||
|
- 视频的后续部分未加密
|
||||||
|
- 解密脚本会自动处理
|
||||||
|
|
||||||
|
## 🛠️ 技术细节
|
||||||
|
|
||||||
|
### Isaac64 算法
|
||||||
|
|
||||||
|
- **类型**: 密码学安全的伪随机数生成器
|
||||||
|
- **周期**: 2^8295
|
||||||
|
- **输出**: 64-bit 随机数
|
||||||
|
- **实现**: 微信官方 WASM 模块
|
||||||
|
|
||||||
|
### 关键代码
|
||||||
|
|
||||||
|
**JavaScript (密钥流生成)**:
|
||||||
|
```javascript
|
||||||
|
function wasm_isaac_generate(ptr, size) {
|
||||||
|
decryptor_array = new Uint8Array(size);
|
||||||
|
var wasmArray = new Uint8Array(Module.HEAPU8.buffer, ptr, size);
|
||||||
|
decryptor_array.set(wasmArray.reverse()); // ⚠️ 必须反转
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Python (XOR 解密)**:
|
||||||
|
```python
|
||||||
|
# 解密前 131072 字节
|
||||||
|
for i in range(decrypt_len):
|
||||||
|
decrypted[i] = encrypted[i] ^ keystream[i]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🌐 GitHub Pages 在线使用
|
||||||
|
|
||||||
|
### 部署到 GitHub Pages
|
||||||
|
|
||||||
|
1. Fork 本仓库或上传到你的 GitHub
|
||||||
|
2. 进入仓库设置 Settings → Pages
|
||||||
|
3. Source 选择 `main` 分支,目录选择 `/ (root)`
|
||||||
|
4. 保存后等待几分钟,访问:`https://your-username.github.io/repo-name/`
|
||||||
|
|
||||||
|
### 使用在线版本
|
||||||
|
|
||||||
|
如果项目已部署到 GitHub Pages,可以直接在线使用:
|
||||||
|
|
||||||
|
1. 访问在线页面(例如:`https://evil0ctal.github.io/WeChat-Channels-Video-File-Decryption/`)
|
||||||
|
2. 输入 `decode_key` 并点击 "🚀 Test Decryption"
|
||||||
|
3. 点击 "💾 Export Keystream" 导出密钥流
|
||||||
|
4. 下载密钥流文件后,本地运行 `decrypt_full_video.py` 解密
|
||||||
|
|
||||||
|
**优势**:无需本地启动 HTTP 服务器,直接在线生成密钥流!
|
||||||
|
|
||||||
|
## 📄 许可证
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
## 🤝 贡献
|
||||||
|
|
||||||
|
欢迎提交 Issue 和 Pull Request!
|
||||||
|
|
||||||
|
## ⚠️ 免责声明
|
||||||
|
|
||||||
|
本工具仅供学习和研究使用。请遵守相关法律法规和平台服务条款。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**最后更新**: 2025-10-15
|
||||||
|
**状态**: ✅ 测试通过
|
||||||
|
|||||||
390
decrypt_wechat_video_cli.py
Normal file
390
decrypt_wechat_video_cli.py
Normal file
@@ -0,0 +1,390 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
微信视频号解密工具 - 命令行版本
|
||||||
|
使用从浏览器导出的密钥流文件解密视频
|
||||||
|
|
||||||
|
Author: Evil0ctal
|
||||||
|
GitHub: https://github.com/Evil0ctal/WeChat-Channels-Video-File-Decryption
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def read_keystream_from_file(filename, verbose=True):
|
||||||
|
"""
|
||||||
|
从导出的文件读取密钥流
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename: 密钥流文件路径
|
||||||
|
verbose: 是否显示详细信息
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bytes: 密钥流数据,失败返回 None
|
||||||
|
"""
|
||||||
|
if verbose:
|
||||||
|
print(f"📂 读取密钥流文件: {filename}")
|
||||||
|
|
||||||
|
if not os.path.exists(filename):
|
||||||
|
if verbose:
|
||||||
|
print(f"❌ 文件不存在: {filename}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
with open(filename, 'r', encoding='utf-8') as f:
|
||||||
|
hex_string = f.read().strip()
|
||||||
|
|
||||||
|
# 移除所有空格、换行和制表符
|
||||||
|
hex_string = hex_string.replace(' ', '').replace('\n', '').replace('\r', '').replace('\t', '')
|
||||||
|
|
||||||
|
# 转换为字节
|
||||||
|
try:
|
||||||
|
keystream = bytes.fromhex(hex_string)
|
||||||
|
if verbose:
|
||||||
|
print(f"✅ 密钥流大小: {len(keystream):,} bytes ({len(keystream) / 1024:.2f} KB)")
|
||||||
|
return keystream
|
||||||
|
except ValueError as e:
|
||||||
|
if verbose:
|
||||||
|
print(f"❌ 解析密钥流失败: {e}")
|
||||||
|
print(f" 请确保文件内容为有效的十六进制字符串")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def read_keystream_from_string(hex_string, verbose=True):
|
||||||
|
"""
|
||||||
|
从十六进制字符串读取密钥流
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hex_string: 十六进制字符串
|
||||||
|
verbose: 是否显示详细信息
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bytes: 密钥流数据,失败返回 None
|
||||||
|
"""
|
||||||
|
if verbose:
|
||||||
|
print(f"📝 从字符串解析密钥流...")
|
||||||
|
|
||||||
|
# 移除所有空格、换行和制表符
|
||||||
|
hex_string = hex_string.replace(' ', '').replace('\n', '').replace('\r', '').replace('\t', '')
|
||||||
|
|
||||||
|
try:
|
||||||
|
keystream = bytes.fromhex(hex_string)
|
||||||
|
if verbose:
|
||||||
|
print(f"✅ 密钥流大小: {len(keystream):,} bytes ({len(keystream) / 1024:.2f} KB)")
|
||||||
|
return keystream
|
||||||
|
except ValueError as e:
|
||||||
|
if verbose:
|
||||||
|
print(f"❌ 解析密钥流失败: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def decrypt_video(encrypted_file, keystream, output_file, verbose=True):
|
||||||
|
"""
|
||||||
|
解密视频文件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
encrypted_file: 加密视频文件路径
|
||||||
|
keystream: 密钥流数据(bytes)
|
||||||
|
output_file: 输出文件路径
|
||||||
|
verbose: 是否显示详细信息
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 解密是否成功
|
||||||
|
"""
|
||||||
|
if verbose:
|
||||||
|
print(f"\n📁 读取加密文件: {encrypted_file}")
|
||||||
|
|
||||||
|
if not os.path.exists(encrypted_file):
|
||||||
|
if verbose:
|
||||||
|
print(f"❌ 文件不存在: {encrypted_file}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 读取加密文件
|
||||||
|
with open(encrypted_file, 'rb') as f:
|
||||||
|
encrypted_data = f.read()
|
||||||
|
|
||||||
|
file_size = len(encrypted_data)
|
||||||
|
if verbose:
|
||||||
|
print(f" 文件大小: {file_size:,} bytes ({file_size / 1024 / 1024:.2f} MB)")
|
||||||
|
|
||||||
|
# 确定需要解密的长度
|
||||||
|
decrypt_len = min(len(keystream), len(encrypted_data))
|
||||||
|
if verbose:
|
||||||
|
print(f"\n🔓 开始解密...")
|
||||||
|
print(f" 解密长度: {decrypt_len:,} bytes ({decrypt_len / 1024:.2f} KB)")
|
||||||
|
|
||||||
|
# XOR 解密前 decrypt_len 字节
|
||||||
|
if verbose:
|
||||||
|
print(f" 进行 XOR 运算...")
|
||||||
|
|
||||||
|
decrypted_chunk = bytes(a ^ b for a, b in zip(encrypted_data[:decrypt_len], keystream))
|
||||||
|
|
||||||
|
# 拼接未加密的部分
|
||||||
|
decrypted_full = decrypted_chunk + encrypted_data[decrypt_len:]
|
||||||
|
|
||||||
|
# 验证解密
|
||||||
|
if verbose:
|
||||||
|
print(f"\n🔍 验证解密结果...")
|
||||||
|
print(f" 前 32 字节: {' '.join(f'{b:02x}' for b in decrypted_full[:32])}")
|
||||||
|
|
||||||
|
# 检查 MP4 文件签名
|
||||||
|
is_valid_mp4 = False
|
||||||
|
if b'ftyp' in decrypted_full[:32]:
|
||||||
|
ftyp_offset = decrypted_full[:32].find(b'ftyp')
|
||||||
|
is_valid_mp4 = True
|
||||||
|
if verbose:
|
||||||
|
print(f" ✅✅✅ 找到 MP4 签名 'ftyp' @ 偏移 {ftyp_offset}")
|
||||||
|
print(f" 🎬 这是一个有效的 MP4 文件!")
|
||||||
|
else:
|
||||||
|
if verbose:
|
||||||
|
print(f" ⚠️ 未找到 'ftyp' 签名")
|
||||||
|
print(f" 可能需要检查密钥流是否正确")
|
||||||
|
|
||||||
|
# 保存解密后的文件
|
||||||
|
if verbose:
|
||||||
|
print(f"\n💾 保存解密文件: {output_file}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(output_file, 'wb') as f:
|
||||||
|
f.write(decrypted_full)
|
||||||
|
|
||||||
|
saved_size = os.path.getsize(output_file)
|
||||||
|
if verbose:
|
||||||
|
print(f" ✅ 保存成功!")
|
||||||
|
print(f" 文件大小: {saved_size:,} bytes ({saved_size / 1024 / 1024:.2f} MB)")
|
||||||
|
|
||||||
|
return is_valid_mp4
|
||||||
|
except Exception as e:
|
||||||
|
if verbose:
|
||||||
|
print(f" ❌ 保存失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def interactive_mode():
|
||||||
|
"""交互式模式"""
|
||||||
|
print("=" * 70)
|
||||||
|
print("🎬 微信视频号解密工具 - 交互模式")
|
||||||
|
print("=" * 70)
|
||||||
|
print()
|
||||||
|
|
||||||
|
# 密钥流文件
|
||||||
|
keystream_file = "keystream_131072_bytes.txt"
|
||||||
|
keystream = None
|
||||||
|
|
||||||
|
if not os.path.exists(keystream_file):
|
||||||
|
print(f"⚠️ 未找到密钥流文件: {keystream_file}")
|
||||||
|
print()
|
||||||
|
print("请选择输入方式:")
|
||||||
|
print("1. 输入密钥流文件路径")
|
||||||
|
print("2. 直接粘贴十六进制密钥流")
|
||||||
|
print("3. 退出")
|
||||||
|
choice = input("\n请选择 (1/2/3): ").strip()
|
||||||
|
|
||||||
|
if choice == "1":
|
||||||
|
keystream_file = input("请输入密钥流文件路径: ").strip()
|
||||||
|
if os.path.exists(keystream_file):
|
||||||
|
keystream = read_keystream_from_file(keystream_file)
|
||||||
|
else:
|
||||||
|
print(f"❌ 文件不存在: {keystream_file}")
|
||||||
|
return
|
||||||
|
elif choice == "2":
|
||||||
|
hex_string = input("请粘贴十六进制密钥流: ").strip()
|
||||||
|
keystream = read_keystream_from_string(hex_string)
|
||||||
|
if keystream:
|
||||||
|
# 保存到文件
|
||||||
|
with open(keystream_file, 'w') as f:
|
||||||
|
f.write(hex_string)
|
||||||
|
print(f"✅ 已将密钥流保存到: {keystream_file}")
|
||||||
|
else:
|
||||||
|
print("❌ 用户取消操作")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
keystream = read_keystream_from_file(keystream_file)
|
||||||
|
|
||||||
|
if not keystream:
|
||||||
|
print("❌ 无法读取密钥流")
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(keystream) != 131072:
|
||||||
|
print(f"⚠️ 警告: 密钥流大小不是 131072 bytes (实际: {len(keystream):,} bytes)")
|
||||||
|
confirm = input("是否继续? (y/n): ").strip().lower()
|
||||||
|
if confirm != 'y':
|
||||||
|
return
|
||||||
|
|
||||||
|
# 加密文件
|
||||||
|
encrypted_file = "wx_encrypted.mp4"
|
||||||
|
if not os.path.exists(encrypted_file):
|
||||||
|
encrypted_file = input(f"\n请输入加密视频文件路径: ").strip()
|
||||||
|
if not os.path.exists(encrypted_file):
|
||||||
|
print(f"❌ 文件不存在: {encrypted_file}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 输出文件
|
||||||
|
default_output = "wx_decrypted.mp4"
|
||||||
|
user_input = input(f"\n请输入输出文件名 (默认: {default_output}): ").strip()
|
||||||
|
if user_input:
|
||||||
|
output_file = user_input if user_input.endswith('.mp4') else f"{user_input}.mp4"
|
||||||
|
else:
|
||||||
|
output_file = default_output
|
||||||
|
|
||||||
|
# 解密
|
||||||
|
success = decrypt_video(encrypted_file, keystream, output_file)
|
||||||
|
|
||||||
|
# 结果
|
||||||
|
print()
|
||||||
|
print("=" * 70)
|
||||||
|
if success:
|
||||||
|
print("🎉 解密完成!")
|
||||||
|
print("=" * 70)
|
||||||
|
print()
|
||||||
|
print(f"📂 解密文件: {output_file}")
|
||||||
|
print(f"📍 完整路径: {os.path.abspath(output_file)}")
|
||||||
|
print()
|
||||||
|
print("💡 可以使用以下命令播放视频:")
|
||||||
|
print(f" open {output_file}")
|
||||||
|
print(f" 或")
|
||||||
|
print(f" mpv {output_file}")
|
||||||
|
else:
|
||||||
|
print("⚠️ 解密完成,但可能存在问题")
|
||||||
|
print("=" * 70)
|
||||||
|
print()
|
||||||
|
print("请检查:")
|
||||||
|
print("1. 密钥流是否正确")
|
||||||
|
print("2. 加密文件是否完整")
|
||||||
|
print("3. decode_key 是否匹配此视频")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def cli_mode(args):
|
||||||
|
"""命令行模式"""
|
||||||
|
print("=" * 70)
|
||||||
|
print("🎬 微信视频号解密工具")
|
||||||
|
print("=" * 70)
|
||||||
|
print()
|
||||||
|
|
||||||
|
# 读取密钥流
|
||||||
|
keystream = None
|
||||||
|
if args.keystream_file:
|
||||||
|
keystream = read_keystream_from_file(args.keystream_file, verbose=not args.quiet)
|
||||||
|
elif args.keystream_hex:
|
||||||
|
keystream = read_keystream_from_string(args.keystream_hex, verbose=not args.quiet)
|
||||||
|
|
||||||
|
if not keystream:
|
||||||
|
print("❌ 无法读取密钥流")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if len(keystream) != 131072 and not args.quiet:
|
||||||
|
print(f"⚠️ 警告: 密钥流大小不是 131072 bytes (实际: {len(keystream):,} bytes)")
|
||||||
|
|
||||||
|
# 解密文件
|
||||||
|
success = decrypt_video(
|
||||||
|
args.input,
|
||||||
|
keystream,
|
||||||
|
args.output,
|
||||||
|
verbose=not args.quiet
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
if not args.quiet:
|
||||||
|
print()
|
||||||
|
print("=" * 70)
|
||||||
|
print("🎉 解密完成!")
|
||||||
|
print("=" * 70)
|
||||||
|
print()
|
||||||
|
print(f"📂 解密文件: {args.output}")
|
||||||
|
print(f"📍 完整路径: {os.path.abspath(args.output)}")
|
||||||
|
print()
|
||||||
|
else:
|
||||||
|
if not args.quiet:
|
||||||
|
print()
|
||||||
|
print("⚠️ 解密完成,但可能存在问题")
|
||||||
|
print("请检查密钥流和加密文件是否正确")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="微信视频号解密工具 - 使用 Isaac64 密钥流解密视频",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog="""
|
||||||
|
示例:
|
||||||
|
# 交互模式(推荐新手使用)
|
||||||
|
%(prog)s
|
||||||
|
|
||||||
|
# 使用密钥流文件解密
|
||||||
|
%(prog)s -i wx_encrypted.mp4 -k keystream_131072_bytes.txt -o wx_decrypted.mp4
|
||||||
|
|
||||||
|
# 使用十六进制字符串解密
|
||||||
|
%(prog)s -i encrypted.mp4 -H "0a1b2c3d..." -o decrypted.mp4
|
||||||
|
|
||||||
|
# 静默模式
|
||||||
|
%(prog)s -i encrypted.mp4 -k keystream.txt -o decrypted.mp4 -q
|
||||||
|
|
||||||
|
项目地址: https://github.com/Evil0ctal/WeChat-Channels-Video-File-Decryption
|
||||||
|
作者: Evil0ctal
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'-i', '--input',
|
||||||
|
help='加密视频文件路径'
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'-o', '--output',
|
||||||
|
help='输出文件路径(默认: wx_decrypted.mp4)'
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'-k', '--keystream-file',
|
||||||
|
help='密钥流文件路径(十六进制文本文件)'
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'-H', '--keystream-hex',
|
||||||
|
help='直接提供十六进制密钥流字符串'
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'-q', '--quiet',
|
||||||
|
action='store_true',
|
||||||
|
help='静默模式,只显示错误信息'
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--version',
|
||||||
|
action='version',
|
||||||
|
version='%(prog)s 1.0.0'
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# 如果没有提供任何参数,进入交互模式
|
||||||
|
if not args.input and not args.keystream_file and not args.keystream_hex:
|
||||||
|
interactive_mode()
|
||||||
|
else:
|
||||||
|
# 验证必要参数
|
||||||
|
if not args.input:
|
||||||
|
parser.error("请提供加密视频文件路径 (-i/--input)")
|
||||||
|
|
||||||
|
if not args.keystream_file and not args.keystream_hex:
|
||||||
|
parser.error("请提供密钥流文件 (-k/--keystream-file) 或十六进制字符串 (-H/--keystream-hex)")
|
||||||
|
|
||||||
|
if not args.output:
|
||||||
|
args.output = "wx_decrypted.mp4"
|
||||||
|
if not args.quiet:
|
||||||
|
print(f"ℹ️ 未指定输出文件,使用默认: {args.output}")
|
||||||
|
|
||||||
|
cli_mode(args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\n❌ 用户中断操作")
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n❌ 发生错误: {e}")
|
||||||
|
sys.exit(1)
|
||||||
517
decrypt_wechat_video_gui.py
Normal file
517
decrypt_wechat_video_gui.py
Normal file
@@ -0,0 +1,517 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
微信视频号解密工具 - 图形界面版本
|
||||||
|
使用 tkinter 提供友好的图形界面
|
||||||
|
|
||||||
|
Author: Evil0ctal
|
||||||
|
GitHub: https://github.com/Evil0ctal/WeChat-Channels-Video-File-Decryption
|
||||||
|
"""
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk, filedialog, messagebox, scrolledtext
|
||||||
|
import threading
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# 导入 CLI 模块的函数
|
||||||
|
from decrypt_wechat_video_cli import (
|
||||||
|
read_keystream_from_file,
|
||||||
|
read_keystream_from_string,
|
||||||
|
decrypt_video
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DecryptionGUI:
|
||||||
|
"""解密工具 GUI 主类"""
|
||||||
|
|
||||||
|
def __init__(self, root):
|
||||||
|
self.root = root
|
||||||
|
self.root.title("微信视频号解密工具")
|
||||||
|
self.root.geometry("800x700")
|
||||||
|
self.root.resizable(True, True)
|
||||||
|
|
||||||
|
# 设置应用图标(如果有的话)
|
||||||
|
try:
|
||||||
|
# 可以添加图标
|
||||||
|
pass
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 变量
|
||||||
|
self.keystream_file_var = tk.StringVar(value="keystream_131072_bytes.txt")
|
||||||
|
self.encrypted_file_var = tk.StringVar(value="wx_encrypted.mp4")
|
||||||
|
self.output_file_var = tk.StringVar(value="wx_decrypted.mp4")
|
||||||
|
self.keystream_data = None
|
||||||
|
self.is_decrypting = False
|
||||||
|
|
||||||
|
# 创建 UI
|
||||||
|
self.create_widgets()
|
||||||
|
|
||||||
|
# 检查默认密钥流文件
|
||||||
|
self.check_default_keystream()
|
||||||
|
|
||||||
|
def create_widgets(self):
|
||||||
|
"""创建界面组件"""
|
||||||
|
# 主容器
|
||||||
|
main_frame = ttk.Frame(self.root, padding="10")
|
||||||
|
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||||
|
|
||||||
|
# 配置网格权重
|
||||||
|
self.root.columnconfigure(0, weight=1)
|
||||||
|
self.root.rowconfigure(0, weight=1)
|
||||||
|
main_frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
# 标题
|
||||||
|
title_label = ttk.Label(
|
||||||
|
main_frame,
|
||||||
|
text="🎬 微信视频号解密工具",
|
||||||
|
font=("Arial", 18, "bold")
|
||||||
|
)
|
||||||
|
title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20))
|
||||||
|
|
||||||
|
# 第一部分:密钥流
|
||||||
|
row = 1
|
||||||
|
ttk.Label(main_frame, text="密钥流文件:", font=("Arial", 11)).grid(
|
||||||
|
row=row, column=0, sticky=tk.W, pady=5
|
||||||
|
)
|
||||||
|
ttk.Entry(main_frame, textvariable=self.keystream_file_var, width=50).grid(
|
||||||
|
row=row, column=1, sticky=(tk.W, tk.E), pady=5, padx=5
|
||||||
|
)
|
||||||
|
ttk.Button(main_frame, text="选择文件", command=self.browse_keystream).grid(
|
||||||
|
row=row, column=2, pady=5
|
||||||
|
)
|
||||||
|
|
||||||
|
# 密钥流状态
|
||||||
|
row += 1
|
||||||
|
self.keystream_status_label = ttk.Label(
|
||||||
|
main_frame,
|
||||||
|
text="等待加载密钥流...",
|
||||||
|
foreground="gray"
|
||||||
|
)
|
||||||
|
self.keystream_status_label.grid(
|
||||||
|
row=row, column=1, sticky=tk.W, pady=(0, 10)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 或者直接输入密钥流
|
||||||
|
row += 1
|
||||||
|
ttk.Label(main_frame, text="或粘贴密钥流:", font=("Arial", 11)).grid(
|
||||||
|
row=row, column=0, sticky=tk.W, pady=5
|
||||||
|
)
|
||||||
|
self.hex_input = scrolledtext.ScrolledText(
|
||||||
|
main_frame,
|
||||||
|
height=3,
|
||||||
|
width=50,
|
||||||
|
wrap=tk.WORD,
|
||||||
|
font=("Courier", 9)
|
||||||
|
)
|
||||||
|
self.hex_input.grid(row=row, column=1, columnspan=2, sticky=(tk.W, tk.E), pady=5, padx=5)
|
||||||
|
|
||||||
|
row += 1
|
||||||
|
ttk.Button(
|
||||||
|
main_frame,
|
||||||
|
text="从文本加载密钥流",
|
||||||
|
command=self.load_keystream_from_text
|
||||||
|
).grid(row=row, column=1, sticky=tk.W, pady=(0, 15))
|
||||||
|
|
||||||
|
# 分隔线
|
||||||
|
row += 1
|
||||||
|
ttk.Separator(main_frame, orient='horizontal').grid(
|
||||||
|
row=row, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10
|
||||||
|
)
|
||||||
|
|
||||||
|
# 第二部分:加密文件
|
||||||
|
row += 1
|
||||||
|
ttk.Label(main_frame, text="加密视频文件:", font=("Arial", 11)).grid(
|
||||||
|
row=row, column=0, sticky=tk.W, pady=5
|
||||||
|
)
|
||||||
|
ttk.Entry(main_frame, textvariable=self.encrypted_file_var, width=50).grid(
|
||||||
|
row=row, column=1, sticky=(tk.W, tk.E), pady=5, padx=5
|
||||||
|
)
|
||||||
|
ttk.Button(main_frame, text="选择文件", command=self.browse_encrypted).grid(
|
||||||
|
row=row, column=2, pady=5
|
||||||
|
)
|
||||||
|
|
||||||
|
# 第三部分:输出文件
|
||||||
|
row += 1
|
||||||
|
ttk.Label(main_frame, text="输出文件名:", font=("Arial", 11)).grid(
|
||||||
|
row=row, column=0, sticky=tk.W, pady=5
|
||||||
|
)
|
||||||
|
ttk.Entry(main_frame, textvariable=self.output_file_var, width=50).grid(
|
||||||
|
row=row, column=1, sticky=(tk.W, tk.E), pady=5, padx=5
|
||||||
|
)
|
||||||
|
ttk.Button(main_frame, text="另存为", command=self.browse_output).grid(
|
||||||
|
row=row, column=2, pady=5
|
||||||
|
)
|
||||||
|
|
||||||
|
# 分隔线
|
||||||
|
row += 1
|
||||||
|
ttk.Separator(main_frame, orient='horizontal').grid(
|
||||||
|
row=row, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10
|
||||||
|
)
|
||||||
|
|
||||||
|
# 解密按钮
|
||||||
|
row += 1
|
||||||
|
button_frame = ttk.Frame(main_frame)
|
||||||
|
button_frame.grid(row=row, column=0, columnspan=3, pady=10)
|
||||||
|
|
||||||
|
self.decrypt_button = ttk.Button(
|
||||||
|
button_frame,
|
||||||
|
text="🚀 开始解密",
|
||||||
|
command=self.start_decryption,
|
||||||
|
width=20
|
||||||
|
)
|
||||||
|
self.decrypt_button.grid(row=0, column=0, padx=5)
|
||||||
|
|
||||||
|
self.open_folder_button = ttk.Button(
|
||||||
|
button_frame,
|
||||||
|
text="📂 打开文件夹",
|
||||||
|
command=self.open_output_folder,
|
||||||
|
state=tk.DISABLED,
|
||||||
|
width=20
|
||||||
|
)
|
||||||
|
self.open_folder_button.grid(row=0, column=1, padx=5)
|
||||||
|
|
||||||
|
ttk.Button(
|
||||||
|
button_frame,
|
||||||
|
text="❓ 帮助",
|
||||||
|
command=self.show_help,
|
||||||
|
width=15
|
||||||
|
).grid(row=0, column=2, padx=5)
|
||||||
|
|
||||||
|
# 日志输出区域
|
||||||
|
row += 1
|
||||||
|
ttk.Label(main_frame, text="操作日志:", font=("Arial", 11)).grid(
|
||||||
|
row=row, column=0, sticky=tk.W, pady=(10, 5)
|
||||||
|
)
|
||||||
|
|
||||||
|
row += 1
|
||||||
|
self.log_text = scrolledtext.ScrolledText(
|
||||||
|
main_frame,
|
||||||
|
height=15,
|
||||||
|
width=80,
|
||||||
|
wrap=tk.WORD,
|
||||||
|
font=("Courier", 9),
|
||||||
|
state=tk.DISABLED
|
||||||
|
)
|
||||||
|
self.log_text.grid(
|
||||||
|
row=row, column=0, columnspan=3,
|
||||||
|
sticky=(tk.W, tk.E, tk.N, tk.S),
|
||||||
|
pady=5
|
||||||
|
)
|
||||||
|
|
||||||
|
# 配置日志文本标签
|
||||||
|
self.log_text.tag_config("success", foreground="green")
|
||||||
|
self.log_text.tag_config("error", foreground="red")
|
||||||
|
self.log_text.tag_config("warning", foreground="orange")
|
||||||
|
self.log_text.tag_config("info", foreground="blue")
|
||||||
|
|
||||||
|
# 状态栏
|
||||||
|
row += 1
|
||||||
|
self.status_label = ttk.Label(
|
||||||
|
main_frame,
|
||||||
|
text="就绪",
|
||||||
|
relief=tk.SUNKEN,
|
||||||
|
anchor=tk.W
|
||||||
|
)
|
||||||
|
self.status_label.grid(
|
||||||
|
row=row, column=0, columnspan=3,
|
||||||
|
sticky=(tk.W, tk.E), pady=(10, 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 配置行列权重使文本框可扩展
|
||||||
|
main_frame.rowconfigure(row - 1, weight=1)
|
||||||
|
|
||||||
|
# 欢迎信息
|
||||||
|
self.log("欢迎使用微信视频号解密工具!", "info")
|
||||||
|
self.log("作者: Evil0ctal", "info")
|
||||||
|
self.log("项目地址: https://github.com/Evil0ctal/WeChat-Channels-Video-File-Decryption\n", "info")
|
||||||
|
|
||||||
|
def log(self, message, tag=None):
|
||||||
|
"""添加日志"""
|
||||||
|
self.log_text.config(state=tk.NORMAL)
|
||||||
|
if tag:
|
||||||
|
self.log_text.insert(tk.END, message + "\n", tag)
|
||||||
|
else:
|
||||||
|
self.log_text.insert(tk.END, message + "\n")
|
||||||
|
self.log_text.see(tk.END)
|
||||||
|
self.log_text.config(state=tk.DISABLED)
|
||||||
|
self.root.update_idletasks()
|
||||||
|
|
||||||
|
def update_status(self, message):
|
||||||
|
"""更新状态栏"""
|
||||||
|
self.status_label.config(text=message)
|
||||||
|
self.root.update_idletasks()
|
||||||
|
|
||||||
|
def check_default_keystream(self):
|
||||||
|
"""检查默认密钥流文件"""
|
||||||
|
keystream_file = self.keystream_file_var.get()
|
||||||
|
if os.path.exists(keystream_file):
|
||||||
|
self.keystream_data = read_keystream_from_file(keystream_file, verbose=False)
|
||||||
|
if self.keystream_data:
|
||||||
|
size_kb = len(self.keystream_data) / 1024
|
||||||
|
self.keystream_status_label.config(
|
||||||
|
text=f"✅ 已加载密钥流 ({size_kb:.2f} KB)",
|
||||||
|
foreground="green"
|
||||||
|
)
|
||||||
|
self.log(f"✅ 自动加载密钥流文件: {keystream_file}", "success")
|
||||||
|
else:
|
||||||
|
self.keystream_status_label.config(
|
||||||
|
text="❌ 密钥流文件格式错误",
|
||||||
|
foreground="red"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.keystream_status_label.config(
|
||||||
|
text="⚠️ 未找到默认密钥流文件",
|
||||||
|
foreground="orange"
|
||||||
|
)
|
||||||
|
|
||||||
|
def browse_keystream(self):
|
||||||
|
"""选择密钥流文件"""
|
||||||
|
filename = filedialog.askopenfilename(
|
||||||
|
title="选择密钥流文件",
|
||||||
|
filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
|
||||||
|
)
|
||||||
|
if filename:
|
||||||
|
self.keystream_file_var.set(filename)
|
||||||
|
self.keystream_data = read_keystream_from_file(filename, verbose=False)
|
||||||
|
if self.keystream_data:
|
||||||
|
size_kb = len(self.keystream_data) / 1024
|
||||||
|
self.keystream_status_label.config(
|
||||||
|
text=f"✅ 已加载密钥流 ({size_kb:.2f} KB)",
|
||||||
|
foreground="green"
|
||||||
|
)
|
||||||
|
self.log(f"✅ 加载密钥流文件: {filename}", "success")
|
||||||
|
else:
|
||||||
|
self.keystream_status_label.config(
|
||||||
|
text="❌ 密钥流文件格式错误",
|
||||||
|
foreground="red"
|
||||||
|
)
|
||||||
|
self.log(f"❌ 加载密钥流失败: {filename}", "error")
|
||||||
|
|
||||||
|
def load_keystream_from_text(self):
|
||||||
|
"""从文本框加载密钥流"""
|
||||||
|
hex_string = self.hex_input.get("1.0", tk.END).strip()
|
||||||
|
if not hex_string:
|
||||||
|
messagebox.showwarning("警告", "请粘贴十六进制密钥流!")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.keystream_data = read_keystream_from_string(hex_string, verbose=False)
|
||||||
|
if self.keystream_data:
|
||||||
|
size_kb = len(self.keystream_data) / 1024
|
||||||
|
self.keystream_status_label.config(
|
||||||
|
text=f"✅ 已加载密钥流 ({size_kb:.2f} KB)",
|
||||||
|
foreground="green"
|
||||||
|
)
|
||||||
|
self.log(f"✅ 从文本加载密钥流成功 ({size_kb:.2f} KB)", "success")
|
||||||
|
|
||||||
|
# 保存到文件
|
||||||
|
save_file = "keystream_131072_bytes.txt"
|
||||||
|
with open(save_file, 'w') as f:
|
||||||
|
f.write(hex_string)
|
||||||
|
self.log(f"✅ 密钥流已保存到: {save_file}", "info")
|
||||||
|
else:
|
||||||
|
self.keystream_status_label.config(
|
||||||
|
text="❌ 密钥流格式错误",
|
||||||
|
foreground="red"
|
||||||
|
)
|
||||||
|
self.log("❌ 密钥流格式错误,请检查输入", "error")
|
||||||
|
|
||||||
|
def browse_encrypted(self):
|
||||||
|
"""选择加密文件"""
|
||||||
|
filename = filedialog.askopenfilename(
|
||||||
|
title="选择加密视频文件",
|
||||||
|
filetypes=[("MP4 视频", "*.mp4"), ("所有文件", "*.*")]
|
||||||
|
)
|
||||||
|
if filename:
|
||||||
|
self.encrypted_file_var.set(filename)
|
||||||
|
self.log(f"✅ 选择加密文件: {filename}", "info")
|
||||||
|
|
||||||
|
def browse_output(self):
|
||||||
|
"""选择输出文件"""
|
||||||
|
filename = filedialog.asksaveasfilename(
|
||||||
|
title="保存解密视频",
|
||||||
|
defaultextension=".mp4",
|
||||||
|
filetypes=[("MP4 视频", "*.mp4"), ("所有文件", "*.*")]
|
||||||
|
)
|
||||||
|
if filename:
|
||||||
|
self.output_file_var.set(filename)
|
||||||
|
self.log(f"✅ 输出文件: {filename}", "info")
|
||||||
|
|
||||||
|
def start_decryption(self):
|
||||||
|
"""开始解密(在线程中执行)"""
|
||||||
|
if self.is_decrypting:
|
||||||
|
messagebox.showwarning("警告", "正在解密中,请等待...")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 验证输入
|
||||||
|
if not self.keystream_data:
|
||||||
|
messagebox.showerror("错误", "请先加载密钥流文件或粘贴密钥流!")
|
||||||
|
return
|
||||||
|
|
||||||
|
encrypted_file = self.encrypted_file_var.get()
|
||||||
|
if not os.path.exists(encrypted_file):
|
||||||
|
messagebox.showerror("错误", f"加密文件不存在:\n{encrypted_file}")
|
||||||
|
return
|
||||||
|
|
||||||
|
output_file = self.output_file_var.get()
|
||||||
|
if not output_file:
|
||||||
|
messagebox.showerror("错误", "请指定输出文件名!")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 禁用按钮
|
||||||
|
self.decrypt_button.config(state=tk.DISABLED)
|
||||||
|
self.is_decrypting = True
|
||||||
|
|
||||||
|
# 在新线程中执行解密
|
||||||
|
thread = threading.Thread(target=self.decrypt_worker, args=(encrypted_file, output_file))
|
||||||
|
thread.daemon = True
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def decrypt_worker(self, encrypted_file, output_file):
|
||||||
|
"""解密工作线程"""
|
||||||
|
try:
|
||||||
|
self.log("\n" + "=" * 70, "info")
|
||||||
|
self.log("🚀 开始解密...", "info")
|
||||||
|
self.log("=" * 70 + "\n", "info")
|
||||||
|
self.update_status("正在解密...")
|
||||||
|
|
||||||
|
# 验证密钥流大小
|
||||||
|
keystream_size = len(self.keystream_data)
|
||||||
|
self.log(f"📊 密钥流大小: {keystream_size:,} bytes ({keystream_size / 1024:.2f} KB)", "info")
|
||||||
|
|
||||||
|
if keystream_size != 131072:
|
||||||
|
self.log(f"⚠️ 警告: 密钥流大小不是标准的 131072 bytes", "warning")
|
||||||
|
|
||||||
|
# 文件信息
|
||||||
|
file_size = os.path.getsize(encrypted_file)
|
||||||
|
self.log(f"📁 加密文件: {encrypted_file}", "info")
|
||||||
|
self.log(f"📊 文件大小: {file_size:,} bytes ({file_size / 1024 / 1024:.2f} MB)", "info")
|
||||||
|
self.log(f"💾 输出文件: {output_file}\n", "info")
|
||||||
|
|
||||||
|
# 调用 CLI 模块的解密函数
|
||||||
|
self.log("🔓 开始 XOR 解密...", "info")
|
||||||
|
success = decrypt_video(
|
||||||
|
encrypted_file,
|
||||||
|
self.keystream_data,
|
||||||
|
output_file,
|
||||||
|
verbose=False # 我们自己处理日志输出
|
||||||
|
)
|
||||||
|
|
||||||
|
# 验证结果
|
||||||
|
if success:
|
||||||
|
self.log("\n" + "=" * 70, "success")
|
||||||
|
self.log("🎉 解密成功!", "success")
|
||||||
|
self.log("=" * 70 + "\n", "success")
|
||||||
|
|
||||||
|
output_size = os.path.getsize(output_file)
|
||||||
|
self.log(f"✅ 解密文件: {output_file}", "success")
|
||||||
|
self.log(f"📊 文件大小: {output_size:,} bytes ({output_size / 1024 / 1024:.2f} MB)", "success")
|
||||||
|
self.log(f"📍 完整路径: {os.path.abspath(output_file)}\n", "info")
|
||||||
|
|
||||||
|
self.update_status("解密完成!")
|
||||||
|
|
||||||
|
# 启用打开文件夹按钮
|
||||||
|
self.open_folder_button.config(state=tk.NORMAL)
|
||||||
|
|
||||||
|
# 显示成功对话框
|
||||||
|
result = messagebox.askyesno(
|
||||||
|
"解密成功",
|
||||||
|
f"视频解密完成!\n\n文件: {output_file}\n\n是否打开文件所在文件夹?"
|
||||||
|
)
|
||||||
|
if result:
|
||||||
|
self.open_output_folder()
|
||||||
|
else:
|
||||||
|
self.log("\n" + "=" * 70, "warning")
|
||||||
|
self.log("⚠️ 解密完成,但可能存在问题", "warning")
|
||||||
|
self.log("=" * 70 + "\n", "warning")
|
||||||
|
self.log("请检查:", "warning")
|
||||||
|
self.log("1. 密钥流是否正确", "warning")
|
||||||
|
self.log("2. decode_key 是否匹配此视频", "warning")
|
||||||
|
self.log("3. 加密文件是否完整\n", "warning")
|
||||||
|
|
||||||
|
self.update_status("解密完成(可能有问题)")
|
||||||
|
|
||||||
|
messagebox.showwarning(
|
||||||
|
"警告",
|
||||||
|
"解密完成,但未检测到有效的 MP4 签名。\n请检查密钥流和文件是否正确。"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"\n❌ 解密失败: {e}", "error")
|
||||||
|
self.update_status("解密失败")
|
||||||
|
messagebox.showerror("错误", f"解密失败:\n{e}")
|
||||||
|
finally:
|
||||||
|
# 恢复按钮
|
||||||
|
self.decrypt_button.config(state=tk.NORMAL)
|
||||||
|
self.is_decrypting = False
|
||||||
|
|
||||||
|
def open_output_folder(self):
|
||||||
|
"""打开输出文件所在文件夹"""
|
||||||
|
output_file = self.output_file_var.get()
|
||||||
|
if os.path.exists(output_file):
|
||||||
|
folder = os.path.dirname(os.path.abspath(output_file))
|
||||||
|
if sys.platform == "darwin": # macOS
|
||||||
|
os.system(f'open "{folder}"')
|
||||||
|
elif sys.platform == "win32": # Windows
|
||||||
|
os.system(f'explorer "{folder}"')
|
||||||
|
else: # Linux
|
||||||
|
os.system(f'xdg-open "{folder}"')
|
||||||
|
else:
|
||||||
|
messagebox.showwarning("警告", "输出文件不存在!")
|
||||||
|
|
||||||
|
def show_help(self):
|
||||||
|
"""显示帮助信息"""
|
||||||
|
help_text = """
|
||||||
|
微信视频号解密工具 - 使用说明
|
||||||
|
|
||||||
|
📝 使用步骤:
|
||||||
|
|
||||||
|
1️⃣ 获取密钥流
|
||||||
|
方式一:使用浏览器生成
|
||||||
|
- 访问项目 GitHub Pages 或启动本地服务器
|
||||||
|
- 在页面中输入 decode_key
|
||||||
|
- 点击"生成密钥流"
|
||||||
|
- 点击"导出密钥流"下载文件
|
||||||
|
|
||||||
|
方式二:直接粘贴
|
||||||
|
- 将密钥流十六进制字符串粘贴到文本框
|
||||||
|
- 点击"从文本加载密钥流"
|
||||||
|
|
||||||
|
2️⃣ 选择加密文件
|
||||||
|
- 点击"选择文件"选择加密的 MP4 视频
|
||||||
|
|
||||||
|
3️⃣ 指定输出文件
|
||||||
|
- 输入输出文件名(默认:wx_decrypted.mp4)
|
||||||
|
|
||||||
|
4️⃣ 开始解密
|
||||||
|
- 点击"开始解密"按钮
|
||||||
|
- 等待解密完成
|
||||||
|
|
||||||
|
🔧 技术原理:
|
||||||
|
- 加密算法:Isaac64 PRNG
|
||||||
|
- 加密范围:视频前 128 KB
|
||||||
|
- 解密方式:XOR 运算
|
||||||
|
- 关键步骤:密钥流必须 reverse()
|
||||||
|
|
||||||
|
📌 注意事项:
|
||||||
|
- 每个视频有唯一的 decode_key
|
||||||
|
- 密钥流大小应为 131,072 bytes (128 KB)
|
||||||
|
- 解密后文件头应包含 'ftyp' 签名
|
||||||
|
|
||||||
|
🔗 项目地址:
|
||||||
|
https://github.com/Evil0ctal/WeChat-Channels-Video-File-Decryption
|
||||||
|
|
||||||
|
👨💻 作者:Evil0ctal
|
||||||
|
"""
|
||||||
|
messagebox.showinfo("使用帮助", help_text)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
root = tk.Tk()
|
||||||
|
app = DecryptionGUI(root)
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
601
index.html
Normal file
601
index.html
Normal file
@@ -0,0 +1,601 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="description" content="微信视频号解密工具 - 基于 Isaac64 WASM 的密钥流生成器">
|
||||||
|
<title>微信视频号解密工具 - 密钥流生成器</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-size: 28px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header p {
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section h2 {
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #667eea;
|
||||||
|
border-bottom: 2px solid #f0f0f0;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group label {
|
||||||
|
display: block;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
border: 2px solid #e0e0e0;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
font-family: 'Monaco', 'Menlo', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 12px 24px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
background: #28a745;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: #6c757d;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#output {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 20px;
|
||||||
|
min-height: 200px;
|
||||||
|
max-height: 600px;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
#output:empty::before {
|
||||||
|
content: '等待操作...';
|
||||||
|
color: #999;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
color: #28a745;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: #dc3545;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
color: #0066cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
color: #ff9800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tech-details {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-left: 4px solid #667eea;
|
||||||
|
padding: 15px 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tech-details h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #667eea;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tech-details ul {
|
||||||
|
margin: 10px 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tech-details li {
|
||||||
|
margin: 5px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tech-details code {
|
||||||
|
background: #e9ecef;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: 'Monaco', 'Menlo', monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sponsor {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sponsor h3 {
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sponsor p {
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 8px 0;
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sponsor a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
border-bottom: 2px solid rgba(255, 255, 255, 0.5);
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sponsor a:hover {
|
||||||
|
border-bottom-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-info {
|
||||||
|
background: white;
|
||||||
|
border: 2px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 15px 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-info .avatar {
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-info .details h4 {
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-info .details p {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #666;
|
||||||
|
margin: 3px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-info .details a {
|
||||||
|
color: #667eea;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-info .details a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #666;
|
||||||
|
border-top: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer a {
|
||||||
|
color: #667eea;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.container {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>🔐 微信视频号解密工具</h1>
|
||||||
|
<p>基于 Isaac64 WASM 的密钥流生成器</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<div class="section">
|
||||||
|
<h2>配置</h2>
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="decodeKey">Decode Key(解密密钥)</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="decodeKey"
|
||||||
|
placeholder="请输入从 API 响应中获取的 decode_key"
|
||||||
|
value=""
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<button class="btn-primary" onclick="generateKeystream()">
|
||||||
|
<span>🚀</span>
|
||||||
|
<span>生成密钥流</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn-success" onclick="exportKeystream()" id="exportBtn" disabled>
|
||||||
|
<span>💾</span>
|
||||||
|
<span>导出密钥流</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn-secondary" onclick="clearOutput()">
|
||||||
|
<span>🗑️</span>
|
||||||
|
<span>清空日志</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>操作日志</h2>
|
||||||
|
<div id="output"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 技术细节 -->
|
||||||
|
<div class="section">
|
||||||
|
<div class="tech-details">
|
||||||
|
<h3>🔧 技术细节</h3>
|
||||||
|
<ul>
|
||||||
|
<li><strong>加密算法</strong>:Isaac64 PRNG(密码学安全的伪随机数生成器)</li>
|
||||||
|
<li><strong>实现方式</strong>:使用微信官方 WASM 模块(<code>wasm_video_decode.wasm</code>)</li>
|
||||||
|
<li><strong>加密范围</strong>:仅加密视频前 131,072 bytes(128 KB)</li>
|
||||||
|
<li><strong>解密方式</strong>:XOR 运算(<code>decrypted = encrypted ^ keystream</code>)</li>
|
||||||
|
<li><strong>关键步骤</strong>:密钥流必须经过 <code>reverse()</code> 操作</li>
|
||||||
|
<li><strong>种子来源</strong>:API 响应中的 <code>decode_key</code> 字段</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 赞助商 -->
|
||||||
|
<div class="section">
|
||||||
|
<div class="sponsor">
|
||||||
|
<h3>🚀 技术支持</h3>
|
||||||
|
<p>本项目由 <a href="https://tikhub.io" target="_blank">TikHub.io</a> 提供技术支持</p>
|
||||||
|
<p>TikHub - 专业的社交媒体数据 API 服务平台</p>
|
||||||
|
<p>支持抖音、快手、小红书、微信公众号、微信视频号、TikTok、Instagram、YouTube 等多平台数据获取与分析</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 作者信息 -->
|
||||||
|
<div class="section">
|
||||||
|
<div class="author-info">
|
||||||
|
<div class="avatar">👨💻</div>
|
||||||
|
<div class="details">
|
||||||
|
<h4>作者:Evil0ctal</h4>
|
||||||
|
<p>GitHub: <a href="https://github.com/Evil0ctal" target="_blank">@Evil0ctal</a></p>
|
||||||
|
<p>项目地址: <a href="https://github.com/Evil0ctal/WeChat-Channels-Video-File-Decryption" target="_blank">WeChat-Channels-Video-File-Decryption</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>
|
||||||
|
开源项目 ·
|
||||||
|
<a href="https://github.com/Evil0ctal/WeChat-Channels-Video-File-Decryption" target="_blank">GitHub</a> ·
|
||||||
|
仅供学习研究使用
|
||||||
|
</p>
|
||||||
|
<p style="margin-top: 8px; font-size: 12px;">
|
||||||
|
© 2025 Evil0ctal · MIT License
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- WASM 模块配置 -->
|
||||||
|
<script>
|
||||||
|
window.VTS_WASM_URL = 'wechat_files/wasm_video_decode.wasm';
|
||||||
|
window.MAX_HEAP_SIZE = 33554432;
|
||||||
|
</script>
|
||||||
|
<script src="wechat_files/wasm_video_decode.js"></script>
|
||||||
|
|
||||||
|
<!-- 主程序 -->
|
||||||
|
<script>
|
||||||
|
// 全局变量
|
||||||
|
let keystreamData = null;
|
||||||
|
const KEYSTREAM_SIZE = 131072; // 128 KB
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志输出函数
|
||||||
|
*/
|
||||||
|
function log(message) {
|
||||||
|
const output = document.getElementById('output');
|
||||||
|
output.innerHTML += message + '\n';
|
||||||
|
output.scrollTop = output.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空日志
|
||||||
|
*/
|
||||||
|
function clearOutput() {
|
||||||
|
document.getElementById('output').innerHTML = '';
|
||||||
|
keystreamData = null;
|
||||||
|
document.getElementById('exportBtn').disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出密钥流到文件
|
||||||
|
*/
|
||||||
|
function exportKeystream() {
|
||||||
|
if (!keystreamData) {
|
||||||
|
alert('⚠️ 请先生成密钥流!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为十六进制字符串
|
||||||
|
const hexString = Array.from(keystreamData)
|
||||||
|
.map(b => b.toString(16).padStart(2, '0'))
|
||||||
|
.join(' ');
|
||||||
|
|
||||||
|
// 创建下载
|
||||||
|
const blob = new Blob([hexString], { type: 'text/plain' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = 'keystream_131072_bytes.txt';
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
|
||||||
|
log('\n<span class="success">✅ 密钥流已导出</span>');
|
||||||
|
log(` 文件名: keystream_131072_bytes.txt`);
|
||||||
|
log(` 大小: ${keystreamData.length.toLocaleString()} bytes`);
|
||||||
|
log(` 格式: 十六进制,空格分隔`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WASM 回调函数 - 接收生成的密钥流
|
||||||
|
*/
|
||||||
|
window.wasm_isaac_generate = function(ptr, size) {
|
||||||
|
log('<span class="info">📝 接收 WASM 生成的密钥流</span>');
|
||||||
|
log(` 内存指针: ${ptr}`);
|
||||||
|
log(` 数据大小: ${size.toLocaleString()} bytes`);
|
||||||
|
|
||||||
|
// 创建数组保存密钥流
|
||||||
|
keystreamData = new Uint8Array(size);
|
||||||
|
|
||||||
|
// 从 WASM 内存读取
|
||||||
|
const wasmArray = new Uint8Array(Module.HEAPU8.buffer, ptr, size);
|
||||||
|
|
||||||
|
// 关键步骤:反转数组(微信加密算法要求)
|
||||||
|
keystreamData.set(wasmArray.slice().reverse());
|
||||||
|
|
||||||
|
log('<span class="info">🔄 已应用 reverse() 操作</span>');
|
||||||
|
log(` 前 16 字节: ${Array.from(keystreamData.slice(0, 16))
|
||||||
|
.map(b => b.toString(16).padStart(2, '0')).join(' ')}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成密钥流
|
||||||
|
*/
|
||||||
|
async function generateKeystream() {
|
||||||
|
clearOutput();
|
||||||
|
|
||||||
|
const decodeKey = document.getElementById('decodeKey').value.trim();
|
||||||
|
|
||||||
|
if (!decodeKey) {
|
||||||
|
alert('⚠️ 请输入 decode_key!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log('═══════════════════════════════════════════════════════════');
|
||||||
|
log(`🔑 Decode Key: ${decodeKey}`);
|
||||||
|
log('═══════════════════════════════════════════════════════════\n');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 检查 WASM 模块
|
||||||
|
if (typeof Module === 'undefined' || !Module.WxIsaac64) {
|
||||||
|
log('<span class="warning">⏳ WASM 模块未就绪,等待加载...</span>');
|
||||||
|
|
||||||
|
// 等待最多 10 秒
|
||||||
|
let loaded = false;
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
if (typeof Module !== 'undefined' && Module.WxIsaac64) {
|
||||||
|
loaded = true;
|
||||||
|
log('<span class="success">✅ WASM 模块已加载</span>\n');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loaded) {
|
||||||
|
log('<span class="error">❌ WASM 模块加载超时</span>');
|
||||||
|
log(' 请检查浏览器控制台 (F12) 查看错误信息');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第一步:创建 Isaac64 实例
|
||||||
|
log('<span class="info">步骤 1/3: 创建 Isaac64 实例</span>');
|
||||||
|
const decryptor = new Module.WxIsaac64(decodeKey);
|
||||||
|
log('<span class="success">✅ 实例创建成功</span>\n');
|
||||||
|
|
||||||
|
// 第二步:生成密钥流
|
||||||
|
log(`<span class="info">步骤 2/3: 生成 ${KEYSTREAM_SIZE.toLocaleString()} bytes 密钥流</span>`);
|
||||||
|
await decryptor.generate(KEYSTREAM_SIZE);
|
||||||
|
log('<span class="success">✅ 密钥流生成完成</span>\n');
|
||||||
|
|
||||||
|
// 第三步:清理资源
|
||||||
|
log('<span class="info">步骤 3/3: 清理资源</span>');
|
||||||
|
await decryptor.delete();
|
||||||
|
log('<span class="success">✅ 完成</span>\n');
|
||||||
|
|
||||||
|
// 验证结果
|
||||||
|
if (!keystreamData) {
|
||||||
|
log('<span class="error">❌ 密钥流数据为空</span>');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log('═══════════════════════════════════════════════════════════');
|
||||||
|
log('<span class="success">🎉 密钥流生成成功!</span>');
|
||||||
|
log('═══════════════════════════════════════════════════════════\n');
|
||||||
|
|
||||||
|
log('<span class="info">📊 密钥流信息:</span>');
|
||||||
|
log(` 大小: ${keystreamData.length.toLocaleString()} bytes (${(keystreamData.length / 1024).toFixed(2)} KB)`);
|
||||||
|
log(` 前 64 字节:`);
|
||||||
|
|
||||||
|
for (let i = 0; i < 64; i += 16) {
|
||||||
|
const chunk = Array.from(keystreamData.slice(i, i + 16))
|
||||||
|
.map(b => b.toString(16).padStart(2, '0')).join(' ');
|
||||||
|
log(` [${i.toString().padStart(5, '0')}]: ${chunk}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
log('\n<span class="success">✅ 请点击"导出密钥流"按钮保存文件</span>');
|
||||||
|
log('<span class="info">💡 导出后可使用 Python 脚本解密视频</span>');
|
||||||
|
|
||||||
|
// 启用导出按钮
|
||||||
|
document.getElementById('exportBtn').disabled = false;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
log(`\n<span class="error">❌ 错误: ${error.message}</span>`);
|
||||||
|
console.error('详细错误:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载完成后初始化
|
||||||
|
window.addEventListener('DOMContentLoaded', function() {
|
||||||
|
log('<span class="info">📦 正在加载 WASM 模块...</span>');
|
||||||
|
|
||||||
|
// 检查 WASM 加载状态
|
||||||
|
const checkInterval = setInterval(function() {
|
||||||
|
if (typeof Module !== 'undefined' && Module.WxIsaac64) {
|
||||||
|
clearInterval(checkInterval);
|
||||||
|
log('<span class="success">✅ WASM 模块加载成功</span>');
|
||||||
|
log('<span class="info">👉 请输入 decode_key 并点击"生成密钥流"开始</span>\n');
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
// 30 秒超时
|
||||||
|
setTimeout(function() {
|
||||||
|
clearInterval(checkInterval);
|
||||||
|
if (typeof Module === 'undefined' || !Module.WxIsaac64) {
|
||||||
|
log('<span class="error">❌ WASM 模块加载超时</span>');
|
||||||
|
log(' 请刷新页面重试,或检查浏览器控制台查看错误');
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
4707
wechat_files/wasm_video_decode.js
Normal file
4707
wechat_files/wasm_video_decode.js
Normal file
File diff suppressed because it is too large
Load Diff
BIN
wechat_files/wasm_video_decode.wasm
Normal file
BIN
wechat_files/wasm_video_decode.wasm
Normal file
Binary file not shown.
1626799
wechat_files/wasm_video_decode.wat
Normal file
1626799
wechat_files/wasm_video_decode.wat
Normal file
File diff suppressed because one or more lines are too long
602270
wechat_files/wasm_video_decode_fallback.js
Normal file
602270
wechat_files/wasm_video_decode_fallback.js
Normal file
File diff suppressed because one or more lines are too long
2
wechat_files/worker_release.js
Normal file
2
wechat_files/worker_release.js
Normal file
File diff suppressed because one or more lines are too long
BIN
wx_encrypted.mp4
Normal file
BIN
wx_encrypted.mp4
Normal file
Binary file not shown.
337
wx_response.json
Normal file
337
wx_response.json
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"request_id": "b36a339c-1ed4-4798-9ea3-9fff1dce6a2f",
|
||||||
|
"message": "Request successful. This request will incur a charge.",
|
||||||
|
"message_zh": "请求成功,本次请求将被计费。",
|
||||||
|
"support": "Discord: https://discord.gg/aMEAS8Xsvz",
|
||||||
|
"time": "2025-10-15 03:34:24",
|
||||||
|
"time_stamp": 1760524464,
|
||||||
|
"time_zone": "America/Los_Angeles",
|
||||||
|
"docs": "https://api.tikhub.io/#/WeChat-Channels-API/fetch_video_detail_api_v1_wechat_channels_fetch_video_detail_get",
|
||||||
|
"cache_message": "This request will be cached. You can access the cached result directly using the URL below, valid for 24 hours.",
|
||||||
|
"cache_message_zh": "本次请求将被缓存,你可以使用下面的 URL 直接访问缓存结果,有效期为 24 小时。",
|
||||||
|
"cache_url": "https://cache.tikhub.io/api/v1/cache/public/b36a339c-1ed4-4798-9ea3-9fff1dce6a2f?sign=0e03bd8b5e05c3361f8f0dfeace456aa3eb33981c3a8289e557b60f95ac33e96",
|
||||||
|
"router": "/api/v1/wechat_channels/fetch_video_detail",
|
||||||
|
"params": {
|
||||||
|
"id": "14396973035218999573"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"id": "14396973035218999573",
|
||||||
|
"nickname": "佳佳老师-努力版",
|
||||||
|
"username": "v2_060000231003b20faec8c7e58e1bc1d1c905ea37b0770368b6ad548d723634171b8e0eeff170@finder",
|
||||||
|
"object_desc": {
|
||||||
|
"description": "带孩子来北京旅游第一天我就后悔了#北京#北京旅游攻略#旅行推荐官#我的旅行日记#跟团游",
|
||||||
|
"media": [
|
||||||
|
{
|
||||||
|
"url": "https://finder.video.qq.com/251/20302/stodownload?encfilekey=Cvvj5Ix3eewK0tHtibORqcsqchXNh0Gf3sJcaYqC2rQDFKfMJuEK7ribjtVrZJynxHs9IucnCXqIPJHiascSbdk6BrlInWMAiczF8fx9mBicEtCsqr7A7ITDrelbCzNlU4xfY&bizid=1023&dotrans=0&hy=SH&idx=1&m=&uzid=1&token=AxricY7RBHdU0OAdVicXSW4veiapib5oxOib9uRQcFicccaMiczictkQn68Zp6JX1da8pQtJ4H0fohLibya35bOFUawnLH2Cf7ybX1BGXUNEImSmuqOno9JXiafVk296ZMWPicAbN8ml6Ra9Tz1uhZCMxa89lceeFWV4bpYyTIy3qC2uatqzo0&basedata=CAESABoDeFYwIgAqBwiLKRAAGAI&sign=EITEholF7h-_js8GwFQOZ2miMP6UJI4R9UNHA6SkNuVfHtUFh0DV4fx42il6sbRP62LcBaelT8jvzLRsOd2oXA&ctsc=141&web=1&extg=10f0000&ftype=417&svrbypass=AAuL%2FQsFAAABAAAAAACsrIwfqmjvxA%2Flr3jvaBAAAADnaHZTnGbFfAj9RgZXfw6Vxxyyr4YvTFozgElsFt%2F7BH8AjATMJhpCDbez09AVBwBlAne7Ss%2FtAvY%3D&svrnonce=1760524463",
|
||||||
|
"thumb_url": "https://finder.video.qq.com/251/20304/stodownload?encfilekey=rjD5jyTuFrIpZ2ibE8T7Ym3K77SEULgkia943VUmotibxAfXAd748CscH6AeiaOhLqLTPwOwUibibUJSaibvicAaownnJFkYqI1LiawYJtj2v0Udhbatn7luFiaxjhJQ&dotrans=0&hy=SZ&idx=1&m=&scene=2&uzid=2&picformat=200&token=cztXnd9GyrGhE2iaHGOXDiaCZM5peqiciaZj7EIOT6KTic9ek6biaHjaL1B3AHlMP3xsX7egYpfsPhlict5pzqiavzutmgUGibLyibBSicOsJ16pf04qypk4m85UEv6vKEmBSC5LjcMrkR2DUicGp9BBibkpYoFCqhh5tm57sJnrib&ctsc=1-141",
|
||||||
|
"media_type": 4,
|
||||||
|
"video_play_len": 52,
|
||||||
|
"width": 720.0,
|
||||||
|
"height": 1280.0,
|
||||||
|
"md5sum": "a4087c1f46961fc4165c67c508ce0506",
|
||||||
|
"file_size": 14088528,
|
||||||
|
"bitrate": 2066432,
|
||||||
|
"spec": [
|
||||||
|
{
|
||||||
|
"file_format": "xWT111",
|
||||||
|
"first_load_bytes": 1989820,
|
||||||
|
"bit_rate": 263,
|
||||||
|
"coding_format": "h264",
|
||||||
|
"dynamic_range_type": 0,
|
||||||
|
"vfps": 30.0,
|
||||||
|
"width": 720,
|
||||||
|
"height": 1280,
|
||||||
|
"duration_ms": 52300,
|
||||||
|
"quality_score": 0,
|
||||||
|
"video_bitrate": 2018,
|
||||||
|
"audio_bitrate": 128,
|
||||||
|
"level_order": 100,
|
||||||
|
"bypass": "{\"rid\":\"1760524463741618761\";\"level_order\":100;\"ip_area_id\":\"cn.ml\";\"ftype\":\"417\";\"max_bitrate\":150;\"trans_flag\":21;\"phone_level\":0;\"full_url_type\":0;\"video_play_len\":52;\"grade\":1;\"highest_grade\":1;\"lowest_grade\":3;\"cgi_id\":3763;\"cgi_scene\":6;\"pre_f_time\":30000;\"b_len\":10;\"avg_v_len\":52;\"fake_uin\":4122229112}",
|
||||||
|
"is_3az": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file_format": "xWT112",
|
||||||
|
"first_load_bytes": 1769820,
|
||||||
|
"bit_rate": 219,
|
||||||
|
"coding_format": "h264",
|
||||||
|
"dynamic_range_type": 0,
|
||||||
|
"vfps": 30.0,
|
||||||
|
"width": 576,
|
||||||
|
"height": 1024,
|
||||||
|
"duration_ms": 52300,
|
||||||
|
"quality_score": 76,
|
||||||
|
"video_bitrate": 1658,
|
||||||
|
"audio_bitrate": 128,
|
||||||
|
"level_order": 200,
|
||||||
|
"bypass": "{\"rid\":\"1760524463741618761\";\"level_order\":200;\"ip_area_id\":\"cn.ml\";\"ftype\":\"417\";\"max_bitrate\":150;\"trans_flag\":21;\"phone_level\":0;\"full_url_type\":0;\"video_play_len\":52;\"grade\":2;\"highest_grade\":1;\"lowest_grade\":3;\"cgi_id\":3763;\"cgi_scene\":6;\"pre_f_time\":30000;\"b_len\":10;\"avg_v_len\":52;\"fake_uin\":4122229112}",
|
||||||
|
"is_3az": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file_format": "xWT113",
|
||||||
|
"first_load_bytes": 1349513,
|
||||||
|
"bit_rate": 170,
|
||||||
|
"coding_format": "h264",
|
||||||
|
"dynamic_range_type": 0,
|
||||||
|
"vfps": 30.0,
|
||||||
|
"width": 576,
|
||||||
|
"height": 1024,
|
||||||
|
"duration_ms": 52300,
|
||||||
|
"quality_score": 74,
|
||||||
|
"video_bitrate": 1261,
|
||||||
|
"audio_bitrate": 128,
|
||||||
|
"level_order": 300,
|
||||||
|
"bypass": "{\"rid\":\"1760524463741618761\";\"level_order\":300;\"ip_area_id\":\"cn.ml\";\"ftype\":\"417\";\"max_bitrate\":150;\"trans_flag\":21;\"phone_level\":0;\"full_url_type\":0;\"video_play_len\":52;\"grade\":3;\"highest_grade\":1;\"lowest_grade\":3;\"cgi_id\":3763;\"cgi_scene\":6;\"pre_f_time\":30000;\"b_len\":10;\"avg_v_len\":52;\"fake_uin\":4122229112}",
|
||||||
|
"is_3az": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file_format": "xWT156",
|
||||||
|
"first_load_bytes": 1119125,
|
||||||
|
"bit_rate": 156,
|
||||||
|
"coding_format": "h265",
|
||||||
|
"dynamic_range_type": 0,
|
||||||
|
"vfps": 30.0,
|
||||||
|
"width": 720,
|
||||||
|
"height": 1280,
|
||||||
|
"duration_ms": 52300,
|
||||||
|
"quality_score": 80,
|
||||||
|
"video_bitrate": 1143,
|
||||||
|
"audio_bitrate": 128,
|
||||||
|
"level_order": 100,
|
||||||
|
"bypass": "{\"rid\":\"1760524463741618761\";\"level_order\":100;\"ip_area_id\":\"cn.ml\";\"ftype\":\"417\";\"max_bitrate\":150;\"trans_flag\":21;\"phone_level\":0;\"full_url_type\":0;\"video_play_len\":52;\"grade\":1;\"highest_grade\":1;\"lowest_grade\":3;\"cgi_id\":3763;\"cgi_scene\":6;\"pre_f_time\":30000;\"b_len\":10;\"avg_v_len\":52;\"fake_uin\":4122229112}",
|
||||||
|
"is_3az": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file_format": "xWT157",
|
||||||
|
"first_load_bytes": 914900,
|
||||||
|
"bit_rate": 130,
|
||||||
|
"coding_format": "h265",
|
||||||
|
"dynamic_range_type": 0,
|
||||||
|
"vfps": 30.0,
|
||||||
|
"width": 576,
|
||||||
|
"height": 1024,
|
||||||
|
"duration_ms": 52300,
|
||||||
|
"quality_score": 73,
|
||||||
|
"video_bitrate": 928,
|
||||||
|
"audio_bitrate": 128,
|
||||||
|
"level_order": 200,
|
||||||
|
"bypass": "{\"rid\":\"1760524463741618761\";\"level_order\":200;\"ip_area_id\":\"cn.ml\";\"ftype\":\"417\";\"max_bitrate\":150;\"trans_flag\":21;\"phone_level\":0;\"full_url_type\":0;\"video_play_len\":52;\"grade\":2;\"highest_grade\":1;\"lowest_grade\":3;\"cgi_id\":3763;\"cgi_scene\":6;\"pre_f_time\":30000;\"b_len\":10;\"avg_v_len\":52;\"fake_uin\":4122229112}",
|
||||||
|
"is_3az": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file_format": "xWT158",
|
||||||
|
"first_load_bytes": 710639,
|
||||||
|
"bit_rate": 103,
|
||||||
|
"coding_format": "h265",
|
||||||
|
"dynamic_range_type": 0,
|
||||||
|
"vfps": 30.0,
|
||||||
|
"width": 576,
|
||||||
|
"height": 1024,
|
||||||
|
"duration_ms": 52300,
|
||||||
|
"quality_score": 71,
|
||||||
|
"video_bitrate": 712,
|
||||||
|
"audio_bitrate": 128,
|
||||||
|
"level_order": 300,
|
||||||
|
"bypass": "{\"rid\":\"1760524463741618761\";\"level_order\":300;\"ip_area_id\":\"cn.ml\";\"ftype\":\"417\";\"max_bitrate\":150;\"trans_flag\":21;\"phone_level\":0;\"full_url_type\":0;\"video_play_len\":52;\"grade\":3;\"highest_grade\":1;\"lowest_grade\":3;\"cgi_id\":3763;\"cgi_scene\":6;\"pre_f_time\":30000;\"b_len\":10;\"avg_v_len\":52;\"fake_uin\":4122229112}",
|
||||||
|
"is_3az": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cover_url": "http://wxapp.tc.qq.com/251/20304/stodownload?encfilekey=rjD5jyTuFrIpZ2ibE8T7Ym3K77SEULgkia943VUmotibxAfXAd748CscH6AeiaOhLqLTPwOwUibibUJSaibvicAaownnJFkYqI1LiawYJtj2v0Udhbatn7luFiaxjhJQ&token=x5Y29zUxcibA8OUawCN97XhLesuEXBnuqQcoJSxlAmMZZZhybqzospvjBIGxhIutewInR3KtfYCua3hY00vdibOCZ1T3LoQUZnjy0Ksn1x6kVoGR5a5IM2KpB2pR1JEibtvWawbk4mCjJibNV5SmbHZ1B0eQYVVIHgnP&idx=1&dotrans=0&hy=SZ&m=&scene=2&uzid=2&picformat=200",
|
||||||
|
"decode_key": "2136343393",
|
||||||
|
"url_token": "&token=AxricY7RBHdU0OAdVicXSW4veiapib5oxOib9uRQcFicccaMiczictkQn68Zp6JX1da8pQtJ4H0fohLibya35bOFUawnLH2Cf7ybX1BGXUNEImSmuqOno9JXiafVk296ZMWPicAbN8ml6Ra9Tz1uhZCMxa89lceeFWV4bpYyTIy3qC2uatqzo0&basedata=CAESABoDeFYwIgAqBwiLKRAAGAI&sign=EITEholF7h-_js8GwFQOZ2miMP6UJI4R9UNHA6SkNuVfHtUFh0DV4fx42il6sbRP62LcBaelT8jvzLRsOd2oXA&ctsc=141&web=1&extg=10f0000&ftype=417&svrbypass=AAuL%2FQsFAAABAAAAAACsrIwfqmjvxA%2Flr3jvaBAAAADnaHZTnGbFfAj9RgZXfw6Vxxyyr4YvTFozgElsFt%2F7BH8AjATMJhpCDbez09AVBwBlAne7Ss%2FtAvY%3D&svrnonce=1760524463",
|
||||||
|
"codec_info": {
|
||||||
|
"video_score": 59.0
|
||||||
|
},
|
||||||
|
"full_thumb_url": "https://finder.video.qq.com/251/20304/stodownload?encfilekey=rjD5jyTuFrIpZ2ibE8T7Ym3K77SEULgkia943VUmotibxAfXAd748CscH6AeiaOhLqLTPwOwUibibUJSaibvicAaownnJFkYqI1LiawYJtj2v0Udhbatn7luFiaxjhJQ&dotrans=0&hy=SZ&idx=1&m=&scene=2&uzid=2&picformat=200&token=x5Y29zUxcibA8OUawCN97XvrwADwSPrOmnVMvgoPbKKicbIbYXufUutPyLIMRldiaPG7icOm081lW4IW0EGDRX31Ijw1kVmW3I44Soic3Srjic8ibz77IibSrgyCPJZMdNHyHOibmsibV3eoXG1zIN2FDKu1nhpfTlF1qw42Sn&ctsc=3-141",
|
||||||
|
"full_url": "",
|
||||||
|
"full_width": 0.0,
|
||||||
|
"full_height": 0.0,
|
||||||
|
"full_md5sum": "",
|
||||||
|
"full_file_size": 0,
|
||||||
|
"full_bitrate": 0,
|
||||||
|
"full_cover_url": "https://finder.video.qq.com/251/20304/stodownload?encfilekey=rjD5jyTuFrIpZ2ibE8T7Ym3K77SEULgkia943VUmotibxAfXAd748CscH6AeiaOhLqLTPwOwUibibUJSaibvicAaownnJFkYqI1LiawYJtj2v0Udhbatn7luFiaxjhJQ&token=x5Y29zUxcibDjE9JYkmdS0pjkicoNWSyBXjLOjFY4Y7uBjhfh1zLMlI3RWuvdUdcAt1AbhbImTNoQZBMTZtya7C9cOEQ1ZKXSSWh7IBDYibeiab4VzZTgz3feJObKryUtGibOPzBOeX0ztYPYx9ZhzH5D4Df4iaibBFOHRf&idx=1&dotrans=0&hy=SZ&m=&scene=2&uzid=2&picformat=200&ctsc=6-141",
|
||||||
|
"live_cover_imgs": [],
|
||||||
|
"scaling_info": {
|
||||||
|
"version": "v2.0.1",
|
||||||
|
"is_split_screen": false,
|
||||||
|
"is_disable_follow": false,
|
||||||
|
"up_percent_position": 0.0,
|
||||||
|
"down_percent_position": 0.0
|
||||||
|
},
|
||||||
|
"audio_spec": [],
|
||||||
|
"media_cdn_info": {
|
||||||
|
"is_use_pcdn": true,
|
||||||
|
"begin_use_pcdn_buffer_seconds": 12,
|
||||||
|
"exit_use_pcdn_buffer_seconds": 8,
|
||||||
|
"preload_begin_use_pcdn_buffer_kbytes": 768,
|
||||||
|
"pcdn_timeout_retry_count": 1,
|
||||||
|
"mars_pre_download_kbytes": 0,
|
||||||
|
"is_use_ugc_when_no_preload": true,
|
||||||
|
"preload_use_pcdn_only": true,
|
||||||
|
"preload_pcdn_connections": 4,
|
||||||
|
"soc_force_use_h3": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"media_type": 4,
|
||||||
|
"location": {
|
||||||
|
"product_id": [],
|
||||||
|
"multi_lang_info": []
|
||||||
|
},
|
||||||
|
"ext_reading": {},
|
||||||
|
"topic": {},
|
||||||
|
"mentioned_user": [],
|
||||||
|
"feed_location": {
|
||||||
|
"product_id": [],
|
||||||
|
"multi_lang_info": []
|
||||||
|
},
|
||||||
|
"mentioned_musics": [],
|
||||||
|
"short_title": [
|
||||||
|
{
|
||||||
|
"short_title": "北京旅游报团推荐"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"flow_card_desc": {
|
||||||
|
"description": "带孩子来北京旅游第一天我就后悔了"
|
||||||
|
},
|
||||||
|
"finder_newlife_desc": {
|
||||||
|
"secretly_push_chatroom_name": [],
|
||||||
|
"comment_egg_info": [],
|
||||||
|
"video_tmpl_info": [],
|
||||||
|
"custom_crop_info": [],
|
||||||
|
"mp_locations": []
|
||||||
|
},
|
||||||
|
"member_data": {},
|
||||||
|
"mod_feed_info": {
|
||||||
|
"history": [],
|
||||||
|
"modify_button_status": 0,
|
||||||
|
"button_tips_wording": "发表超过3个月后无法修改"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"createtime": 1716252927,
|
||||||
|
"like_flag": 0,
|
||||||
|
"like_list": [],
|
||||||
|
"comment_list": [],
|
||||||
|
"forward_count": 2602,
|
||||||
|
"contact": {
|
||||||
|
"username": "v2_060000231003b20faec8c7e58e1bc1d1c905ea37b0770368b6ad548d723634171b8e0eeff170@finder",
|
||||||
|
"nickname": "佳佳老师-努力版",
|
||||||
|
"head_url": "https://wx.qlogo.cn/finderhead/ver_1/HobNmPgW6o9b3riajQfAunTaBP5Nd6aLCicBpr0H8XxtYL0zSZ4v8LtAjG2ubKVR9bQvWRCZgQIOGg0wJbFzOtQ6Y7up7dViagSviaIwVB3epDU/132",
|
||||||
|
"signature": "童蒙养正 \n用老祖宗的智慧养正孩子",
|
||||||
|
"follow_flag": 0,
|
||||||
|
"cover_img_url": "",
|
||||||
|
"spam_status": 0,
|
||||||
|
"ext_flag": 2097164,
|
||||||
|
"ext_info": {
|
||||||
|
"country": "CN",
|
||||||
|
"province": "Hunan",
|
||||||
|
"city": "Changsha",
|
||||||
|
"sex": 2
|
||||||
|
},
|
||||||
|
"live_status": 2,
|
||||||
|
"live_cover_img_url": "",
|
||||||
|
"live_info": {
|
||||||
|
"anchor_status_flag": "2048",
|
||||||
|
"switch_flag": 4607,
|
||||||
|
"source_type": 0,
|
||||||
|
"mic_setting": {
|
||||||
|
"setting_flag": 0,
|
||||||
|
"setting_switch_flag": 4
|
||||||
|
},
|
||||||
|
"lottery_setting": {},
|
||||||
|
"live_cover_imgs": []
|
||||||
|
},
|
||||||
|
"friend_follow_count": 0,
|
||||||
|
"feed_count": 272,
|
||||||
|
"bind_info": [],
|
||||||
|
"menu": [],
|
||||||
|
"status": "0",
|
||||||
|
"additional_flag": "1"
|
||||||
|
},
|
||||||
|
"recommender_list": [],
|
||||||
|
"like_count": 2570,
|
||||||
|
"comment_count": 268,
|
||||||
|
"friend_like_count": 0,
|
||||||
|
"object_nonce_id": "14645445282565319987_0_141_0_0",
|
||||||
|
"object_status": 0,
|
||||||
|
"send_share_fav_wording": "",
|
||||||
|
"original_flag": 0,
|
||||||
|
"secondary_show_flag": 1,
|
||||||
|
"mentioned_user_contact": [],
|
||||||
|
"session_buffer": "eyJjdXJfbGlrZV9jb3VudCI6MjU3MCwiY3VyX2NvbW1lbnRfY291bnQiOjI2OCwicmVjYWxsX3R5cGVzIjpbXSwiZGVsaXZlcnlfc2NlbmUiOjYsImRlbGl2ZXJ5X3RpbWUiOjE3NjA1MjQ0NjMsInNldF9jb25kaXRpb25fZmxhZyI6MjksInJlY2FsbF9pbmRleCI6W10sInJlY2FsbF9pbmZvIjpbXSwic2VjcmV0ZV9kYXRhIjoiQmdBQVo5WHliRFgwYlljNTVGTDVpSFwvTm1TT1BPKzFRMURyYVpRMDU4eDk2SDNHMDlqTkdTY3pEYWZHdHIrNlFKTHhRdzVaY3NRSWwiLCJpZGMiOjMsImRldmljZV90eXBlX2lkIjoyOSwiY2xpZW50X3JlcG9ydF9idWZmIjoie1wiZW50cmFuY2VJZFwiOlwiMTAwMVwifSIsImNvbW1lbnRfc2NlbmUiOjE0MSwib2JqZWN0X2lkIjoxNDM5Njk3MzAzNTIxODk5OTU3MywiZW50cmFuY2Vfc2NlbmUiOjMyLCJjYXJkX3R5cGUiOjIwLCJleHB0X2ZsYWciOjEsImN0eF9pZCI6IjMyLTIwLTE0MC1XZjY1NWU5ZWE4Nzg0YTEwYzE3NjA1MjQ0NjMwMTAiLCJvYmpfZmxhZyI6MTYzODQwLCJlcmlsIjpbXSwicGdrZXlzIjpbXSwib2JqX2V4dF9mbGFnIjoyNzAzMzYsInNjaWQiOiI4NDU3NzQ1Mi1hOWIyLTExZjAtOTI3MC04N2NiN2NkOGVhM2YifQ==",
|
||||||
|
"fav_count": 4703,
|
||||||
|
"fav_flag": 0,
|
||||||
|
"url_valid_duration": 172800,
|
||||||
|
"forward_style": 0,
|
||||||
|
"permission_flag": 2147483648,
|
||||||
|
"object_type": 0,
|
||||||
|
"friend_comment_list": [],
|
||||||
|
"ad_flag": 4,
|
||||||
|
"func_flag": 272,
|
||||||
|
"show_original": false,
|
||||||
|
"finder_promotion_jumpinfo": {
|
||||||
|
"toast_msg": "无法推广3个月以上的视频或照片",
|
||||||
|
"show_toast_msg": true,
|
||||||
|
"wording": "帮上热门",
|
||||||
|
"destination_type": 1
|
||||||
|
},
|
||||||
|
"ip_region_info": {
|
||||||
|
"region_text": "湖南"
|
||||||
|
},
|
||||||
|
"object_extend": {
|
||||||
|
"fav_info": {
|
||||||
|
"star_fav_flag": 0,
|
||||||
|
"star_fav_count": 0,
|
||||||
|
"fingerlike_fav_flag": 0,
|
||||||
|
"fingerlike_fav_count": 4632
|
||||||
|
},
|
||||||
|
"preload_config": {
|
||||||
|
"comment_is_preload": true,
|
||||||
|
"comment_wait_time": 0,
|
||||||
|
"comment_preload_buffer": "CAEQAA=="
|
||||||
|
},
|
||||||
|
"monotonic_data": {
|
||||||
|
"count_info": {
|
||||||
|
"comment_count": 268,
|
||||||
|
"like_count": 2570,
|
||||||
|
"forward_count": 2602,
|
||||||
|
"read_count": 0,
|
||||||
|
"fav_count": 4703,
|
||||||
|
"version_data": {
|
||||||
|
"data_version": 1760511462,
|
||||||
|
"overwrite": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"comment_count": {
|
||||||
|
"comment_count": 268,
|
||||||
|
"image_comment_count": 0,
|
||||||
|
"version_data": {
|
||||||
|
"data_version": 1760511462
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"global_fav_count": {},
|
||||||
|
"global_fav_flag": {},
|
||||||
|
"thumb_up_count": {
|
||||||
|
"thumb_up_count": 4703
|
||||||
|
},
|
||||||
|
"thumb_up_flag": {},
|
||||||
|
"chatroom_push_count": {},
|
||||||
|
"chatroom_push_flag": {
|
||||||
|
"chatroom_push_list": []
|
||||||
|
},
|
||||||
|
"thank_chatroom_push_flag": {}
|
||||||
|
},
|
||||||
|
"finder_newlife_info": {
|
||||||
|
"chatroom_push_list": [],
|
||||||
|
"picture_crop_info": [],
|
||||||
|
"follow_post_info": {}
|
||||||
|
},
|
||||||
|
"original_info": {
|
||||||
|
"original_audit_status": 1
|
||||||
|
},
|
||||||
|
"carousel_info": {
|
||||||
|
"carousel_comment_latency_time": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user