ScriptCat / Tampermonkey 用 GM_xmlhttpRequest 调用 AI 视觉接口

为什么必须用 GM_xmlhttpRequest

用户脚本环境里调用第三方 API,普通 fetch 会被浏览器 CORS 限制卡死。GM_xmlhttpRequest 是油猴环境提供的跨域请求接口,可以绕过这个限制:

// ❌ 这样会报 CORS 错误
fetch("https://open.bigmodel.cn/api/paas/v4/chat/completions", {...})

// ✅ 用 GM_xmlhttpRequest
GM_xmlhttpRequest({
  method: "POST",
  url: "https://open.bigmodel.cn/api/paas/v4/chat/completions",
  ...
})

脚本头部必须声明权限:

// @grant  GM_xmlhttpRequest

完整示例:调用视觉模型分析页面图片

// ==UserScript==
// @name         AI 图片分析
// @namespace    http://tampermonkey.net/
// @version      1.0
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function () {
  const API_KEY = "your-api-key"; // 换成你的 key

  function getImagesFromPage() {
    const imgs = document.querySelectorAll("img");
    return Array.from(imgs)
      .slice(0, 3)
      .map(img => ({
        type: "image_url",
        image_url: {
          url: img.src || img.dataset.src  // 处理懒加载
        }
      }))
      .filter(item => item.image_url.url);
  }

  function requestAI(imageItems) {
    GM_xmlhttpRequest({
      method: "POST",
      url: "https://open.bigmodel.cn/api/paas/v4/chat/completions",
      headers: {
        "Content-Type": "application/json",
        "Authorization": "Bearer " + API_KEY
      },
      data: JSON.stringify({
        model: "glm-4v",
        messages: [
          {
            role: "user",
            content: [
              ...imageItems,
              {
                type: "text",
                text: "请分析这些图片的内容"
              }
            ]
          }
        ]
      }),
      onload: function (res) {
        try {
          const data = JSON.parse(res.responseText);
          const answer = data?.choices?.[0]?.message?.content;
          console.log("AI 回答:", answer);
          alert(answer);
        } catch (e) {
          console.error("解析失败", e);
        }
      },
      onerror: function (err) {
        console.error("请求失败", err);
      }
    });
  }

  const images = getImagesFromPage();
  if (images.length > 0) {
    requestAI(images);
  }
})();

图片来源处理

指定区域内的图片

function getQuestionImages() {
  const container = document.querySelector(".question-area");
  if (!container) return [];

  return Array.from(container.querySelectorAll("img")).map(img => ({
    type: "image_url",
    image_url: {
      url: new URL(img.getAttribute("src") || "", location.href).href
    }
  }));
}

new URL(src, location.href).href 可以把相对路径补全为绝对 URL。

canvas 图片转 base64

页面是 canvas 渲染(反爬、PDF 等)时,需要截图再传:

function getCanvasImage() {
  const canvas = document.querySelector("canvas");
  if (!canvas) return [];

  return [{
    type: "image_url",
    image_url: {
      url: canvas.toDataURL("image/png")  // data:image/png;base64,...
    }
  }];
}

大多数视觉模型 API 同时支持 URL 和 base64 格式的图片。

常见问题

GM_xmlhttpRequest is not a function
脚本头部没有声明 @grant GM_xmlhttpRequest

图片 URL 404 / 模型报错

  • 图片 URL 必须能公网访问,localhostblob:file:// 都不行
  • 带签名的临时 URL(如 S3 presigned URL)容易过期,建议转 base64

@grant none@grant GM_xmlhttpRequest 冲突
用了 GM API 就不能同时 @grant none,两者互斥。

绑定到按钮

把 AI 调用绑定到页面按钮,点击时分析当前可见的图片:

const btn = document.createElement("button");
btn.textContent = "AI 分析";
Object.assign(btn.style, {
  position: "fixed",
  right: "20px",
  bottom: "20px",
  padding: "10px 18px",
  background: "#2563eb",
  color: "#fff",
  border: "none",
  borderRadius: "8px",
  cursor: "pointer",
  zIndex: 9999
});
btn.onclick = () => requestAI(getImagesFromPage());
document.body.appendChild(btn);