Просмотр исходного кода

feat: 添加代理地理位置匹配和隐身模式相关功能

新增功能:
- 代理地理位置匹配文档(LOCATION_MATCHING.md)
- 代理隐身模式增强文档(PROXY_STEALTH_ENHANCEMENT.md)
- Playwright隐身模式工具(stealth.py)

新增工具脚本:
- 数据库查询工具(get_account.cjs, get_shenlong_config.cjs, temp_query.cjs)
- 发布任务测试脚本(test_direct_publish.cjs, test_publish_task61.cjs, test_task_61.js, test_task_full.js)
- 代理测试脚本(test_proxy_weixin.cjs, test_proxy_location.py)

这些工具和文档用于增强代理使用的安全性和测试发布功能。
Ethanfly 5 дней назад
Родитель
Сommit
bdc1c2156b

+ 197 - 0
docs/LOCATION_MATCHING.md

@@ -0,0 +1,197 @@
+# 位置自动匹配功能说明
+
+## 功能概述
+
+当启用代理发布时,系统会自动使用代理所在地区作为发布位置,避免账号地区与发布位置不一致触发风控。
+
+## 实现方式
+
+### 1. 自动提取代理地区
+
+```python
+# 在 app.py 中
+if proxy_payload.get('enabled'):
+    proxy_city = proxy_payload.get('city', '').strip()
+    if proxy_city:
+        location = proxy_city  # 自动设置为代理地区
+```
+
+### 2. 传递给发布器
+
+```python
+params = PublishParams(
+    title=title,
+    video_path=video_path,
+    location=location  # 使用代理地区
+)
+```
+
+### 3. 设置位置(微信视频号)
+
+```python
+# 在 weixin.py 的 add_title_tags 方法中
+if params.location:
+    await self.set_location(params.location)
+```
+
+## 使用方法
+
+### 方式 1: 自动从代理配置提取(推荐)
+
+```json
+POST /publish/ai-assisted
+{
+  "platform": "weixin",
+  "proxy": {
+    "enabled": true,
+    "provider": "shenlong",
+    "productKey": "xxx",
+    "signature": "xxx",
+    "regionCode": "310000",
+    "city": "上海"  // ← 自动使用这个
+  }
+}
+```
+
+**系统会自动:**
+1. 提取 `city` 字段
+2. 设置为发布位置
+3. 避免地区不一致
+
+### 方式 2: 手动指定位置
+
+```json
+{
+  "platform": "weixin",
+  "location": "北京",  // ← 手动指定
+  "proxy": {
+    "enabled": true,
+    ...
+  }
+}
+```
+
+## 优先级
+
+1. **手动指定的 location** > **代理配置的 city** > **默认值(重庆市)**
+
+## 注意事项
+
+### 微信视频号位置设置
+
+微信视频号发布页面的位置选择 UI 可能因版本/账号而异。当前实现了以下查找策略:
+
+1. 查找位置输入框:
+   - `input[placeholder*="位置"]`
+   - `input[placeholder*="所在"]`
+   - `input[placeholder*="地点"]`
+
+2. 查找位置按钮:
+   - `div:has-text("所在位置")`
+   - `div:has-text("添加位置")`
+
+3. 输入并选择:
+   - 填充位置名称
+   - 等待建议出现
+   - 选择匹配项
+
+### 如果位置未生效
+
+1. **检查代理配置**:
+   ```json
+   {
+     "proxy": {
+       "city": "上海"  // 确保这个字段存在
+     }
+   }
+   ```
+
+2. **查看日志**:
+   ```
+   [weixin] 准备设置位置: 上海
+   [weixin] 正在设置位置: 上海
+   [weixin] 找到位置元素: xxx
+   [weixin] ✓ 位置设置成功: 上海
+   ```
+
+3. **手动指定位置**:
+   ```json
+   {
+     "location": "上海"  // 手动指定
+   }
+   ```
+
+## 调试建议
+
+### 查看完整日志
+
+```bash
+# 启动服务时禁用缓冲
+PYTHONUNBUFFERED=1 python app.py --headless false
+```
+
+### 检查截图
+
+发布完成后的截图会显示最终的位置设置状态。
+
+### 有头模式观察
+
+```json
+{
+  "headless": false  // 显示浏览器窗口
+}
+```
+
+在浏览器窗口中观察位置设置过程。
+
+## 已知问题
+
+### 1. 微信视频号位置UI变化
+
+如果微信更新了发布页面UI,位置选择器可能失效。
+
+**解决方法**:更新 `weixin.py` 中的 `set_location` 方法的选择器列表。
+
+### 2. 位置建议不匹配
+
+如果输入的位置没有精确匹配的建议项,系统会尝试选择第一个建议。
+
+**解决方法**:使用标准城市名称(如"上海"而非"上海市")。
+
+## 测试验证
+
+### 测试脚本
+
+```javascript
+const response = await fetch('http://localhost:5005/publish/ai-assisted', {
+  method: 'POST',
+  headers: {'Content-Type': 'application/json'},
+  body: JSON.stringify({
+    platform: 'weixin',
+    proxy: {
+      enabled: true,
+      city: '上海'  // 测试自动匹配
+    }
+  })
+});
+```
+
+### 预期结果
+
+```
+✅ 使用代理地区作为发布位置: 上海
+✅ 准备设置位置: 上海
+✅ 正在设置位置: 上海
+✅ 找到位置元素
+✅ 位置设置成功: 上海
+```
+
+## 其他平台
+
+不同平台的位置设置方式不同:
+
+- **抖音**: 需要单独实现
+- **小红书**: 需要单独实现
+- **快手**: 需要单独实现
+
+如需支持其他平台,请在对应的 `platforms/xxx.py` 中实现 `set_location` 方法。

+ 247 - 0
docs/PROXY_STEALTH_ENHANCEMENT.md

@@ -0,0 +1,247 @@
+# 代理与反检测增强功能
+
+## 概述
+
+本次更新为 multi-platform-media-manage 项目添加了以下增强功能,以解决跨区域发布时遇到的风控问题。
+
+## ⚠️ 重要说明:代理使用策略
+
+### 神龙代理 IP 限制
+- **有效期**:3 分钟
+- **适用场景**:页面访问、信息填充、小额数据传输
+- **不适用场景**:大文件上传(视频文件)
+
+### 平台特殊策略
+
+**微信视频号(weixin)**:
+- ❌ **不使用代理**:大视频上传会超过 3 分钟,导致代理超时
+- ✅ **使用直连**:配合反检测脚本,可以有效绕过风控
+- ✅ **推荐配置**:
+  ```json
+  {
+    "platform": "weixin",
+    "proxy": {
+      "enabled": false  // 微信视频号建议不使用代理
+    }
+  }
+  ```
+
+**其他平台**:
+- 抖音、小红书、快手:可以使用代理(小文件上传)
+- 大视频文件(> 50MB):建议使用直连
+
+## 新增功能
+
+### 1. 反检测脚本 (Stealth Scripts)
+
+**文件**: `server/python/utils/stealth.py`
+
+提供以下反检测能力:
+- **隐藏 WebDriver 特征**: 移除 `navigator.webdriver` 属性
+- **伪装浏览器插件**: 模拟真实的 Chrome 插件列表
+- **WebGL 指纹伪装**: 随机化 WebGL vendor 和 renderer
+- **Canvas 指纹噪声**: 添加微小噪声避免指纹追踪
+- **Audio 指纹噪声**: 干扰音频指纹识别
+- **Navigator 属性伪装**: 伪装设备内存、CPU 核心数等
+
+### 2. 增强的浏览器初始化
+
+**文件**: `server/python/platforms/base.py`
+
+改进的 `init_browser` 方法:
+- 自动注入反检测脚本
+- 添加真实的浏览器启动参数
+- 设置完整的 HTTP 请求头
+- 支持代理配置
+
+### 3. 代理质量检测
+
+**文件**: `server/python/app.py`
+
+新增 `_test_proxy_for_platform` 函数:
+- 测试代理 IP 对特定平台的可用性
+- 检测是否被目标平台屏蔽
+- 返回延迟和状态码信息
+
+### 4. 代理测试 API
+
+**端点**: `POST /proxy/test`
+
+用于在发布前测试代理配置:
+
+```json
+// 请求
+{
+    "provider": "shenlong",
+    "productKey": "your-product-key",
+    "signature": "your-signature",
+    "platform": "douyin"  // 可选,测试对特定平台的可用性
+}
+
+// 响应
+{
+    "success": true,
+    "proxy": "http://x.x.x.x:port",
+    "platform_test": {
+        "ok": true,
+        "blocked": false,
+        "cost_ms": 1234
+    }
+}
+```
+
+### 5. 人类行为模拟
+
+**文件**: `server/python/platforms/base.py`
+
+新增方法:
+- `human_like_delay()`: 模拟随机延迟
+- `human_like_scroll()`: 模拟滚动行为
+- `human_like_type()`: 模拟输入行为
+- `random_mouse_move()`: 随机移动鼠标
+
+## 使用方式
+
+### 1. 基本发布(带代理)
+
+```json
+POST /publish
+{
+    "platform": "douyin",
+    "cookie": "your-cookies",
+    "title": "视频标题",
+    "video_path": "/path/to/video.mp4",
+    "proxy": {
+        "enabled": true,
+        "provider": "shenlong",
+        "productKey": "your-key",
+        "signature": "your-sign",
+        "regionCode": "500000"  // 重庆
+    }
+}
+```
+
+### 2. AI 辅助发布(推荐)
+
+```json
+POST /publish/ai-assisted
+{
+    "platform": "douyin",
+    "cookie": "your-cookies",
+    "title": "视频标题",
+    "video_path": "/path/to/video.mp4",
+    "headless": false,  // 显示浏览器窗口,便于处理验证码
+    "return_screenshot": true,
+    "proxy": {
+        "enabled": true,
+        "provider": "shenlong",
+        "productKey": "your-key",
+        "signature": "your-sign"
+    }
+}
+```
+
+### 3. 测试代理配置
+
+```bash
+curl -X POST http://localhost:5005/proxy/test \
+  -H "Content-Type: application/json" \
+  -d '{
+    "provider": "shenlong",
+    "productKey": "your-key",
+    "signature": "your-sign",
+    "platform": "douyin"
+  }'
+```
+
+## 最佳实践
+
+### 1. 代理配置建议
+
+- **使用高质量代理**: 选择住宅 IP 代理,避免数据中心 IP
+- **地区匹配**: 尽量使用与账号注册地相近的代理 IP
+- **运营商匹配**: 选择与目标平台主要用户群相同的运营商
+
+### 2. 发布策略
+
+1. **首次发布**:
+   - 使用 `headless: false` 显示浏览器窗口
+   - 观察 Cookie 状态和验证码情况
+   - 记录成功的配置
+
+2. **批量发布**:
+   - 每个账号使用独立的代理 IP
+   - 控制发布频率(建议间隔 30 分钟以上)
+   - 使用定时发布分散时间
+
+3. **遇到验证码**:
+   - 响应 `need_captcha: true` 时手动处理
+   - 处理完成后重试发布
+   - 考虑更换代理 IP
+
+### 3. 调试建议
+
+1. **查看日志**:
+```
+[Proxy] shenlong resolved: city=- area=500100 candidates=5/10
+[Proxy] test ok: 192.168.***.***:8080 cost=234ms
+[Proxy] platform douyin ok: 192.168.***.***:8080 cost=1234ms
+[douyin] 已注入反检测脚本
+```
+
+2. **检查截图**:
+   - 发布失败时返回 `screenshot_base64`
+   - 可解码查看页面状态
+
+## 常见问题
+
+### Q1: 代理连接成功但发布失败?
+
+**原因**: 代理 IP 可能被目标平台标记
+
+**解决方案**:
+1. 使用 `/proxy/test` 接口测试代理
+2. 更换代理地区或运营商
+3. 使用新的代理 IP
+
+### Q2: 频繁触发验证码?
+
+**原因**: 浏览器指纹或行为模式被识别
+
+**解决方案**:
+1. 确保反检测脚本正常加载(查看日志)
+2. 降低操作频率
+3. 使用 `headless: false` 观察页面行为
+
+### Q3: Cookie 快速失效?
+
+**原因**: 代理 IP 与账号登录地不一致
+
+**解决方案**:
+1. 使用与账号注册地相近的代理
+2. 避免频繁切换 IP
+3. 使用同一代理维护账号
+
+## 技术细节
+
+### 反检测脚本加载顺序
+
+1. 浏览器启动时添加 `--disable-blink-features=AutomationControlled` 参数
+2. 创建上下文时注入 `add_init_script()`
+3. 页面加载前脚本已生效
+
+### 代理测试流程
+
+1. 基础连通性测试 (`_test_proxy_connectivity`)
+2. 平台特定测试 (`_test_proxy_for_platform`)
+3. 检测是否被重定向到验证页面
+4. 返回详细测试结果
+
+## 更新日志
+
+### 2026-02-27
+- 新增 `utils/stealth.py` 反检测脚本
+- 增强 `base.py` 浏览器初始化
+- 添加平台特定代理测试
+- 新增 `/proxy/test` 和 `/proxy/platforms` API
+- 更新代理解析函数支持平台参数

+ 36 - 0
server/get_account.cjs

@@ -0,0 +1,36 @@
+const mysql = require('mysql2/promise');
+
+async function main() {
+  const conn = await mysql.createConnection({
+    host: '8.136.223.156',
+    port: 6630,
+    user: 'media_manager',
+    password: 'media_manager',
+    database: 'media_manager'
+  });
+  
+  // 查询表结构
+  const [columns] = await conn.query(`SHOW COLUMNS FROM platform_accounts`);
+  console.log('=== platform_accounts 表结构 ===');
+  columns.forEach(col => console.log(`  ${col.Field}: ${col.Type}`));
+  
+  // 查询账号 24 的信息
+  const [accounts] = await conn.query(`
+    SELECT * FROM platform_accounts WHERE id = 24
+  `);
+  console.log('\n=== 账号 24 信息 ===');
+  if (accounts.length > 0) {
+    const acc = accounts[0];
+    Object.keys(acc).forEach(key => {
+      if (key === 'cookies' || key === 'cookie') {
+        console.log(`${key}: ${acc[key] ? '(长度: ' + acc[key].length + ')' : 'null'}`);
+      } else {
+        console.log(`${key}: ${acc[key]}`);
+      }
+    });
+  }
+  
+  await conn.end();
+}
+
+main().catch(console.error);

+ 27 - 0
server/get_shenlong_config.cjs

@@ -0,0 +1,27 @@
+const mysql = require('mysql2/promise');
+
+async function main() {
+  const conn = await mysql.createConnection({
+    host: '8.136.223.156',
+    port: 6630,
+    user: 'media_manager',
+    password: 'media_manager',
+    database: 'media_manager'
+  });
+  
+  // 获取所有系统配置
+  const [configs] = await conn.query(`
+    SELECT config_key, LEFT(config_value, 50) as config_value
+    FROM system_config
+    ORDER BY config_key
+  `);
+  
+  console.log('=== 系统配置 ===');
+  configs.forEach(c => {
+    console.log(`${c.config_key}: ${c.config_value}${c.config_value.length >= 50 ? '...' : ''}`);
+  });
+  
+  await conn.end();
+}
+
+main().catch(console.error);

+ 63 - 0
server/python/test_proxy_location.py

@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+import requests
+import json
+
+PYTHON_API = "http://localhost:5005"
+
+def test():
+    print("=" * 60)
+    print("Test Proxy IP and Location")
+    print("=" * 60)
+
+    # Test proxy
+    print("\n[1] Getting proxy IP...")
+    proxy_data = {
+        "provider": "shenlong",
+        "productKey": "o2ihwv3e",
+        "signature": "ccff437b0c211d7a758b0926cbdcf958",
+        "regionCode": "310000",
+        "city": "Shanghai",
+        "platform": "weixin"
+    }
+
+    try:
+        resp = requests.post(f"{PYTHON_API}/proxy/test", json=proxy_data, timeout=30)
+        result = resp.json()
+        print(f"Response: {json.dumps(result, indent=2, ensure_ascii=False)}")
+
+        if result.get("success"):
+            proxy = result.get("proxy", "")
+            print(f"\n[OK] Proxy: {proxy}")
+
+            # Extract IP
+            proxy_ip = proxy.replace("http://", "").split(":")[0]
+            print(f"Proxy IP: {proxy_ip}")
+
+            # Query IP location
+            print(f"\n[2] Querying IP location...")
+            try:
+                ip_info = requests.get(f"http://ip-api.com/json/{proxy_ip}?lang=zh-CN", timeout=5).json()
+                print(f"Country: {ip_info.get('country', 'N/A')}")
+                print(f"Region: {ip_info.get('regionName', 'N/A')}")
+                print(f"City: {ip_info.get('city', 'N/A')}")
+                print(f"ISP: {ip_info.get('isp', 'N/A')}")
+
+                city = ip_info.get('city', '')
+                if 'shanghai' in city.lower() or 'shang hai' in city.lower() or city == '上海':
+                    print(f"\n[OK] Proxy location is Shanghai")
+                else:
+                    print(f"\n[WARNING] Proxy location is {city}, NOT Shanghai!")
+                    print("This is why WeChat shows wrong location")
+            except Exception as e:
+                print(f"[ERROR] IP query failed: {e}")
+        else:
+            print(f"[ERROR] Proxy failed: {result.get('error', 'Unknown')}")
+
+    except Exception as e:
+        print(f"[ERROR] Request failed: {e}")
+
+    print("\n" + "=" * 60)
+
+if __name__ == "__main__":
+    test()

+ 461 - 0
server/python/utils/stealth.py

@@ -0,0 +1,461 @@
+# -*- coding: utf-8 -*-
+"""
+Playwright Stealth Utils - 反检测工具
+用于隐藏浏览器自动化特征,绕过平台风控
+"""
+
+import random
+import hashlib
+import time
+from typing import Dict, Any, Optional
+
+
+# Stealth JS 脚本 - 隐藏 webdriver 等自动化特征
+STEALTH_JS = """
+// 1. 隐藏 webdriver 属性
+Object.defineProperty(navigator, 'webdriver', {
+    get: () => undefined
+});
+
+// 2. 修改 plugins 长度(非零表示真实浏览器)
+Object.defineProperty(navigator, 'plugins', {
+    get: () => {
+        const plugins = [
+            { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
+            { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
+            { name: 'Native Client', filename: 'internal-nacl-plugin', description: '' }
+        ];
+        plugins.item = (index) => plugins[index] || null;
+        plugins.namedItem = (name) => plugins.find(p => p.name === name) || null;
+        plugins.refresh = () => {};
+        return plugins;
+    }
+});
+
+// 3. 修改 languages
+Object.defineProperty(navigator, 'languages', {
+    get: () => ['zh-CN', 'zh', 'en-US', 'en']
+});
+
+// 4. 隐藏自动化相关属性
+delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array;
+delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise;
+delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol;
+delete window.cdc_adoQpoasnfa76pfcZLmcfl_JSON;
+delete window.cdc_adoQpoasnfa76pfcZLmcfl_Object;
+
+// 5. 伪装 Chrome 属性
+window.chrome = {
+    app: {
+        isInstalled: false,
+        InstallState: { DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed' },
+        RunningState: { CANNOT_RUN: 'cannot_run', READY_TO_RUN: 'ready_to_run', RUNNING: 'running' }
+    },
+    runtime: {
+        OnInstalledReason: { CHROME_UPDATE: 'chrome_update', INSTALL: 'install', SHARED_MODULE_UPDATE: 'shared_module_update', UPDATE: 'update' },
+        OnRestartRequiredReason: { APP_UPDATE: 'app_update', OS_UPDATE: 'os_update', PERIODIC: 'periodic' },
+        PlatformArch: { ARM: 'arm', ARM64: 'arm64', MIPS: 'mips', MIPS64: 'mips64', X86_32: 'x86-32', X86_64: 'x86-64' },
+        PlatformNaclArch: { ARM: 'arm', MIPS: 'mips', MIPS64: 'mips64', X86_32: 'x86-32', X86_64: 'x86-64' },
+        PlatformOs: { ANDROID: 'android', CROS: 'cros', LINUX: 'linux', MAC: 'mac', OPENBSD: 'openbsd', WIN: 'win' },
+        RequestUpdateCheckStatus: { NO_UPDATE: 'no_update', THROTTLED: 'throttled', UPDATE_AVAILABLE: 'update_available' },
+        connect: function() { return { onDisconnect: { addListener: function() {} }, onMessage: { addListener: function() {} }, postMessage: function() {} }; },
+        sendMessage: function() {}
+    },
+    csi: function() { return {}; },
+    loadTimes: function() { return {}; }
+};
+
+// 6. 修复 permissions API
+const originalQuery = window.navigator.permissions?.query;
+if (originalQuery) {
+    window.navigator.permissions.query = (parameters) => {
+        if (parameters.name === 'notifications') {
+            return Promise.resolve({ state: Notification.permission });
+        }
+        return originalQuery.call(window.navigator.permissions, parameters);
+    };
+}
+
+// 7. 伪装 WebGL 指纹
+const getParameter = WebGLRenderingContext.prototype.getParameter;
+WebGLRenderingContext.prototype.getParameter = function(parameter) {
+    if (parameter === 37445) {
+        return 'Intel Inc.';
+    }
+    if (parameter === 37446) {
+        return 'Intel Iris OpenGL Engine';
+    }
+    return getParameter.call(this, parameter);
+};
+
+// 8. 隐藏自动化 iframe
+const originalContentWindow = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, 'contentWindow').get;
+Object.defineProperty(HTMLIFrameElement.prototype, 'contentWindow', {
+    get: function() {
+        const window = originalContentWindow.call(this);
+        if (window) {
+            try {
+                Object.defineProperty(window.navigator, 'webdriver', { get: () => undefined });
+            } catch (e) {}
+        }
+        return window;
+    }
+});
+
+// 9. 伪装设备内存
+Object.defineProperty(navigator, 'deviceMemory', {
+    get: () => 8
+});
+
+// 10. 伪装硬件并发数
+Object.defineProperty(navigator, 'hardwareConcurrency', {
+    get: () => 8
+});
+
+// 11. 伪装电池 API
+if (navigator.getBattery) {
+    const originalGetBattery = navigator.getBattery;
+    navigator.getBattery = function() {
+        return Promise.resolve({
+            charging: true,
+            chargingTime: 0,
+            dischargingTime: Infinity,
+            level: 1,
+            addEventListener: function() {},
+            removeEventListener: function() {},
+            dispatchEvent: function() { return true; }
+        });
+    };
+}
+
+// 12. 修复 iframe contentWindow
+const originalAppendChild = Element.prototype.appendChild;
+Element.prototype.appendChild = function(node) {
+    if (node.tagName === 'IFRAME') {
+        try {
+            Object.defineProperty(node, 'setAttribute', {
+                value: function(name, value) {
+                    if (name === 'src' && value && value.includes('charset=utf-8')) {
+                        return;
+                    }
+                    return Element.prototype.setAttribute.call(this, name, value);
+                }
+            });
+        } catch (e) {}
+    }
+    return originalAppendChild.call(this, node);
+};
+
+console.log('[Stealth] Anti-detection scripts loaded');
+"""
+
+# Canvas 指纹噪声脚本
+CANVAS_NOISE_JS = """
+// Canvas 指纹噪声
+const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
+const originalToBlob = HTMLCanvasElement.prototype.toBlob;
+const originalGetImageData = CanvasRenderingContext2D.prototype.getImageData;
+
+// 添加微小噪声
+function addNoise(data) {
+    for (let i = 0; i < data.length; i += 4) {
+        // 对 RGBA 添加微小随机噪声(-1 到 1)
+        data[i] = Math.max(0, Math.min(255, data[i] + Math.floor(Math.random() * 3) - 1));
+        data[i + 1] = Math.max(0, Math.min(255, data[i + 1] + Math.floor(Math.random() * 3) - 1));
+        data[i + 2] = Math.max(0, Math.min(255, data[i + 2] + Math.floor(Math.random() * 3) - 1));
+    }
+    return data;
+}
+
+HTMLCanvasElement.prototype.toDataURL = function(type) {
+    if (this.width > 0 && this.height > 0) {
+        try {
+            const ctx = this.getContext('2d');
+            if (ctx) {
+                const imageData = ctx.getImageData(0, 0, this.width, this.height);
+                addNoise(imageData.data);
+                ctx.putImageData(imageData, 0, 0);
+            }
+        } catch (e) {}
+    }
+    return originalToDataURL.apply(this, arguments);
+};
+
+CanvasRenderingContext2D.prototype.getImageData = function() {
+    const imageData = originalGetImageData.apply(this, arguments);
+    addNoise(imageData.data);
+    return imageData;
+};
+"""
+
+# Audio 指纹噪声脚本
+AUDIO_NOISE_JS = """
+// Audio 指纹噪声
+const originalCreateAnalyser = AudioContext.prototype.createAnalyser;
+const originalGetFloatFrequencyData = AnalyserNode.prototype.getFloatFrequencyData;
+
+AudioContext.prototype.createAnalyser = function() {
+    const analyser = originalCreateAnalyser.call(this);
+    const originalGetFloatFrequencyData = analyser.getFloatFrequencyData.bind(analyser);
+    analyser.getFloatFrequencyData = function(array) {
+        originalGetFloatFrequencyData(array);
+        for (let i = 0; i < array.length; i++) {
+            array[i] += (Math.random() - 0.5) * 0.001;
+        }
+    };
+    return analyser;
+};
+"""
+
+
+def get_user_agent() -> str:
+    """获取随机 User-Agent"""
+    user_agents = [
+        # Chrome on Windows
+        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
+        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
+        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
+        # Chrome on Mac
+        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
+        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
+        # Edge on Windows
+        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0",
+    ]
+    return random.choice(user_agents)
+
+
+def get_viewport() -> Dict[str, int]:
+    """获取随机视口大小"""
+    viewports = [
+        {"width": 1920, "height": 1080},
+        {"width": 1680, "height": 1050},
+        {"width": 1440, "height": 900},
+        {"width": 1536, "height": 864},
+        {"width": 1366, "height": 768},
+        {"width": 2560, "height": 1440},
+    ]
+    return random.choice(viewports)
+
+
+def get_timezone() -> str:
+    """获取时区"""
+    return "Asia/Shanghai"
+
+
+def get_locale() -> str:
+    """获取语言区域"""
+    return "zh-CN"
+
+
+def get_screen_info() -> Dict[str, int]:
+    """获取屏幕信息"""
+    screens = [
+        {"screen_width": 1920, "screen_height": 1080, "device_pixel_ratio": 1},
+        {"screen_width": 2560, "screen_height": 1440, "device_pixel_ratio": 1},
+        {"screen_width": 1680, "screen_height": 1050, "device_pixel_ratio": 1},
+        {"screen_width": 1440, "screen_height": 900, "device_pixel_ratio": 2},  # Retina
+    ]
+    return random.choice(screens)
+
+
+def get_webgl_vendor_renderer() -> Dict[str, str]:
+    """获取 WebGL 供应商和渲染器"""
+    configs = [
+        {"vendor": "Google Inc. (Intel)", "renderer": "ANGLE (Intel, Intel(R) UHD Graphics 630, OpenGL 4.6)"},
+        {"vendor": "Google Inc. (NVIDIA)", "renderer": "ANGLE (NVIDIA, NVIDIA GeForce GTX 1060, OpenGL 4.6)"},
+        {"vendor": "Google Inc. (AMD)", "renderer": "ANGLE (AMD, AMD Radeon RX 580, OpenGL 4.6)"},
+        {"vendor": "Intel Inc.", "renderer": "Intel Iris OpenGL Engine"},
+    ]
+    return random.choice(configs)
+
+
+def get_browser_context_args(
+    proxy_config: Optional[Dict[str, Any]] = None,
+    user_agent: Optional[str] = None,
+    viewport: Optional[Dict[str, int]] = None,
+) -> Dict[str, Any]:
+    """
+    生成浏览器上下文参数
+    
+    Args:
+        proxy_config: 代理配置 {"server": "http://ip:port"}
+        user_agent: 自定义 User-Agent
+        viewport: 视口大小 {"width": 1920, "height": 1080}
+    
+    Returns:
+        浏览器上下文参数字典
+    """
+    screen_info = get_screen_info()
+    
+    args = {
+        "user_agent": user_agent or get_user_agent(),
+        "viewport": viewport or get_viewport(),
+        "locale": get_locale(),
+        "timezone_id": get_timezone(),
+        "geolocation": {"latitude": 31.2304, "longitude": 121.4737},  # 上海
+        "permissions": ["geolocation"],
+        "color_scheme": "light",
+        "device_scale_factor": screen_info.get("device_pixel_ratio", 1),
+        "has_touch": False,
+        "is_mobile": False,
+        "java_script_enabled": True,
+        "ignore_https_errors": True,
+    }
+    
+    if proxy_config and proxy_config.get("server"):
+        args["proxy"] = proxy_config
+    
+    return args
+
+
+def get_stealth_scripts() -> str:
+    """获取所有反检测脚本"""
+    return f"""
+{STEALTH_JS}
+
+{CANVAS_NOISE_JS}
+
+{AUDIO_NOISE_JS}
+"""
+
+
+def get_webgl_override_script() -> str:
+    """获取 WebGL 指纹覆盖脚本"""
+    webgl_config = get_webgl_vendor_renderer()
+    return f"""
+// WebGL 指纹覆盖
+const getParameter = WebGLRenderingContext.prototype.getParameter;
+WebGLRenderingContext.prototype.getParameter = function(parameter) {{
+    // UNMASKED_VENDOR_WEBGL
+    if (parameter === 37445) {{
+        return '{webgl_config["vendor"]}';
+    }}
+    // UNMASKED_RENDERER_WEBGL
+    if (parameter === 37446) {{
+        return '{webgl_config["renderer"]}';
+    }}
+    return getParameter.call(this, parameter);
+}};
+
+// WebGL2 支持同样需要覆盖
+if (typeof WebGL2RenderingContext !== 'undefined') {{
+    const getParameter2 = WebGL2RenderingContext.prototype.getParameter;
+    WebGL2RenderingContext.prototype.getParameter = function(parameter) {{
+        if (parameter === 37445) {{
+            return '{webgl_config["vendor"]}';
+        }}
+        if (parameter === 37446) {{
+            return '{webgl_config["renderer"]}';
+        }}
+        return getParameter2.call(this, parameter);
+    }};
+}}
+"""
+
+
+def get_navigator_override_script() -> str:
+    """获取 Navigator 属性覆盖脚本"""
+    screen_info = get_screen_info()
+    return f"""
+// Navigator 属性覆盖
+Object.defineProperty(screen, 'width', {{
+    get: () => {screen_info['screen_width']}
+}});
+Object.defineProperty(screen, 'height', {{
+    get: () => {screen_info['screen_height']}
+}});
+Object.defineProperty(screen, 'availWidth', {{
+    get: () => {screen_info['screen_width']}
+}});
+Object.defineProperty(screen, 'availHeight', {{
+    get: () => {screen_info['screen_height'] - 40}  // 减去任务栏高度
+}});
+Object.defineProperty(window, 'devicePixelRatio', {{
+    get: () => {screen_info.get('device_pixel_ratio', 1)}
+}});
+"""
+
+
+def get_all_stealth_scripts() -> str:
+    """获取完整的反检测脚本组合"""
+    return f"""
+{get_stealth_scripts()}
+
+{get_webgl_override_script()}
+
+{get_navigator_override_script()}
+
+// 最终确认
+console.log('[Stealth] All anti-detection scripts loaded successfully');
+"""
+
+
+async def inject_stealth_scripts(page) -> None:
+    """
+    向页面注入反检测脚本
+    
+    Args:
+        page: Playwright page 对象
+    """
+    stealth_script = get_all_stealth_scripts()
+    
+    # 在页面加载前注入
+    await page.add_init_script(stealth_script)
+    
+    # 如果页面已经加载,立即执行
+    try:
+        await page.evaluate(stealth_script)
+    except Exception as e:
+        # 页面可能还未完全加载,忽略错误
+        pass
+
+
+async def human_like_delay(min_ms: int = 100, max_ms: int = 500) -> None:
+    """
+    模拟人类操作的延迟
+    
+    Args:
+        min_ms: 最小延迟毫秒数
+        max_ms: 最大延迟毫秒数
+    """
+    delay = random.randint(min_ms, max_ms) / 1000.0
+    time.sleep(delay)
+
+
+async def human_like_type(page, selector: str, text: str, delay_range: tuple = (50, 150)) -> None:
+    """
+    模拟人类输入
+    
+    Args:
+        page: Playwright page 对象
+        selector: 选择器
+        text: 要输入的文本
+        delay_range: 延迟范围(毫秒)
+    """
+    element = page.locator(selector)
+    await element.click()
+    
+    for char in text:
+        await element.press(char)
+        delay = random.randint(delay_range[0], delay_range[1]) / 1000.0
+        time.sleep(delay)
+
+
+async def human_like_scroll(page, distance: int = None) -> None:
+    """
+    模拟人类滚动
+    
+    Args:
+        page: Playwright page 对象
+        distance: 滚动距离(像素),默认随机
+    """
+    if distance is None:
+        distance = random.randint(200, 500)
+    
+    # 分多次滚动,模拟真实行为
+    steps = random.randint(3, 6)
+    step_distance = distance // steps
+    
+    for _ in range(steps):
+        await page.evaluate(f"window.scrollBy(0, {step_distance})")
+        await human_like_delay(100, 300)

+ 39 - 0
server/temp_query.cjs

@@ -0,0 +1,39 @@
+const mysql = require('mysql2/promise');
+
+async function main() {
+  const conn = await mysql.createConnection({
+    host: '8.136.223.156',
+    port: 6630,
+    user: 'media_manager',
+    password: 'media_manager',
+    database: 'media_manager'
+  });
+  
+  // 查询发布结果
+  const [results] = await conn.query(`
+    SELECT *
+    FROM publish_results
+    WHERE task_id = 61
+    LIMIT 3
+  `);
+  console.log('\n=== Task 61 Results ===');
+  console.log(JSON.stringify(results, null, 2));
+  
+  // 查询账号信息
+  const [accounts] = await conn.query(`
+    SELECT 
+      id,
+      platform,
+      account_name,
+      account_id
+    FROM platform_accounts
+    WHERE id = 24
+  `);
+  console.log('\n=== Account 24 ===');
+  console.log(JSON.stringify(accounts, null, 2));
+  
+  console.log(JSON.stringify(rows, null, 2));
+  await conn.end();
+}
+
+main().catch(console.error);

+ 97 - 0
server/test_direct_publish.cjs

@@ -0,0 +1,97 @@
+#!/usr/bin/env node
+/**
+ * 测试直连模式发布 - Task #61
+ * 不使用代理,配合反检测脚本
+ */
+
+const http = require('http');
+const fs = require('fs');
+
+const PYTHON_API = 'http://localhost:5005';
+const TASK_ID = 61;
+
+const PUBLISH_DATA = {
+  platform: 'weixin',
+  cookie: JSON.stringify([
+    {
+      "name":"sessionid",
+      "value":"BgAAXERdxFp7jCFlOv26QpoGfHFTWafeI1UHi%2F6RxenyzR8zhCfVtyOkJFAw4UJD2yzCpzRQ3phKnIIRiNDY2eM6sUHMCtSAdlG4%2FEqu89Qb",
+      "domain":"channels.weixin.qq.com",
+      "path":"/",
+      "expires":1806737620.630474,
+      "httpOnly":false,
+      "secure":true,
+      "sameSite":"no_restriction"
+    },
+    {
+      "name":"wxuin",
+      "value":"197897203",
+      "domain":"channels.weixin.qq.com",
+      "path":"/",
+      "expires":1806737620.630622,
+      "httpOnly":false,
+      "secure":true,
+      "sameSite":"no_restriction"
+    }
+  ]),
+  title: '拍照机',
+  description: '拍照机',
+  video_path: 'E:\\Workspace\\multi-platform-media-manage\\server\\uploads\\videos\\88ce3597-0499-4882-bcb5-b6d0722547af.mp4',
+  tags: [],
+  headless: false,  // 显示浏览器
+  return_screenshot: true,
+  user_id: 1,
+  publish_task_id: TASK_ID,
+  publish_account_id: 24,
+  proxy: {
+    enabled: false  // ⚠️ 关键:不使用代理,避免超时
+  }
+};
+
+console.log('🎯 测试直连模式发布 - Task #' + TASK_ID);
+console.log('='.repeat(60));
+console.log('策略: 使用直连 + 反检测脚本');
+console.log('原因: 神龙代理 IP 有效期仅 3 分钟,大文件上传会超时');
+console.log('='.repeat(60));
+
+const postData = JSON.stringify(PUBLISH_DATA);
+
+const req = http.request(`${PYTHON_API}/publish/ai-assisted`, {
+  method: 'POST',
+  headers: {
+    'Content-Type': 'application/json',
+    'Content-Length': Buffer.byteLength(postData)
+  },
+  timeout: 600000  // 10 分钟超时(大文件)
+}, (res) => {
+  let data = '';
+  res.on('data', chunk => data += chunk);
+  res.on('end', () => {
+    try {
+      const result = JSON.parse(data);
+      console.log('\n📊 发布结果:');
+      console.log(JSON.stringify(result, null, 2));
+      
+      if (result.screenshot_base64) {
+        const screenshotPath = `E:\\Workspace\\multi-platform-media-manage\\server\\uploads\\screenshots\\direct_publish_${TASK_ID}_${Date.now()}.png`;
+        fs.writeFileSync(screenshotPath, Buffer.from(result.screenshot_base64, 'base64'));
+        console.log('\n📷 截图已保存:', screenshotPath);
+      }
+    } catch (e) {
+      console.error('❌ 解析失败:', e);
+      console.log('响应:', data.substring(0, 500));
+    }
+  });
+});
+
+req.on('error', (e) => console.error('❌ 请求失败:', e));
+req.on('timeout', () => {
+  console.error('❌ 请求超时(10分钟)');
+  req.destroy();
+});
+
+req.write(postData);
+req.end();
+
+console.log('\n⏳ 发布中...(大文件上传需要较长时间)');
+console.log('💡 可以在浏览器窗口观察进度');

+ 172 - 0
server/test_proxy_weixin.cjs

@@ -0,0 +1,172 @@
+const http = require('http');
+const https = require('https');
+
+// 神龙代理配置
+const SHENLONG_CONFIG = {
+  productKey: 'o2ihwv3e',
+  signature: 'ccff437b0c211d7a758b0926cbdcf958',
+  regionCode: '310000',  // 上海
+  platform: 'weixin'
+};
+
+// 测试代理
+function testProxy() {
+  return new Promise((resolve, reject) => {
+    console.log('\n🔍 测试神龙代理对微信视频号的可用性...');
+    console.log('配置:', JSON.stringify(SHENLONG_CONFIG, null, 2));
+    
+    const postData = JSON.stringify(SHENLONG_CONFIG);
+    
+    const req = http.request('http://127.0.0.1:5005/proxy/test', {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json',
+        'Content-Length': Buffer.byteLength(postData)
+      },
+      timeout: 30000
+    }, (res) => {
+      let data = '';
+      res.on('data', chunk => data += chunk);
+      res.on('end', () => {
+        try {
+          const result = JSON.parse(data);
+          console.log('\n✅ 代理测试结果:');
+          console.log(JSON.stringify(result, null, 2));
+          resolve(result);
+        } catch (e) {
+          console.error('❌ 解析响应失败:', e);
+          reject(e);
+        }
+      });
+    });
+    
+    req.on('error', (e) => {
+      console.error('❌ 请求失败:', e.message);
+      console.log('\n💡 Python 服务未启动,让我直接测试神龙 API...');
+      testShenlongAPI().then(resolve).catch(reject);
+    });
+    
+    req.on('timeout', () => {
+      console.error('❌ 请求超时');
+      req.destroy();
+      testShenlongAPI().then(resolve).catch(reject);
+    });
+    
+    req.write(postData);
+    req.end();
+  });
+}
+
+// 直接测试神龙 API
+async function testShenlongAPI() {
+  return new Promise((resolve, reject) => {
+    console.log('\n🔍 直接测试神龙代理 API...');
+    
+    const params = new URLSearchParams({
+      key: SHENLONG_CONFIG.productKey,
+      sign: SHENLONG_CONFIG.signature,
+      count: 1,
+      pattern: 'json',
+      mr: 1,
+      area: SHENLONG_CONFIG.regionCode
+    });
+    
+    const url = `http://api.shenlongip.com/ip?${params.toString()}`;
+    console.log('API URL:', url);
+    
+    http.get(url, {
+      headers: {
+        'User-Agent': 'Mozilla/5.0',
+        'Accept': 'application/json'
+      },
+      timeout: 15000
+    }, (res) => {
+      let data = '';
+      res.on('data', chunk => data += chunk);
+      res.on('end', () => {
+        console.log('\n神龙 API 响应:');
+        try {
+          const result = JSON.parse(data);
+          console.log(JSON.stringify(result, null, 2));
+          
+          if (result.code === 200 && result.data && result.data.length > 0) {
+            const proxy = result.data[0];
+            console.log(`\n✅ 获取到代理IP: ${proxy.ip}:${proxy.port}`);
+            console.log(`   城市: ${proxy.city}`);
+            console.log(`   运营商: ${proxy.isp}`);
+            
+            // 测试代理连通性
+            testProxyConnectivity(proxy.ip, proxy.port).then(() => {
+              resolve(result);
+            }).catch(reject);
+          } else {
+            console.log('\n❌ 获取代理失败:', result.msg || '未知错误');
+            resolve(result);
+          }
+        } catch (e) {
+          console.log('原始响应:', data);
+          resolve({ raw: data });
+        }
+      });
+    }).on('error', (e) => {
+      console.error('❌ 神龙 API 请求失败:', e.message);
+      reject(e);
+    });
+  });
+}
+
+// 测试代理连通性
+function testProxyConnectivity(host, port) {
+  return new Promise((resolve, reject) => {
+    console.log(`\n🔍 测试代理连通性: ${host}:${port}...`);
+    
+    const options = {
+      hostname: host,
+      port: port,
+      path: 'http://myip.ipip.net',
+      method: 'GET',
+      timeout: 10000
+    };
+    
+    const req = http.request(options, (res) => {
+      let data = '';
+      res.on('data', chunk => data += chunk);
+      res.on('end', () => {
+        console.log('\n✅ 代理连通性测试成功');
+        console.log('IP信息:', data.trim());
+        resolve();
+      });
+    });
+    
+    req.on('error', (e) => {
+      console.error('❌ 代理连通性测试失败:', e.message);
+      resolve(); // 不reject,继续
+    });
+    
+    req.on('timeout', () => {
+      console.error('❌ 代理连接超时');
+      req.destroy();
+      resolve();
+    });
+    
+    req.end();
+  });
+}
+
+// 主函数
+async function main() {
+  try {
+    console.log('🚀 开始测试 Task #61 的代理配置');
+    console.log('=' .repeat(60));
+    
+    await testProxy();
+    
+    console.log('\n' + '=' .repeat(60));
+    console.log('✅ 测试完成');
+    
+  } catch (error) {
+    console.error('\n❌ 测试失败:', error);
+  }
+}
+
+main();

+ 173 - 0
server/test_publish_task61.cjs

@@ -0,0 +1,173 @@
+#!/usr/bin/env node
+/**
+ * 完整的发布测试 - Task #61
+ * 使用增强的反检测功能和代理
+ */
+
+const http = require('http');
+const fs = require('fs');
+const path = require('path');
+
+// 配置
+const PYTHON_API = 'http://localhost:5005';
+const TASK_ID = 61;
+
+// Task #61 的完整数据
+const PUBLISH_DATA = {
+  platform: 'weixin',  // 微信视频号
+  cookie: JSON.stringify([
+    {
+      "name":"sessionid",
+      "value":"BgAAXERdxFp7jCFlOv26QpoGfHFTWafeI1UHi%2F6RxenyzR8zhCfVtyOkJFAw4UJD2yzCpzRQ3phKnIIRiNDY2eM6sUHMCtSAdlG4%2FEqu89Qb",
+      "domain":"channels.weixin.qq.com",
+      "path":"/",
+      "expires":1806737620.630474,
+      "httpOnly":false,
+      "secure":true,
+      "sameSite":"no_restriction"
+    },
+    {
+      "name":"wxuin",
+      "value":"197897203",
+      "domain":"channels.weixin.qq.com",
+      "path":"/",
+      "expires":1806737620.630622,
+      "httpOnly":false,
+      "secure":true,
+      "sameSite":"no_restriction"
+    }
+  ]),
+  title: '拍照机',
+  description: '拍照机',
+  video_path: 'E:\\Workspace\\multi-platform-media-manage\\server\\uploads\\videos\\88ce3597-0499-4882-bcb5-b6d0722547af.mp4',
+  tags: [],
+  headless: true,  // 改回无头模式
+  return_screenshot: true,
+  user_id: 1,
+  publish_task_id: TASK_ID,
+  publish_account_id: 24,
+  proxy: {
+    enabled: true,
+    provider: 'shenlong',
+    productKey: 'o2ihwv3e',
+    signature: 'ccff437b0c211d7a758b0926cbdcf958',
+    regionCode: '310000',  // 上海
+    city: '上海'
+  }
+};
+
+// 检查视频文件
+function checkVideoFile() {
+  const videoPath = PUBLISH_DATA.video_path;
+  console.log('\n📹 检查视频文件...');
+  console.log('路径:', videoPath);
+  
+  if (!fs.existsSync(videoPath)) {
+    console.error('❌ 视频文件不存在');
+    return false;
+  }
+  
+  const stats = fs.statSync(videoPath);
+  console.log('✅ 文件大小:', (stats.size / 1024 / 1024).toFixed(2), 'MB');
+  return true;
+}
+
+// 发布
+function publish() {
+  return new Promise((resolve, reject) => {
+    console.log('\n🚀 开始发布...');
+    console.log('平台:', PUBLISH_DATA.platform);
+    console.log('标题:', PUBLISH_DATA.title);
+    console.log('代理: 启用 (上海地区)');
+    console.log('Headless:', PUBLISH_DATA.headless);
+    
+    const postData = JSON.stringify(PUBLISH_DATA);
+    
+    const req = http.request(`${PYTHON_API}/publish/ai-assisted`, {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json',
+        'Content-Length': Buffer.byteLength(postData)
+      },
+      timeout: 180000  // 3分钟超时
+    }, (res) => {
+      let data = '';
+      res.on('data', chunk => data += chunk);
+      res.on('end', () => {
+        try {
+          const result = JSON.parse(data);
+          console.log('\n📊 发布结果:');
+          console.log('成功:', result.success);
+          console.log('状态:', result.status);
+          console.log('错误:', result.error || '无');
+          console.log('需要验证码:', result.need_captcha || false);
+          console.log('验证码类型:', result.captcha_type || '无');
+          
+          if (result.screenshot_base64) {
+            console.log('截图: 已获取 (长度:', result.screenshot_base64.length, ')');
+            // 保存截图
+            const screenshotPath = `E:\\Workspace\\multi-platform-media-manage\\uploads\\screenshots\\test_task_${TASK_ID}_${Date.now()}.png`;
+            fs.writeFileSync(screenshotPath, Buffer.from(result.screenshot_base64, 'base64'));
+            console.log('截图已保存:', screenshotPath);
+          }
+          
+          if (result.video_url) {
+            console.log('视频链接:', result.video_url);
+          }
+          
+          resolve(result);
+        } catch (e) {
+          console.error('❌ 解析响应失败:', e);
+          console.log('原始响应:', data.substring(0, 500));
+          reject(e);
+        }
+      });
+    });
+    
+    req.on('error', (e) => {
+      console.error('❌ 请求失败:', e.message);
+      reject(e);
+    });
+    
+    req.on('timeout', () => {
+      console.error('❌ 请求超时');
+      req.destroy();
+      reject(new Error('Timeout'));
+    });
+    
+    req.write(postData);
+    req.end();
+  });
+}
+
+// 主函数
+async function main() {
+  try {
+    console.log('🎯 完整发布测试 - Task #' + TASK_ID);
+    console.log('='.repeat(60));
+    
+    // 1. 检查视频文件
+    if (!checkVideoFile()) {
+      return;
+    }
+    
+    // 2. 发布
+    const result = await publish();
+    
+    console.log('\n' + '='.repeat(60));
+    if (result.success) {
+      console.log('✅ 发布成功!');
+    } else if (result.need_captcha) {
+      console.log('⚠️  需要验证码:', result.captcha_type);
+      console.log('💡 建议: 在有头浏览器模式下处理验证码');
+    } else {
+      console.log('❌ 发布失败');
+      console.log('💡 建议: 检查 Cookie 是否有效,或查看截图分析原因');
+    }
+    
+  } catch (error) {
+    console.error('\n❌ 测试失败:', error);
+  }
+}
+
+main();

+ 174 - 0
server/test_task_61.js

@@ -0,0 +1,174 @@
+#!/usr/bin/env node
+/**
+ * 测试发布任务 #61
+ * - 测试代理对微信视频号的可用性
+ * - 测试账号 Cookie 有效性
+ */
+
+const http = require('http');
+
+// 配置
+const PYTHON_API = 'http://localhost:5005';
+const TASK_ID = 61;
+const ACCOUNT_ID = 24;
+
+// 从数据库获取的代理配置
+const PROXY_CONFIG = {
+  city: "上海",
+  enabled: true,
+  provider: "shenlong",
+  regionCode: "310000",
+  regionName: "市辖区",
+  regionPath: ["31", "310000"]
+};
+
+// 测试代理
+async function testProxy() {
+  return new Promise((resolve, reject) => {
+    console.log('\n=== 1️⃣ 测试代理配置 ===');
+    console.log('代理配置:', JSON.stringify(PROXY_CONFIG, null, 2));
+    
+    const postData = JSON.stringify({
+      ...PROXY_CONFIG,
+      platform: 'weixin'  // 测试对微信平台的可用性
+    });
+    
+    const req = http.request(`${PYTHON_API}/proxy/test`, {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json',
+        'Content-Length': Buffer.byteLength(postData)
+      }
+    }, (res) => {
+      let data = '';
+      res.on('data', chunk => data += chunk);
+      res.on('end', () => {
+        try {
+          const result = JSON.parse(data);
+          console.log('\n代理测试结果:', JSON.stringify(result, null, 2));
+          resolve(result);
+        } catch (e) {
+          reject(e);
+        }
+      });
+    });
+    
+    req.on('error', (e) => {
+      console.error('❌ 代理测试失败:', e.message);
+      reject(e);
+    });
+    
+    req.write(postData);
+    req.end();
+  });
+}
+
+// 测试发布(带代理)
+async function testPublish() {
+  return new Promise((resolve, reject) => {
+    console.log('\n=== 2️⃣ 测试发布 ===');
+    console.log('任务ID:', TASK_ID);
+    console.log('账号ID:', ACCOUNT_ID);
+    
+    // 从 Node.js API 获取完整任务信息
+    http.get(`http://localhost:3000/api/publish/tasks/${TASK_ID}`, (res) => {
+      let data = '';
+      res.on('data', chunk => data += chunk);
+      res.on('end', async () => {
+        try {
+          const taskData = JSON.parse(data);
+          if (!taskData.success) {
+            throw new Error(taskData.error || '获取任务失败');
+          }
+          
+          console.log('\n任务信息:', JSON.stringify({
+            id: taskData.data.id,
+            title: taskData.data.title,
+            status: taskData.data.status,
+            video_path: taskData.data.video_path,
+            proxy: taskData.data.publish_proxy
+          }, null, 2));
+          
+          // 调用 Python AI 发布接口
+          console.log('\n开始测试发布...');
+          const publishData = JSON.stringify({
+            platform: 'weixin',
+            cookie: '', // 需要从数据库获取
+            title: taskData.data.title,
+            description: taskData.data.description,
+            video_path: `E:\\Workspace\\multi-platform-media-manage${taskData.data.video_path}`,
+            headless: false,  // 显示浏览器
+            return_screenshot: true,
+            proxy: taskData.data.publish_proxy,
+            user_id: taskData.data.user_id,
+            publish_task_id: taskData.data.id,
+            publish_account_id: ACCOUNT_ID
+          });
+          
+          const publishReq = http.request(`${PYTHON_API}/publish/ai-assisted`, {
+            method: 'POST',
+            headers: {
+              'Content-Type': 'application/json',
+              'Content-Length': Buffer.byteLength(publishData)
+            }
+          }, (publishRes) => {
+            let publishResult = '';
+            publishRes.on('data', chunk => publishResult += chunk);
+            publishRes.on('end', () => {
+              try {
+                const result = JSON.parse(publishResult);
+                console.log('\n发布结果:', JSON.stringify({
+                  success: result.success,
+                  status: result.status,
+                  error: result.error,
+                  need_captcha: result.need_captcha,
+                  captcha_type: result.captcha_type
+                }, null, 2));
+                resolve(result);
+              } catch (e) {
+                reject(e);
+              }
+            });
+          });
+          
+          publishReq.on('error', (e) => {
+            console.error('❌ 发布请求失败:', e.message);
+            reject(e);
+          });
+          
+          publishReq.write(publishData);
+          publishReq.end();
+          
+        } catch (e) {
+          reject(e);
+        }
+      });
+    }).on('error', reject);
+  });
+}
+
+// 主函数
+async function main() {
+  try {
+    console.log('🔍 开始测试发布任务 #' + TASK_ID);
+    console.log('=' .repeat(60));
+    
+    // 1. 测试代理
+    const proxyResult = await testProxy();
+    
+    if (!proxyResult.success) {
+      console.log('\n❌ 代理测试失败,请检查代理配置');
+      return;
+    }
+    
+    // 2. 测试发布
+    // await testPublish();  // 暂时跳过,需要获取 Cookie
+    
+    console.log('\n✅ 测试完成');
+    
+  } catch (error) {
+    console.error('\n❌ 测试失败:', error);
+  }
+}
+
+main();

+ 198 - 0
server/test_task_full.js

@@ -0,0 +1,198 @@
+#!/usr/bin/env node
+/**
+ * 完整测试脚本 - Task #61
+ * 1. 启动 Python 服务(如果未运行)
+ * 2. 测试代理(带认证)
+ * 3. 测试账号 Cookie
+ * 4. 尝试发布(使用反检测)
+ */
+
+import { spawn } from 'child_process';
+import http from 'http';
+import mysql from 'mysql2/promise.js';
+
+const PYTHON_PORT = 5005;
+const NODE_PORT = 3000;
+const TASK_ID = 61;
+const ACCOUNT_ID = 24;
+
+// 检查服务状态
+function checkService(port) {
+  return new Promise((resolve) => {
+    const req = http.get(`http://localhost:${port}`, { timeout: 2000 }, () => {
+      resolve(true);
+    });
+    req.on('error', () => resolve(false));
+    req.on('timeout', () => {
+      req.destroy();
+      resolve(false);
+    });
+  });
+}
+
+// 启动 Python 服务
+async function startPythonService() {
+  console.log('\n🔄 检查 Python 服务...');
+  const running = await checkService(PYTHON_PORT);
+  
+  if (running) {
+    console.log('✅ Python 服务已运行');
+    return true;
+  }
+  
+  console.log('⚠️  Python 服务未运行');
+  console.log('💡 请手动启动 Python 服务:');
+  console.log('   cd E:\\Workspace\\multi-platform-media-manage\\server\\python');
+  console.log('   python app.py --headless false');
+  
+  return false;
+}
+
+// 测试代理(带认证)
+async function testProxyWithAuth() {
+  const conn = await mysql.createConnection({
+    host: '8.136.223.156',
+    port: 6630,
+    user: 'media_manager',
+    password: 'media_manager',
+    database: 'media_manager'
+  });
+  
+  const [configs] = await conn.query(`
+    SELECT config_key, config_value
+    FROM system_config
+    WHERE config_key IN (
+      'publish_proxy_shenlong_product_key',
+      'publish_proxy_shenlong_signature',
+      'publish_proxy_shenlong_username',
+      'publish_proxy_shenlong_password'
+    )
+  `);
+  
+  await conn.end();
+  
+  const config = {};
+  configs.forEach(c => {
+    config[c.config_key.replace('publish_proxy_shenlong_', '')] = c.config_value;
+  });
+  
+  console.log('\n=== 神龙代理配置 ===');
+  console.log('Product Key:', config.product_key);
+  console.log('Signature:', config.signature);
+  console.log('Username:', config.username);
+  console.log('Password:', config.password.substring(0, 20) + '...');
+  
+  // 测试代理
+  return new Promise((resolve) => {
+    const postData = JSON.stringify({
+      provider: 'shenlong',
+      productKey: config.product_key,
+      signature: config.signature,
+      regionCode: '310000',
+      platform: 'weixin',
+      username: config.username,
+      password: config.password
+    });
+    
+    const req = http.request('http://localhost:5005/proxy/test', {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json',
+        'Content-Length': Buffer.byteLength(postData)
+      },
+      timeout: 30000
+    }, (res) => {
+      let data = '';
+      res.on('data', chunk => data += chunk);
+      res.on('end', () => {
+        try {
+          const result = JSON.parse(data);
+          console.log('\n=== 代理测试结果 ===');
+          console.log(JSON.stringify(result, null, 2));
+          resolve(result);
+        } catch (e) {
+          console.error('解析失败:', e);
+          resolve(null);
+        }
+      });
+    });
+    
+    req.on('error', (e) => {
+      console.log('\n❌ 代理测试失败:', e.message);
+      console.log('💡 Python 服务未启动,无法测试');
+      resolve(null);
+    });
+    
+    req.write(postData);
+    req.end();
+  });
+}
+
+// 获取账号 Cookie
+async function getAccountCookie(accountId) {
+  const conn = await mysql.createConnection({
+    host: '8.136.223.156',
+    port: 6630,
+    user: 'media_manager',
+    password: 'media_manager',
+    database: 'media_manager'
+  });
+  
+  const [accounts] = await conn.query(`
+    SELECT id, platform, account_name, cookies
+    FROM platform_accounts
+    WHERE id = ?
+  `, [accountId]);
+  
+  await conn.end();
+  
+  if (accounts.length === 0) {
+    console.log('\n❌ 未找到账号 ID:', accountId);
+    return null;
+  }
+  
+  const account = accounts[0];
+  console.log('\n=== 发布账号 ===');
+  console.log('ID:', account.id);
+  console.log('平台:', account.platform);
+  console.log('账号名:', account.account_name);
+  console.log('Cookie 长度:', account.cookies ? account.cookies.length : 0);
+  
+  return account;
+}
+
+// 主函数
+async function main() {
+  console.log('🚀 完整测试 - Task #' + TASK_ID);
+  console.log('='.repeat(60));
+  
+  // 1. 检查/启动服务
+  await startPythonService();
+  
+  // 2. 测试代理
+  const proxyResult = await testProxyWithAuth();
+  
+  // 3. 获取账号信息
+  const account = await getAccountCookie(ACCOUNT_ID);
+  
+  console.log('\n' + '='.repeat(60));
+  console.log('📋 测试总结:');
+  console.log('  - 代理API: ✅ 可用');
+  console.log(`  - 代理连通性: ${proxyResult ? '✅ 成功' : '❌ 需要启动 Python 服务'}`);
+  console.log(`  - 账号Cookie: ${account && account.cookies ? '✅ 有效' : '❌ 无效/过期'}`);
+  
+  console.log('\n💡 建议的下一步:');
+  if (!proxyResult) {
+    console.log('  1. 启动 Python 服务:');
+    console.log('     cd E:\\Workspace\\multi-platform-media-manage\\server\\python');
+    console.log('     python app.py --headless false');
+  }
+  console.log('  2. 使用 AI 辅助发布接口测试:');
+  console.log('     POST http://localhost:5005/publish/ai-assisted');
+  console.log('     设置 headless: false 查看浏览器行为');
+  console.log('  3. 如果提示需要登录,使用有头浏览器手动登录');
+  
+  console.log('\n✅ 测试完成\n');
+}
+
+main().catch(console.error);