微信文件传输助手网页版上传文件
Overview
qqbrowser-skill CLI 没有直接的上传文件命令(38 个 browser_* 命令均不含 upload/set_file_input_files)。可靠的上传方式是:用 browser_eval_content_js 在页面上下文构造 DataTransfer + File 对象,赋值给上传控件的 .files 并触发 change 事件,页面即接收文件进入待发送列表,随后点击"发送"按钮完成发送。该方法已实测可行(无需操作系统文件选择对话框)。
前置条件
- QQ 浏览器已打开微信文件传输助手网页版 https://szfilehelper.weixin.qq.com/
qqbrowser-skillCLI 可用,且browser_start_session已启动(执行任意命令前先确认qqbrowser-skill status正常)- 若页面未打开:先执行
qqbrowser-skill browser_go_to_url --url https://szfilehelper.weixin.qq.com/并等待加载
工作流
Step 1:确认页面状态
执行 qqbrowser-skill browser_snapshot,确认页面包含:
- <input 上传文件;file/>(实际元素:<input id="btnFile" type="file" multiple class="file-input">)
- <a 发送/>
若页面显示"浏览器版本过低"等提示但仍有关键控件,可继续操作。
Step 2:将本地文件转 base64
用 Python 把文件内容编码为 base64,结果将内嵌到注入脚本中:
python -c "import base64;print(base64.b64encode(open(r'<本地文件绝对路径>','rb').read()).decode())"
记录输出的 base64 字符串与文件名(含扩展名)。
Step 3:注入文件到上传控件(核心)
用 browser_eval_content_js 执行以下 JS 模板(必须整体用 IIFE 包裹,避免与页面全局变量冲突;文件内容用 base64 + atob + Uint8Array 构造,保证中文/二进制安全):
(function(){
var b64 = "<步骤2得到的BASE64>";
var bin = atob(b64);
var bytes = new Uint8Array(bin.length);
for (var i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
var file = new File([bytes], "<文件名,含扩展名>", {type: "<MIME类型,如 text/plain>"});
var dt = new DataTransfer();
dt.items.add(file);
var inp = document.getElementById('btnFile');
inp.files = dt.files;
inp.dispatchEvent(new Event('input', {bubbles: true}));
inp.dispatchEvent(new Event('change', {bubbles: true}));
return 'SET_OK files=' + inp.files.length;
})()
执行方式:
qqbrowser-skill browser_eval_content_js --script "<上述JS,换行替换为空格或原样传参>"
注意:browser_eval_content_js 的输出包含完整页面快照,结果在 Result: 后;可用 grep "Result:" 提取。
Step 4:确认文件已进入待发送列表
再次执行 qqbrowser-skill browser_snapshot,页面应出现文件名与文件大小文本(如 文件名列表.txt、252B)。
若此时输入框的 .files 已变空(files=0),属正常现象——页面已消费文件并加入待发送队列,不影响后续发送。
Step 5:点击发送按钮
以最新 browser_snapshot 中的索引为准,点击发送按钮:
qqbrowser-skill browser_click_element --index <发送按钮索引>
若点击无反应(按钮可能带 chat-send__button__disabled class),先用 eval JS 移除禁用状态再触发鼠标事件序列:
(function(){
var btns = document.querySelectorAll('a');
var sendBtn = null;
btns.forEach(function(a){ if(a.textContent.trim()==='发送') sendBtn = a; });
if(!sendBtn) return 'NO_SEND_BTN';
sendBtn.classList.remove('chat-send__button__disabled');
sendBtn.className = 'chat-send__button';
sendBtn.dispatchEvent(new MouseEvent('mousedown', {bubbles:true, cancelable:true, view:window}));
sendBtn.dispatchEvent(new MouseEvent('mouseup', {bubbles:true, cancelable:true, view:window}));
sendBtn.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable:true, view:window}));
return 'SEND_EVENTS_DISPATCHED';
})()
Step 6:验证发送成功
等待数秒后(browser_wait --seconds 5),用 eval JS 确认聊天列表出现该文件消息:
(function(){
var items = document.querySelectorAll('.msg-item.mine');
var info = [];
items.forEach(function(it){ info.push(it.textContent.slice(0,80)); });
return 'SENT_ITEMS=' + info.length + '::' + info.join('||');
})()
若输出包含目标文件名(如 文件名列表.txt252B),说明发送成功。
坑与注意事项
- IIFE 包裹:
browser_eval_content_js在页面全局作用域执行脚本,直接用const input = ...会与页面已有的全局变量(如input)冲突报Identifier has already been declared。一律用(function(){ ... })()包裹,变量用局部名(var inp)。 - 中文/二进制安全:文件内容必须 base64 编码后用
atob+Uint8Array构造,直接内嵌字符串会破坏中文与二进制数据。 - 重复添加:每次注入会新增一条待发送文件。若页面已存在待发送项,重复注入会重复发送。需要先完成发送或清理,避免重复。
- 发送按钮 disabled:微信页面发送按钮初始可能带
chat-send__button__disabledclass,直接click()可能无效;先移除 class,再用 MouseEvent 序列(mousedown/mouseup/click)触发。 - files 被清空:注入后页面可能立即清空
input.files(files=0),这是页面已接收文件的正常表现,不是失败。 - 索引失效:每次命令后 DOM 会变化,点击/输入前必须重新
browser_snapshot获取最新索引。 - 结果提取:所有
browser_*命令返回均含页面快照文本,用grep "Result:"或grep -oE "Result: .*"提取实际结果,避免长输出干扰。