522 lines
33 KiB
HTML
522 lines
33 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>微信全链路控制面板 — 工作手机SDK</title>
|
||
<style>
|
||
*{margin:0;padding:0;box-sizing:border-box}
|
||
:root{--bg:#0a0e17;--card:#131a2b;--border:#1e2940;--accent:#3b82f6;--accent2:#22c55e;--accent3:#f59e0b;--text:#e2e8f0;--text2:#94a3b8;--danger:#ef4444;--glass:rgba(30,41,64,0.7)}
|
||
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:var(--bg);color:var(--text);min-height:100vh}
|
||
.app{display:flex;height:100vh}
|
||
.sidebar{width:220px;background:var(--card);border-right:1px solid var(--border);overflow-y:auto;flex-shrink:0}
|
||
.sidebar h2{padding:16px;font-size:14px;color:var(--accent);border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px}
|
||
.nav-group{padding:4px 0}
|
||
.nav-group-title{padding:8px 16px;font-size:11px;color:var(--text2);text-transform:uppercase;letter-spacing:1px}
|
||
.nav-item{padding:8px 16px 8px 24px;cursor:pointer;font-size:13px;color:var(--text2);transition:all .15s;display:flex;align-items:center;gap:6px}
|
||
.nav-item:hover{background:rgba(59,130,246,0.1);color:var(--text)}
|
||
.nav-item.active{background:rgba(59,130,246,0.15);color:var(--accent);border-right:2px solid var(--accent)}
|
||
.nav-item .count{margin-left:auto;font-size:10px;background:var(--border);padding:1px 6px;border-radius:8px}
|
||
.main{flex:1;display:flex;flex-direction:column;overflow:hidden}
|
||
.header{padding:12px 20px;background:var(--card);border-bottom:1px solid var(--border);display:flex;align-items:center;gap:16px}
|
||
.header select{background:var(--bg);color:var(--text);border:1px solid var(--border);padding:6px 12px;border-radius:6px;font-size:13px}
|
||
.status-pill{padding:4px 12px;border-radius:12px;font-size:11px;font-weight:600}
|
||
.status-online{background:rgba(34,197,94,0.15);color:var(--accent2)}
|
||
.status-offline{background:rgba(239,68,68,0.15);color:var(--danger)}
|
||
.channel-badge{padding:3px 8px;border-radius:4px;font-size:10px;font-weight:600;letter-spacing:.5px}
|
||
.ch-hook{background:rgba(168,85,247,0.2);color:#a855f7}
|
||
.ch-u2{background:rgba(59,130,246,0.2);color:#3b82f6}
|
||
.ch-adb{background:rgba(245,158,11,0.2);color:#f59e0b}
|
||
.content{flex:1;display:flex;overflow:hidden}
|
||
.panel{flex:1;overflow-y:auto;padding:20px}
|
||
.right-panel{width:360px;border-left:1px solid var(--border);overflow-y:auto;display:flex;flex-direction:column}
|
||
.section{margin-bottom:20px}
|
||
.section-title{font-size:13px;font-weight:600;color:var(--accent);margin-bottom:10px;display:flex;align-items:center;gap:6px}
|
||
.card{background:var(--glass);border:1px solid var(--border);border-radius:8px;padding:16px;margin-bottom:12px}
|
||
.form-group{margin-bottom:12px}
|
||
.form-group label{display:block;font-size:12px;color:var(--text2);margin-bottom:4px}
|
||
.form-group input,.form-group textarea,.form-group select{width:100%;background:var(--bg);color:var(--text);border:1px solid var(--border);padding:8px 12px;border-radius:6px;font-size:13px}
|
||
.form-group textarea{resize:vertical;min-height:60px}
|
||
.btn{padding:8px 16px;border:none;border-radius:6px;font-size:13px;cursor:pointer;font-weight:500;transition:all .15s}
|
||
.btn-primary{background:var(--accent);color:#fff}
|
||
.btn-primary:hover{background:#2563eb}
|
||
.btn-success{background:var(--accent2);color:#fff}
|
||
.btn-danger{background:var(--danger);color:#fff}
|
||
.btn-sm{padding:5px 10px;font-size:11px}
|
||
.btn-group{display:flex;gap:8px;flex-wrap:wrap}
|
||
.result-box{background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:12px;font-family:monospace;font-size:12px;max-height:300px;overflow-y:auto;white-space:pre-wrap;word-break:break-all}
|
||
.chain-step{display:flex;align-items:center;gap:8px;padding:6px 0;font-size:12px}
|
||
.chain-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
|
||
.chain-dot.active{background:var(--accent2);box-shadow:0 0 6px var(--accent2)}
|
||
.chain-dot.pending{background:var(--text2)}
|
||
.chain-line{width:1px;height:16px;background:var(--border);margin-left:3.5px}
|
||
.log-entry{padding:4px 0;font-size:11px;color:var(--text2);border-bottom:1px solid rgba(30,41,64,0.5)}
|
||
.log-entry .time{color:var(--accent);margin-right:6px}
|
||
.log-entry .action{color:var(--accent3)}
|
||
.log-entry .ok{color:var(--accent2)}
|
||
.log-entry .fail{color:var(--danger)}
|
||
.stats-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}
|
||
.stat-card{background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:10px;text-align:center}
|
||
.stat-card .val{font-size:20px;font-weight:700;color:var(--accent)}
|
||
.stat-card .lbl{font-size:10px;color:var(--text2);margin-top:2px}
|
||
.api-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:6px}
|
||
.api-btn{background:var(--bg);border:1px solid var(--border);border-radius:4px;padding:6px 8px;font-size:11px;cursor:pointer;text-align:left;color:var(--text2);transition:all .15s}
|
||
.api-btn:hover{border-color:var(--accent);color:var(--text)}
|
||
.api-btn .method{color:var(--accent2);font-size:9px;font-weight:700}
|
||
@media(max-width:900px){.sidebar{width:180px}.right-panel{width:280px}}
|
||
@media(max-width:700px){.sidebar{display:none}.right-panel{display:none}}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="app">
|
||
<div class="sidebar">
|
||
<h2>微信控制</h2>
|
||
<div class="nav-group">
|
||
<div class="nav-group-title">核心功能</div>
|
||
<div class="nav-item active" onclick="showModule('message')">消息管理 <span class="count">6</span></div>
|
||
<div class="nav-item" onclick="showModule('friend')">好友管理 <span class="count">8</span></div>
|
||
<div class="nav-item" onclick="showModule('group')">群聊管理 <span class="count">10</span></div>
|
||
<div class="nav-item" onclick="showModule('moments')">朋友圈 <span class="count">8</span></div>
|
||
<div class="nav-item" onclick="showModule('tag')">标签管理 <span class="count">6</span></div>
|
||
</div>
|
||
<div class="nav-group">
|
||
<div class="nav-group-title">个人 & 安全</div>
|
||
<div class="nav-item" onclick="showModule('profile')">个人设置 <span class="count">6</span></div>
|
||
<div class="nav-item" onclick="showModule('security')">账号安全 <span class="count">9</span></div>
|
||
<div class="nav-item" onclick="showModule('payment')">支付 <span class="count">7</span></div>
|
||
</div>
|
||
<div class="nav-group">
|
||
<div class="nav-group-title">更多功能</div>
|
||
<div class="nav-item" onclick="showModule('video')">视频号 <span class="count">6</span></div>
|
||
<div class="nav-item" onclick="showModule('scan')">扫一扫 <span class="count">4</span></div>
|
||
<div class="nav-item" onclick="showModule('chat_settings')">聊天设置 <span class="count">3</span></div>
|
||
<div class="nav-item" onclick="showModule('favorites')">收藏 <span class="count">2</span></div>
|
||
<div class="nav-item" onclick="showModule('miniapp')">小程序/公众号 <span class="count">2</span></div>
|
||
<div class="nav-item" onclick="showModule('call')">通话 <span class="count">2</span></div>
|
||
<div class="nav-item" onclick="showModule('discover')">群发/搜索/发现 <span class="count">3</span></div>
|
||
<div class="nav-item" onclick="showModule('sport')">运动/位置/表情 <span class="count">6</span></div>
|
||
<div class="nav-item" onclick="showModule('media')">语音/文件 <span class="count">3</span></div>
|
||
<div class="nav-item" onclick="showModule('settings')">设置管理 <span class="count">5</span></div>
|
||
</div>
|
||
<div class="nav-group">
|
||
<div class="nav-group-title">系统</div>
|
||
<div class="nav-item" onclick="showModule('chain')">全链路监控</div>
|
||
<div class="nav-item" onclick="showModule('all_api')">全部API端点</div>
|
||
</div>
|
||
</div>
|
||
<div class="main">
|
||
<div class="header">
|
||
<span style="font-weight:600;font-size:14px">设备:</span>
|
||
<select id="deviceSelect" onchange="switchDevice()"><option value="">加载中...</option></select>
|
||
<span id="deviceStatus" class="status-pill status-offline">离线</span>
|
||
<span style="font-size:12px;color:var(--text2)">通道:</span>
|
||
<span id="channelBadge" class="channel-badge ch-u2">u2</span>
|
||
<button type="button" class="btn btn-sm btn-primary" onclick="loadDevices()" title="重新扫描本机 ADB 与在线 Agent">刷新设备</button>
|
||
<span style="margin-left:auto;font-size:11px;color:var(--text2)" id="apiCount">96 actions</span>
|
||
</div>
|
||
<div class="content">
|
||
<div class="panel" id="modulePanel"></div>
|
||
<div class="right-panel">
|
||
<div style="padding:12px">
|
||
<div class="section">
|
||
<div class="section-title">全链路状态</div>
|
||
<div id="chainStatus">
|
||
<div class="chain-step"><div class="chain-dot active"></div> 网页控制面板</div>
|
||
<div class="chain-line"></div>
|
||
<div class="chain-step"><div class="chain-dot" id="cd1"></div> <span id="cs1">unified.py (API)</span></div>
|
||
<div class="chain-line"></div>
|
||
<div class="chain-step"><div class="chain-dot" id="cd2"></div> <span id="cs2">通道路由</span></div>
|
||
<div class="chain-line"></div>
|
||
<div class="chain-step"><div class="chain-dot" id="cd3"></div> <span id="cs3">Agent/ADB</span></div>
|
||
<div class="chain-line"></div>
|
||
<div class="chain-step"><div class="chain-dot" id="cd4"></div> <span id="cs4">Hook/u2/Shell</span></div>
|
||
<div class="chain-line"></div>
|
||
<div class="chain-step"><div class="chain-dot" id="cd5"></div> <span id="cs5">微信APP</span></div>
|
||
</div>
|
||
</div>
|
||
<div class="section">
|
||
<div class="section-title">执行统计</div>
|
||
<div class="stats-grid">
|
||
<div class="stat-card"><div class="val" id="totalOps">0</div><div class="lbl">总操作</div></div>
|
||
<div class="stat-card"><div class="val" id="successRate">-</div><div class="lbl">成功率</div></div>
|
||
<div class="stat-card"><div class="val" id="avgTime">-</div><div class="lbl">平均耗时</div></div>
|
||
<div class="stat-card"><div class="val" id="lastChannel">-</div><div class="lbl">最近通道</div></div>
|
||
</div>
|
||
</div>
|
||
<div class="section">
|
||
<div class="section-title">执行结果</div>
|
||
<div class="result-box" id="resultBox">等待操作...</div>
|
||
</div>
|
||
<div class="section">
|
||
<div class="section-title">操作日志</div>
|
||
<div id="logBox" style="max-height:200px;overflow-y:auto"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<script>
|
||
const BASE='/api/v3';
|
||
/** API 返回 data 可能是设备数组,或 { devices: [] } */
|
||
function normalizeDeviceArray(json){
|
||
if(!json||typeof json!=='object')return[];
|
||
const d=json.data;
|
||
if(Array.isArray(d))return d;
|
||
if(d&&Array.isArray(d.devices))return d.devices;
|
||
if(Array.isArray(json.devices))return json.devices;
|
||
return [];
|
||
}
|
||
let loadedDeviceList=[];
|
||
let currentDevice='';
|
||
let stats={total:0,success:0,totalTime:0};
|
||
const MODULES={
|
||
message:{title:'消息管理',desc:'发送/获取/转发/撤回消息',channel:'Hook优先(发消息走Frida RPC ~200ms) | u2降级(~5s)',actions:[
|
||
{name:'发送消息',endpoint:'/message/send',method:'POST',fields:[{k:'to_id',l:'联系人',ph:'昵称或备注'},{k:'content',l:'内容',ph:'消息内容',type:'textarea'},{k:'msg_type',l:'类型',ph:'text',v:'text'}]},
|
||
{name:'获取消息',endpoint:'/message/list',method:'POST',fields:[{k:'conversation_id',l:'会话',ph:'联系人名称'},{k:'limit',l:'数量',ph:'20',v:'20'}]},
|
||
{name:'批量发送',endpoint:'/message/batch-send',method:'POST',fields:[{k:'to_ids',l:'接收者(逗号分隔)',ph:'张三,李四'},{k:'content',l:'内容',ph:'消息',type:'textarea'},{k:'interval',l:'间隔秒',v:'2'}]},
|
||
{name:'转发消息',endpoint:'/message/forward',method:'POST',fields:[{k:'to_id',l:'转发给',ph:'联系人'},{k:'content',l:'内容',ph:''}]},
|
||
{name:'撤回消息',endpoint:'/message/recall',method:'POST',fields:[]},
|
||
{name:'发送名片',endpoint:'/message/send-card',method:'POST',fields:[{k:'to_id',l:'发给谁',ph:'联系人'},{k:'card_wxid',l:'名片',ph:'微信号'}]}
|
||
]},
|
||
friend:{title:'好友管理',desc:'添加/通过/备注/删除/搜索好友',channel:'Hook可读(联系人列表走SQLite ~100ms) | u2写操作(~5s)',actions:[
|
||
{name:'添加好友',endpoint:'/friend/add',method:'POST',fields:[{k:'user_id',l:'微信号',ph:'微信号/手机号'},{k:'message',l:'验证消息',ph:'你好'}]},
|
||
{name:'通过请求',endpoint:'/friend/accept',method:'POST',fields:[{k:'user_id',l:'用户',ph:'可选'}]},
|
||
{name:'设置备注',endpoint:'/friend/set-remark',method:'POST',fields:[{k:'user_id',l:'好友',ph:'昵称'},{k:'remark',l:'备注',ph:'新备注'}]},
|
||
{name:'删除好友',endpoint:'/friend/delete',method:'POST',fields:[{k:'user_id',l:'好友',ph:'昵称'}]},
|
||
{name:'获取联系人',endpoint:'/contacts',method:'GET',fields:[{k:'limit',l:'数量',v:'100'}]},
|
||
{name:'搜索联系人',endpoint:'/contacts/search',method:'GET',fields:[{k:'keyword',l:'关键词',ph:'搜索'}]},
|
||
{name:'好友详情',endpoint:'/friend/info',method:'GET',fields:[{k:'user_id',l:'好友',ph:'昵称'}]},
|
||
{name:'批量添加',endpoint:'/friend/batch-add',method:'POST',fields:[{k:'user_ids',l:'ID列表(逗号)',ph:'id1,id2'},{k:'message',l:'验证消息',ph:''}]}
|
||
]},
|
||
group:{title:'群聊管理',desc:'创建/邀请/移除/公告/群名/群消息',channel:'Hook可读(群列表/成员走SQLite) | u2写操作',actions:[
|
||
{name:'创建群聊',endpoint:'/group/create',method:'POST',fields:[{k:'group_name',l:'群名',ph:'新群'},{k:'member_ids',l:'成员(逗号)',ph:'张三,李四'}]},
|
||
{name:'邀请入群',endpoint:'/group/invite',method:'POST',fields:[{k:'group_id',l:'群名',ph:''},{k:'member_ids',l:'邀请(逗号)',ph:''}]},
|
||
{name:'移出群聊',endpoint:'/group/remove',method:'POST',fields:[{k:'group_id',l:'群名',ph:''},{k:'member_ids',l:'移除(逗号)',ph:''}]},
|
||
{name:'群公告',endpoint:'/group/set-notice',method:'POST',fields:[{k:'group_id',l:'群名',ph:''},{k:'notice',l:'公告',ph:'',type:'textarea'}]},
|
||
{name:'群名',endpoint:'/group/set-name',method:'POST',fields:[{k:'group_id',l:'群名',ph:''},{k:'group_name',l:'新群名',ph:''}]},
|
||
{name:'群消息',endpoint:'/group/send-message',method:'POST',fields:[{k:'group_id',l:'群名',ph:''},{k:'content',l:'内容',ph:'',type:'textarea'}]},
|
||
{name:'群欢迎语',endpoint:'/group/set-welcome',method:'POST',fields:[{k:'group_id',l:'群名',ph:''},{k:'welcome_text',l:'欢迎语',ph:''}]},
|
||
{name:'群列表',endpoint:'/group/list',method:'GET',fields:[{k:'limit',l:'数量',v:'100'}]},
|
||
{name:'群成员',endpoint:'/group/members',method:'GET',fields:[{k:'group_id',l:'群名',ph:''}]},
|
||
{name:'退出群聊',endpoint:'/group/quit',method:'POST',fields:[{k:'group_id',l:'群名',ph:''}]}
|
||
]},
|
||
moments:{title:'朋友圈',desc:'发布/浏览/点赞/评论/删除/隐私设置',channel:'u2 UI自动化(~5-10s)',actions:[
|
||
{name:'发布朋友圈',endpoint:'/moments/post',method:'POST',fields:[{k:'content',l:'文字',ph:'',type:'textarea'}]},
|
||
{name:'点赞',endpoint:'/moments/like',method:'POST',fields:[{k:'user_id',l:'好友',ph:''},{k:'post_index',l:'第几条',v:'0'}]},
|
||
{name:'评论',endpoint:'/moments/comment',method:'POST',fields:[{k:'user_id',l:'好友',ph:''},{k:'comment',l:'评论',ph:''},{k:'post_index',l:'第几条',v:'0'}]},
|
||
{name:'获取朋友圈',endpoint:'/moments/list',method:'POST',fields:[{k:'user_id',l:'好友(空=自己)',ph:''},{k:'limit',l:'数量',v:'10'}]},
|
||
{name:'删除',endpoint:'/moments/delete',method:'POST',fields:[{k:'post_index',l:'第几条',v:'0'}]},
|
||
{name:'设置封面',endpoint:'/moments/set-cover',method:'POST',fields:[]},
|
||
{name:'可见天数',endpoint:'/moments/set-privacy',method:'POST',fields:[{k:'days',l:'天数',v:'180'}]},
|
||
{name:'分享链接',endpoint:'/moments/share-link',method:'POST',fields:[{k:'url',l:'URL',ph:''},{k:'title',l:'标题',ph:''}]}
|
||
]},
|
||
tag:{title:'标签管理',desc:'创建/删除/打标/查标',channel:'Hook可读(标签列表走SQLite) | u2写操作',actions:[
|
||
{name:'创建标签',endpoint:'/tag/create',method:'POST',fields:[{k:'tag_name',l:'标签名',ph:''}]},
|
||
{name:'删除标签',endpoint:'/tag/delete',method:'POST',fields:[{k:'tag_name',l:'标签名',ph:''}]},
|
||
{name:'标签列表',endpoint:'/tag/list',method:'GET',fields:[]},
|
||
{name:'打标签',endpoint:'/tag/add',method:'POST',fields:[{k:'user_id',l:'好友',ph:''},{k:'tags',l:'标签(逗号)',ph:'VIP,新客'}]},
|
||
{name:'移除标签',endpoint:'/tag/remove',method:'POST',fields:[{k:'user_id',l:'好友',ph:''},{k:'tags',l:'标签(逗号)',ph:''}]},
|
||
{name:'按标签查人',endpoint:'/tag/users',method:'POST',fields:[{k:'tag_name',l:'标签名',ph:''}]}
|
||
]},
|
||
profile:{title:'个人设置',desc:'昵称/签名/头像/性别/地区/资料',channel:'Hook可读(get_profile走SQLite userinfo ~50ms) | u2写操作(~6s)',actions:[
|
||
{name:'获取资料',endpoint:'/profile/get',method:'GET',fields:[]},
|
||
{name:'修改昵称',endpoint:'/profile/set-nickname',method:'POST',fields:[{k:'nickname',l:'昵称',ph:''}]},
|
||
{name:'修改签名',endpoint:'/profile/set-signature',method:'POST',fields:[{k:'signature',l:'签名',ph:''}]},
|
||
{name:'修改头像',endpoint:'/profile/set-avatar',method:'POST',fields:[]},
|
||
{name:'设置性别',endpoint:'/profile/set-gender',method:'POST',fields:[{k:'gender',l:'性别',ph:'男/女'}]},
|
||
{name:'设置地区',endpoint:'/profile/set-region',method:'POST',fields:[{k:'region',l:'地区',ph:'中国 福建 厦门'}]}
|
||
]},
|
||
security:{title:'账号安全',desc:'状态检查/解封/改密码/限制',channel:'u2 UI自动化(~5-10s)',actions:[
|
||
{name:'账号状态',endpoint:'/account/status',method:'GET',fields:[]},
|
||
{name:'安全中心',endpoint:'/account/safety-center',method:'GET',fields:[]},
|
||
{name:'微信解封',endpoint:'/account/unblock',method:'POST',fields:[]},
|
||
{name:'自助解封',endpoint:'/account/unblock-self',method:'POST',fields:[]},
|
||
{name:'申诉解封',endpoint:'/account/unblock-appeal',method:'POST',fields:[]},
|
||
{name:'短信解封',endpoint:'/account/unblock-sms',method:'POST',fields:[{k:'phone',l:'手机号',ph:''}]},
|
||
{name:'修改密码',endpoint:'/account/change-password',method:'POST',fields:[{k:'old_pwd',l:'旧密码',ph:''},{k:'new_pwd',l:'新密码',ph:''}]},
|
||
{name:'功能限制',endpoint:'/account/restrictions',method:'GET',fields:[]},
|
||
{name:'申诉限制',endpoint:'/account/appeal-restriction',method:'POST',fields:[]}
|
||
]},
|
||
payment:{title:'支付',desc:'红包/转账/收款/钱包/账单',channel:'u2 UI自动化(安全考虑不走Hook, ~5-10s)',actions:[
|
||
{name:'发红包',endpoint:'/payment/red-packet',method:'POST',fields:[{k:'to_id',l:'发给谁',ph:''},{k:'amount',l:'金额',ph:''},{k:'message',l:'祝福语',v:'恭喜发财'}]},
|
||
{name:'转账',endpoint:'/payment/transfer',method:'POST',fields:[{k:'to_id',l:'转给谁',ph:''},{k:'amount',l:'金额',ph:''}]},
|
||
{name:'付款码',endpoint:'/payment/code',method:'GET',fields:[]},
|
||
{name:'收款',endpoint:'/payment/receive',method:'POST',fields:[]},
|
||
{name:'钱包',endpoint:'/payment/wallet',method:'GET',fields:[]},
|
||
{name:'账单',endpoint:'/payment/transactions',method:'GET',fields:[]},
|
||
{name:'领红包',endpoint:'/payment/receive-red-packet',method:'POST',fields:[]}
|
||
]},
|
||
video:{title:'视频号',desc:'浏览/点赞/评论/关注/分享',channel:'u2 UI自动化(~4-6s)',actions:[
|
||
{name:'打开视频号',endpoint:'/video-channel/list',method:'GET',fields:[]},
|
||
{name:'点赞视频',endpoint:'/video-channel/like',method:'POST',fields:[{k:'index',l:'第几个',v:'0'}]},
|
||
{name:'评论视频',endpoint:'/video-channel/comment',method:'POST',fields:[{k:'index',l:'第几个',v:'0'},{k:'comment',l:'评论',ph:''}]},
|
||
{name:'关注创作者',endpoint:'/video-channel/follow',method:'POST',fields:[{k:'index',l:'第几个',v:'0'}]},
|
||
{name:'分享视频',endpoint:'/video-channel/share',method:'POST',fields:[{k:'index',l:'第几个',v:'0'},{k:'to_id',l:'分享给',ph:''}]},
|
||
{name:'视频列表',endpoint:'/video-channel/list',method:'GET',fields:[{k:'limit',l:'数量',v:'10'}]}
|
||
]},
|
||
scan:{title:'扫一扫',desc:'扫码/加好友/我的二维码',channel:'u2 UI自动化(~3-5s)',actions:[
|
||
{name:'扫一扫',endpoint:'/scan/qr-code',method:'POST',fields:[]},
|
||
{name:'扫码加好友',endpoint:'/scan/add-friend',method:'POST',fields:[]},
|
||
{name:'我的二维码',endpoint:'/scan/my-qr',method:'GET',fields:[]},
|
||
{name:'图片识别码',endpoint:'/scan/extract-qr',method:'POST',fields:[]}
|
||
]},
|
||
chat_settings:{title:'聊天设置',desc:'置顶/免打扰/清空',channel:'u2 UI自动化(~5s)',actions:[
|
||
{name:'置顶聊天',endpoint:'/chat/set-top',method:'POST',fields:[{k:'user_id',l:'联系人',ph:''},{k:'enable',l:'开启',v:'true'}]},
|
||
{name:'免打扰',endpoint:'/chat/set-mute',method:'POST',fields:[{k:'user_id',l:'联系人',ph:''},{k:'enable',l:'开启',v:'true'}]},
|
||
{name:'清空记录',endpoint:'/chat/clear-history',method:'POST',fields:[{k:'user_id',l:'联系人',ph:''}]}
|
||
]},
|
||
favorites:{title:'收藏管理',desc:'收藏/列表',channel:'Hook可读(Favorite.db ~100ms) | u2写操作',actions:[
|
||
{name:'收藏',endpoint:'/favorites/add',method:'POST',fields:[]},
|
||
{name:'收藏列表',endpoint:'/favorites/list',method:'GET',fields:[{k:'limit',l:'数量',v:'20'}]}
|
||
]},
|
||
miniapp:{title:'小程序 & 公众号',desc:'',channel:'u2 UI自动化(~8s)',actions:[
|
||
{name:'打开小程序',endpoint:'/miniprogram/open',method:'POST',fields:[{k:'name',l:'名称',ph:''}]},
|
||
{name:'关注公众号',endpoint:'/official-account/follow',method:'POST',fields:[{k:'account_name',l:'公众号名',ph:''}]}
|
||
]},
|
||
call:{title:'通话',desc:'语音/视频通话',channel:'u2 UI自动化(~6s)',actions:[
|
||
{name:'语音通话',endpoint:'/call/voice',method:'POST',fields:[{k:'user_id',l:'联系人',ph:''}]},
|
||
{name:'视频通话',endpoint:'/call/video',method:'POST',fields:[{k:'user_id',l:'联系人',ph:''}]}
|
||
]},
|
||
discover:{title:'群发/搜索/发现',desc:'群发助手/搜一搜/看一看',channel:'u2(~3-10s) | Hook(搜一搜走SQLite)',actions:[
|
||
{name:'群发消息',endpoint:'/mass-send',method:'POST',fields:[{k:'content',l:'内容',ph:'',type:'textarea'},{k:'user_ids',l:'用户(逗号)',ph:''}]},
|
||
{name:'搜一搜',endpoint:'/search/wechat',method:'GET',fields:[{k:'keyword',l:'关键词',ph:''}]},
|
||
{name:'看一看',endpoint:'/discover/top-stories',method:'GET',fields:[]}
|
||
]},
|
||
sport:{title:'运动/位置/表情',desc:'',channel:'u2 UI自动化(~5s)',actions:[
|
||
{name:'获取步数',endpoint:'/wechat-sport/steps',method:'GET',fields:[]},
|
||
{name:'点赞步数',endpoint:'/wechat-sport/like-steps',method:'POST',fields:[{k:'user_id',l:'好友',ph:''}]},
|
||
{name:'发送位置',endpoint:'/location/send',method:'POST',fields:[{k:'user_id',l:'联系人',ph:''}]},
|
||
{name:'实时位置',endpoint:'/location/share-realtime',method:'POST',fields:[{k:'user_id',l:'联系人',ph:''}]},
|
||
{name:'发送表情',endpoint:'/emoji/send',method:'POST',fields:[{k:'user_id',l:'联系人',ph:''},{k:'emoji_name',l:'表情名',ph:''}]},
|
||
{name:'表情列表',endpoint:'/emoji/stickers',method:'GET',fields:[]}
|
||
]},
|
||
media:{title:'语音/文件',desc:'',channel:'u2 UI自动化(~3-6s)',actions:[
|
||
{name:'语音消息',endpoint:'/message/voice',method:'POST',fields:[{k:'user_id',l:'联系人',ph:''},{k:'duration',l:'秒数',v:'3'}]},
|
||
{name:'发送文件',endpoint:'/file/send',method:'POST',fields:[{k:'user_id',l:'联系人',ph:''}]},
|
||
{name:'下载文件',endpoint:'/file/download',method:'POST',fields:[{k:'user_id',l:'联系人',ph:''}]}
|
||
]},
|
||
settings:{title:'设置管理',desc:'勿扰/缓存/更新/退出/切换',channel:'u2 UI自动化(~5s)',actions:[
|
||
{name:'勿扰模式',endpoint:'/settings/do-not-disturb',method:'POST',fields:[{k:'enable',l:'开启',v:'true'}]},
|
||
{name:'清理缓存',endpoint:'/settings/clear-cache',method:'POST',fields:[]},
|
||
{name:'检查更新',endpoint:'/settings/check-update',method:'POST',fields:[]},
|
||
{name:'退出登录',endpoint:'/settings/logout',method:'POST',fields:[]},
|
||
{name:'切换账号',endpoint:'/settings/switch-account',method:'POST',fields:[]}
|
||
]}
|
||
};
|
||
function showModule(key){
|
||
document.querySelectorAll('.nav-item').forEach(e=>e.classList.remove('active'));
|
||
event&&event.target&&event.target.classList.add('active');
|
||
const panel=document.getElementById('modulePanel');
|
||
if(key==='chain'){panel.innerHTML=renderChainView();return}
|
||
if(key==='all_api'){panel.innerHTML=renderAllApi();return}
|
||
const m=MODULES[key];if(!m){panel.innerHTML='';return}
|
||
let h=`<div class="section"><div class="section-title">${m.title}</div>
|
||
<div class="card"><p style="font-size:12px;color:var(--text2);margin-bottom:8px">${m.desc}</p>
|
||
<p style="font-size:11px;margin-bottom:12px"><span class="channel-badge ch-hook" style="margin-right:4px">通道</span> ${m.channel}</p></div></div>`;
|
||
m.actions.forEach((a,i)=>{
|
||
h+=`<div class="card"><div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">
|
||
<span style="font-weight:600;font-size:13px">${a.name}</span>
|
||
<span style="font-size:10px;color:var(--text2)">${a.method} ${a.endpoint}</span></div>
|
||
<form onsubmit="return execAction('${key}',${i},this)">`;
|
||
a.fields.forEach(f=>{
|
||
h+=`<div class="form-group"><label>${f.l}</label>`;
|
||
if(f.type==='textarea')h+=`<textarea name="${f.k}" placeholder="${f.ph||''}">${f.v||''}</textarea>`;
|
||
else h+=`<input name="${f.k}" placeholder="${f.ph||''}" value="${f.v||''}">`;
|
||
h+=`</div>`;
|
||
});
|
||
h+=`<button type="submit" class="btn btn-primary">执行</button></form></div>`;
|
||
});
|
||
panel.innerHTML=h;
|
||
}
|
||
function renderChainView(){
|
||
return `<div class="section"><div class="section-title">微信全链路控制架构</div>
|
||
<div class="card"><pre style="font-size:12px;line-height:1.8;color:var(--text2)">
|
||
网页控制面板 (本页面)
|
||
|
|
||
| HTTP REST API
|
||
v
|
||
unified.py (98个API端点 / 29个功能模块)
|
||
|
|
||
|--- 通道路由 ChannelRouter ---+
|
||
| |
|
||
v v
|
||
WebSocket Hub ADB Engine
|
||
(Agent在线) (Agent离线)
|
||
| |
|
||
v v
|
||
Agent (设备端Python) wechat_adb_engine.py
|
||
| (96个action, ADB Shell)
|
||
|--- 优先 Frida Hook
|
||
| (110个RPC, ~50-200ms)
|
||
|
|
||
|--- 降级 u2 UI自动化
|
||
| (WechatSkill 96个action, ~2-15s)
|
||
|
|
||
v
|
||
微信APP (com.tencent.mm)
|
||
</pre></div></div>
|
||
<div class="section"><div class="section-title">三种控制通道性能对比</div>
|
||
<div class="card">
|
||
<table style="width:100%;font-size:12px;border-collapse:collapse">
|
||
<tr style="border-bottom:1px solid var(--border)"><th style="text-align:left;padding:6px;color:var(--text2)">通道</th><th style="padding:6px;color:var(--text2)">原理</th><th style="padding:6px;color:var(--text2)">延迟</th><th style="padding:6px;color:var(--text2)">稳定性</th><th style="padding:6px;color:var(--text2)">适用</th></tr>
|
||
<tr style="border-bottom:1px solid var(--border)"><td style="padding:6px"><span class="channel-badge ch-hook">Frida Hook</span></td><td style="padding:6px;color:var(--text2)">调用微信内部Java方法/读SQLite</td><td style="padding:6px;color:var(--accent2)">50-200ms</td><td style="padding:6px;color:var(--accent3)">依赖版本偏移表</td><td style="padding:6px;color:var(--text2)">读数据/发消息</td></tr>
|
||
<tr style="border-bottom:1px solid var(--border)"><td style="padding:6px"><span class="channel-badge ch-u2">u2 UI自动化</span></td><td style="padding:6px;color:var(--text2)">uiautomator2模拟点击/输入</td><td style="padding:6px;color:var(--accent)">2-15s</td><td style="padding:6px;color:var(--accent2)">高,跨版本通用</td><td style="padding:6px;color:var(--text2)">所有UI操作</td></tr>
|
||
<tr><td style="padding:6px"><span class="channel-badge ch-adb">ADB Shell</span></td><td style="padding:6px;color:var(--text2)">adb命令+坐标点击</td><td style="padding:6px;color:var(--accent3)">1-10s</td><td style="padding:6px;color:var(--accent3)">中,依赖分辨率</td><td style="padding:6px;color:var(--text2)">兜底/无Agent</td></tr>
|
||
</table></div></div>`;
|
||
}
|
||
function renderAllApi(){
|
||
let h='<div class="section"><div class="section-title">全部 98 个 API 端点</div>';
|
||
Object.entries(MODULES).forEach(([k,m])=>{
|
||
h+=`<div class="card"><div style="font-weight:600;font-size:13px;margin-bottom:8px">${m.title} (${m.actions.length})</div><div class="api-grid">`;
|
||
m.actions.forEach((a,i)=>{
|
||
h+=`<div class="api-btn" onclick="showModule('${k}')"><div class="method">${a.method}</div>${a.name}</div>`;
|
||
});
|
||
h+=`</div></div>`;
|
||
});
|
||
h+=`</div>`;return h;
|
||
}
|
||
async function execAction(moduleKey,actionIdx,form){
|
||
const m=MODULES[moduleKey];const a=m.actions[actionIdx];
|
||
const fd=new FormData(form);const body={};
|
||
fd.forEach((v,k)=>{if(v)body[k]=v});
|
||
body.device_id=currentDevice;body.platform='wechat';
|
||
if(body.to_ids)body.to_ids=body.to_ids.split(',').map(s=>s.trim());
|
||
if(body.member_ids)body.member_ids=body.member_ids.split(',').map(s=>s.trim());
|
||
if(body.user_ids)body.user_ids=body.user_ids.split(',').map(s=>s.trim());
|
||
if(body.tags)body.tags=body.tags.split(',').map(s=>s.trim());
|
||
if(body.enable)body.enable=body.enable==='true';
|
||
['limit','post_index','index','duration','days','interval'].forEach(k=>{if(body[k])body[k]=Number(body[k])});
|
||
const start=Date.now();
|
||
updateChain('executing',a.name);
|
||
try{
|
||
let url=BASE+a.endpoint;
|
||
const opts={headers:{'Content-Type':'application/json'}};
|
||
if(a.method==='GET'){
|
||
const qs=Object.entries(body).map(([k,v])=>`${k}=${encodeURIComponent(v)}`).join('&');
|
||
if(qs)url+='?'+qs;
|
||
opts.method='GET';
|
||
}else{
|
||
opts.method='POST';opts.body=JSON.stringify(body);
|
||
}
|
||
const res=await fetch(url,opts);const data=await res.json();
|
||
const elapsed=Date.now()-start;
|
||
stats.total++;
|
||
const ok=data.code===200&&(data.data?.success!==false);
|
||
if(ok)stats.success++;
|
||
stats.totalTime+=elapsed;
|
||
const ch=data.channel_used||'sdk_control';
|
||
updateStats(ch,elapsed);
|
||
updateChain(ok?'success':'error',a.name,ch,elapsed);
|
||
showResult(data,elapsed,ch);
|
||
addLog(a.name,ok,ch,elapsed);
|
||
}catch(e){
|
||
const elapsed=Date.now()-start;
|
||
stats.total++;stats.totalTime+=elapsed;
|
||
updateStats('-',elapsed);
|
||
updateChain('error',a.name,'-',elapsed);
|
||
showResult({error:e.message},elapsed,'-');
|
||
addLog(a.name,false,'-',elapsed);
|
||
}
|
||
return false;
|
||
}
|
||
function updateChain(state,action,channel,elapsed){
|
||
const dots=['cd1','cd2','cd3','cd4','cd5'];
|
||
const labels=['cs1','cs2','cs3','cs4','cs5'];
|
||
if(state==='executing'){
|
||
dots.forEach(id=>{document.getElementById(id).className='chain-dot active'});
|
||
document.getElementById('cs1').textContent=`unified.py → ${action}`;
|
||
document.getElementById('cs2').textContent='通道路由中...';
|
||
document.getElementById('cs3').textContent='执行中...';
|
||
document.getElementById('cs4').textContent='...';
|
||
document.getElementById('cs5').textContent='微信APP';
|
||
}else{
|
||
const cls=state==='success'?'chain-dot active':'chain-dot';
|
||
dots.forEach(id=>{document.getElementById(id).className=cls});
|
||
document.getElementById('cs1').textContent=`unified.py → ${action}`;
|
||
document.getElementById('cs2').textContent=`通道: ${channel}`;
|
||
document.getElementById('cs3').textContent=state==='success'?'执行成功':'执行失败';
|
||
document.getElementById('cs4').textContent=`${channel} (${elapsed}ms)`;
|
||
document.getElementById('cs5').textContent='微信APP';
|
||
const badge=document.getElementById('channelBadge');
|
||
if(channel?.includes('hook')){badge.className='channel-badge ch-hook';badge.textContent='Hook'}
|
||
else if(channel?.includes('adb')){badge.className='channel-badge ch-adb';badge.textContent='ADB'}
|
||
else{badge.className='channel-badge ch-u2';badge.textContent='u2'}
|
||
}
|
||
}
|
||
function updateStats(ch){
|
||
document.getElementById('totalOps').textContent=stats.total;
|
||
document.getElementById('successRate').textContent=stats.total?Math.round(stats.success/stats.total*100)+'%':'-';
|
||
document.getElementById('avgTime').textContent=stats.total?Math.round(stats.totalTime/stats.total)+'ms':'-';
|
||
document.getElementById('lastChannel').textContent=ch||'-';
|
||
}
|
||
function showResult(data,elapsed,ch){
|
||
const box=document.getElementById('resultBox');
|
||
box.textContent=`// ${ch} | ${elapsed}ms\n`+JSON.stringify(data,null,2);
|
||
}
|
||
function addLog(action,ok,ch,elapsed){
|
||
const box=document.getElementById('logBox');
|
||
const t=new Date().toLocaleTimeString();
|
||
const cls=ok?'ok':'fail';
|
||
box.innerHTML=`<div class="log-entry"><span class="time">${t}</span> <span class="action">${action}</span> <span class="${cls}">${ok?'OK':'FAIL'}</span> ${ch} ${elapsed}ms</div>`+box.innerHTML;
|
||
if(box.children.length>50)box.removeChild(box.lastChild);
|
||
}
|
||
async function loadDevices(){
|
||
try{
|
||
const res=await fetch(BASE+'/devices');
|
||
const data=await res.json();
|
||
const sel=document.getElementById('deviceSelect');
|
||
sel.innerHTML='';
|
||
let devices=normalizeDeviceArray(data);
|
||
if(!devices.length){
|
||
try{
|
||
const adbRes=await fetch('/api/v3/adb/devices');
|
||
const adbData=await adbRes.json();
|
||
normalizeDeviceArray(adbData).forEach(d=>{
|
||
const did=d.device_id||d.serial;
|
||
if(!did)return;
|
||
const st=(d.status==='online'||d.status==='adb')?'adb':'adb';
|
||
devices.push({device_id:did,status:st,model:d.model||did});
|
||
});
|
||
}catch(e){}
|
||
}
|
||
if(!devices.length){sel.innerHTML='<option value="">无设备(请 USB 连接手机并 adb devices 授权,或启动 Agent)</option>';loadedDeviceList=[];updateDeviceStatus(null);return}
|
||
loadedDeviceList=devices;
|
||
const prevPick=currentDevice;
|
||
devices.forEach(d=>{
|
||
const did=d.device_id||d.serial||d;
|
||
const lbl=d.model?`${did} (${d.model})`:did;
|
||
sel.innerHTML+=`<option value="${did}">${lbl}</option>`;
|
||
});
|
||
const first=devices[0];
|
||
const firstId=first.device_id||first.serial;
|
||
if(prevPick&&devices.some(x=>(x.device_id||x.serial)===prevPick))currentDevice=prevPick;
|
||
else currentDevice=firstId;
|
||
sel.value=currentDevice;
|
||
updateDeviceStatus(devices.find(x=>(x.device_id||x.serial)===currentDevice)||first);
|
||
}catch(e){
|
||
document.getElementById('deviceSelect').innerHTML='<option value="">连接失败</option>';
|
||
loadedDeviceList=[];
|
||
updateDeviceStatus(null);
|
||
}
|
||
}
|
||
function switchDevice(){
|
||
currentDevice=document.getElementById('deviceSelect').value;
|
||
const d=loadedDeviceList.find(x=>(x.device_id||x.serial)===currentDevice);
|
||
updateDeviceStatus(d||null);
|
||
}
|
||
function updateDeviceStatus(d){
|
||
const el=document.getElementById('deviceStatus');
|
||
if(!d){el.className='status-pill status-offline';el.textContent='离线';return}
|
||
const s=d.status||'offline';
|
||
if(s==='online'){el.className='status-pill status-online';el.textContent='在线(WS)'}
|
||
else if(s==='adb'){el.className='status-pill status-online';el.textContent='在线(ADB)'}
|
||
else{el.className='status-pill status-offline';el.textContent='离线'}
|
||
}
|
||
loadDevices();
|
||
showModule('message');
|
||
</script>
|
||
</body>
|
||
</html>
|