AutoHotkey 2与Tampermonkey脚本本地通信及CORS问题求解
解决方案
方案1:用Tampermonkey特权API绕过CORS
Tampermonkey自带不受浏览器CORS限制的网络请求API,完全避开前端同源策略限制,适合快速实现需求。
1.1 轮询方案(简单易实现)
Tampermonkey脚本部分:
// ==UserScript== // @name 视频控制-本地命令轮询 // @namespace http://tampermonkey.net/ // @version 0.1 // @description 轮询本地服务获取视频控制命令 // @author You // @match *://*/* // @grant GM_xmlhttpRequest // ==/UserScript== (function() { 'use strict'; const POLL_INTERVAL = 500; // 轮询间隔(毫秒) function pollCommand() { GM_xmlhttpRequest({ method: "GET", url: "http://localhost:3000/get-command", onload: function(response) { if (response.status === 200) { const command = response.responseText.trim(); if (command) { executeVideoCommand(command); // 执行后清除服务端命令 GM_xmlhttpRequest({method: "POST", url: "http://localhost:3000/clear-command"}); } } setTimeout(pollCommand, POLL_INTERVAL); }, onerror: function() { setTimeout(pollCommand, POLL_INTERVAL); } }); } function executeVideoCommand(cmd) { const videos = document.querySelectorAll('video'); if (videos.length === 0) return; switch(cmd) { case 'play': videos.forEach(v => v.play()); break; case 'pause': videos.forEach(v => v.pause()); break; case 'forward': videos.forEach(v => v.currentTime += 5); // 快进5秒 break; case 'backward': videos.forEach(v => v.currentTime -= 5); // 快退5秒 break; case 'speed-up': videos.forEach(v => v.playbackRate = Math.min(v.playbackRate + 0.5, 4)); // 最大4倍速 break; case 'speed-down': videos.forEach(v => v.playbackRate = Math.max(v.playbackRate - 0.5, 0.25)); // 最小0.25倍速 break; } } pollCommand(); })();
本地Express服务(配合AHK调用):
const express = require('express'); const app = express(); let currentCommand = ''; // 允许AHK无限制调用 app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); next(); }); // 获取当前命令 app.get('/get-command', (req, res) => { res.send(currentCommand); }); // 设置命令(AHK调用此接口) app.post('/set-command', express.text(), (req, res) => { currentCommand = req.body; res.send('ok'); }); // 清除命令 app.post('/clear-command', (req, res) => { currentCommand = ''; res.send('ok'); }); app.listen(3000, () => { console.log('本地命令服务运行在 http://localhost:3000'); });
AHK2脚本(StreamDeck触发函数):
; 发送命令到本地服务 SendVideoCommand(cmd) { HttpRequest := ComObject("WinHttp.WinHttpRequest.5.1") HttpRequest.Open("POST", "http://localhost:3000/set-command") HttpRequest.SetRequestHeader("Content-Type", "text/plain") HttpRequest.Send(cmd) } ; StreamDeck绑定的函数示例 PlayVideo() { SendVideoCommand("play") } PauseVideo() { SendVideoCommand("pause") } ForwardVideo() { SendVideoCommand("forward") }
1.2 WebSocket实时方案(低延迟)
如果需要实时响应,用Tampermonkey的GM_webSocket:
Tampermonkey脚本部分:
// ==UserScript== // @name 视频控制-WebSocket实时通信 // @namespace http://tampermonkey.net/ // @version 0.1 // @description 通过WebSocket接收本地视频控制命令 // @author You // @match *://*/* // @grant GM_webSocket // ==/UserScript== (function() { 'use strict'; const ws = GM_webSocket("ws://localhost:3001"); ws.onopen = function() { console.log('WebSocket连接成功'); }; ws.onmessage = function(event) { const command = event.data.trim(); executeVideoCommand(command); }; ws.onerror = function() { console.log('WebSocket连接错误,3秒后重连'); setTimeout(() => window.location.reload(), 3000); }; function executeVideoCommand(cmd) { // 同轮询方案的executeVideoCommand函数 const videos = document.querySelectorAll('video'); if (videos.length === 0) return; switch(cmd) { case 'play': videos.forEach(v => v.play()); break; case 'pause': videos.forEach(v => v.pause()); break; case 'forward': videos.forEach(v => v.currentTime += 5); break; case 'backward': videos.forEach(v => v.currentTime -= 5); break; case 'speed-up': videos.forEach(v => v.playbackRate = Math.min(v.playbackRate + 0.5, 4)); break; case 'speed-down': videos.forEach(v => v.playbackRate = Math.max(v.playbackRate - 0.5, 0.25)); break; } } })();
WebSocket服务(Node.js):
const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 3001 }); let clients = []; wss.on('connection', (ws) => { clients.push(ws); ws.on('close', () => { clients = clients.filter(client => client !== ws); }); }); // 提供HTTP接口给AHK发送命令 const express = require('express'); const app = express(); app.use(express.text()); app.post('/send-command', (req, res) => { const cmd = req.body; clients.forEach(client => { if (client.readyState === WebSocket.OPEN) { client.send(cmd); } }); res.send('ok'); }); app.listen(3002, () => { console.log('命令转发服务运行在 http://localhost:3002'); });
AHK2调用脚本:
SendVideoCommand(cmd) { HttpRequest := ComObject("WinHttp.WinHttpRequest.5.1") HttpRequest.Open("POST", "http://localhost:3002/send-command") HttpRequest.SetRequestHeader("Content-Type", "text/plain") HttpRequest.Send(cmd) }
方案2:AHK直接操控Chrome视频(无需Web服务)
跳过Web通信,用Chrome远程调试协议直接控制标签页,效率更高:
步骤1:开启Chrome远程调试
右键Chrome快捷方式,在目标后添加参数: --remote-debugging-port=9222,示例:"C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222
步骤2:AHK2脚本
; 获取Chrome活动标签页的调试地址 GetChromeDebugUrl() { HttpRequest := ComObject("WinHttp.WinHttpRequest.5.1") HttpRequest.Open("GET", "http://localhost:9222/json") HttpRequest.Send() Tabs := HttpRequest.ResponseText.Parse() for _, Tab in Tabs { if Tab.type = "page" && Tab.active { return Tab.webSocketDebuggerUrl } } return "" } ; 直接发送视频控制命令到Chrome ControlChromeVideo(cmd) { DebugUrl := GetChromeDebugUrl() if DebugUrl = "" { MsgBox "未找到活动Chrome标签页" return } Ws := ComObject("WebSocket") Ws.Connect(DebugUrl) if !Ws.ReadyState = 1 { MsgBox "无法连接Chrome调试端口" return } ; 生成调试协议命令 CmdId := Random(1, 9999) Command := { id: CmdId, method: "Runtime.evaluate", params: { expression: "(() => {const videos = document.querySelectorAll('video'); if(videos.length ===0) return; switch('" cmd "'){case 'play': videos.forEach(v=>v.play()); break; case 'pause': videos.forEach(v=>v.pause()); break; case 'forward': videos.forEach(v=>v.currentTime+=5); break; case 'backward': videos.forEach(v=>v.currentTime-=5); break; case 'speed-up': videos.forEach(v=>v.playbackRate=Math.min(v.playbackRate+0.5,4)); break; case 'speed-down': videos.forEach(v=>v.playbackRate=Math.max(v.playbackRate-0.5,0.25)); break;}})()" } } Ws.Send(Command.Stringify()) Ws.Close() } ; StreamDeck绑定的函数示例 PlayVideo() { ControlChromeVideo("play") } PauseVideo() { ControlChromeVideo("pause") } ForwardVideo() { ControlChromeVideo("forward") }
方案3:修改本地服务CORS配置(不推荐)
如果坚持用原生fetch/XHR,可在Express服务中添加CORS规则允许Tampermonkey来源:
const cors = require('cors'); app.use(cors({ origin: function(origin, callback) { if (!origin || origin.includes('tampermonkey.net')) { callback(null, true); } else { callback(new Error('Not allowed by CORS')); } } }));
此方案需适配不同网站的origin,不如Tampermonkey特权API省心。
内容的提问来源于stack exchange,提问作者FlexXx
相关产品推荐
相关产品推荐

