Add Files

This commit is contained in:
Evil0ctal
2025-10-15 05:44:59 -07:00
parent 988db2df1b
commit da298fbde7
22 changed files with 2236260 additions and 2 deletions

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View 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
View 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
View 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>

View 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
View 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>

View 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>

View 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
View 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
View 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
View 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
View File

@@ -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. 等待解密完成
![GUI Screenshot](https://via.placeholder.com/600x400?text=GUI+Screenshot)
### 方式二:命令行(推荐进阶用户)
#### 交互模式(推荐)
```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
View 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
View 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
View 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 bytes128 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>

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
wx_encrypted.mp4 Normal file

Binary file not shown.

337
wx_response.json Normal file
View 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
}
}
}
}