# 脚本开发文档 v2.3 ## 目录 1. 概述 2. 快速开始 3. 环境检测 4. GM API 标准兼容 5. 环境定制 API(新增) 6. 特权对象:cyc_privileges 7. 特权对象:cyc_http 8. 特权对象:cyc_monitor 9. 完整脚本示例 10. 限制与注意事项 --- ## 1. 概述 Cat Browser 为油猴脚本(UserScript)提供了一个增强的运行时环境。除了兼容标准 `GM_*` API 外,还额外提供三个特权对象和两个环境定制 API,让脚本可以调用 App 的原生能力,实现普通浏览器做不到的功能。 **三个特权对象:** | 对象名 | 用途 | 注入条件 | |--------|------|----------| | `__cyc_privileges__` | UI 交互、资源嗅探、视频悬浮窗、下载、调试开关 | 仅 http:// / https:// | | `__cyc_http__` | 原生跨域 HTTP 请求 | 仅 http:// / https:// | | `__cyc_monitor__` | 页面网络请求监控 | 仅 http:// / https:// | **两个环境定制 API:** | 方法 | 用途 | 可用条件 | |------|------|----------| | `GM_getEnvironment()` | 获取当前应用的伪装环境名称 | 始终可用 | | `GM_setEnvironment(name)` | 临时切换伪装环境 | 始终可用 | **兼容性承诺:** 所有特权对象在普通浏览器(Chrome + Tampermonkey 等)中均为 `undefined`,脚本只需做一次存在性检查即可实现降级兼容。 **支持的标准:** Cat Browser 兼容主流 `GM_*` API,无需在 `@grant` 中声明即可直接使用 `GM_setValue`、`GM_xmlhttpRequest` 等全部方法。 --- ## 2. 快速开始 最小可运行脚本模板:
javascript
// ==UserScript==
// @name 我的脚本
// @namespace my-script
// @version 1.0.0
// @description 脚本描述
// @author 作者名
// @match :///*
// @grant none
// ==/UserScript==
(function() {
'use strict';// 环境检测 const isCyc = typeof __cyc_privileges__ !== 'undefined'; if (isCyc) { __cyc_privileges__.toast('Cat Browser 增强环境已就绪'); } else { console.log('普通浏览器环境,部分功能不可用'); } // 你的脚本逻辑...
})();关键说明: · @match *://*/* 让脚本在所有 HTTP/HTTPS 页面运行 · @grant none 表示不申请额外权限(Cat Browser 已默认开放所有 GM API) · 所有代码包裹在 IIFE (function(){ … })() 中,避免污染全局作用域 · 'use strict' 启用严格模式,减少潜在错误 --- 3. 环境检测 3.1 检测是否在 Cat Browser 中运行
javascript
const isCatBrowser = typeof cyc_privileges !== 'undefined';3.2 检测是否在 HTTP/HTTPS 页面 特权对象仅在 HTTP/HTTPS 页面注入,file:// 或 about:blank 不可用。
javascript
const isHttpPage = window.location.protocol === 'http:' || window.location.protocol === 'https:';3.3 完整的环境信息
javascript
const env = {
isCatBrowser: typeof cyc_privileges !== 'undefined',
isHttpPage: /^https?:/.test(window.location.href),
hasPrivileges: typeof cyc_privileges !== 'undefined',
hasHttp: typeof cyc_http !== 'undefined',
hasMonitor: typeof cyc_monitor !== 'undefined',
hasGMApi: typeof GM_setValue === 'function',
hasEnvApi: typeof GM_getEnvironment === 'function',
url: window.location.href,
currentEnvironment: typeof GM_getEnvironment === 'function' ? GM_getEnvironment() : '不可用',
gmInfo: typeof GM_info !== 'undefined' ? GM_info : null
};
console.log('环境信息:', JSON.stringify(env, null, 2));--- 4. GM API 标准兼容 Cat Browser 兼容主流 GM_* API,无需在 @grant 中声明即可直接使用。 4.1 存储类 方法 参数 返回值 说明 GM_setValue(key, value) key: 字符串, value: 任意类型(自动序列化) 无 持久存储键值对 GM_getValue(key, defaultValue) key: 字符串, defaultValue: 任意类型 存储的值或默认值 读取存储的值 GM_deleteValue(key) key: 字符串 无 删除存储的键 GM_listValues() 无 字符串数组 获取所有存储的键名
javascript
// 存储复杂对象
GM_setValue('myKey', { name: 'test', count: 42 });
// 读取(带默认值)
const data = GM_getValue('myKey', { name: '', count: 0 });
console.log(data.name, data.count); // "test", 42
// 删除
GM_deleteValue('myKey');
// 列出所有键
const keys = GM_listValues();
console.log('已存储的键:', keys);4.2 样式注入 方法 参数 说明 GM_addStyle(css) css: CSS 字符串 向页面注入样式
javascript
GM_addStyle( .my-highlight { background: yellow !important; border: 2px solid red; } );4.3 网络请求 方法 参数 说明 GM_xmlhttpRequest(details) details: 配置对象 发起跨域请求(兼容标准 GM API) details 配置项: 属性 类型 必填 说明 url string 是 请求地址 method string 否 请求方法,默认 GET headers object 否 请求头 data string 否 请求体 timeout number 否 超时毫秒数 onload function 否 成功回调 onerror function 否 失败回调
javascript
GM_xmlhttpRequest({
url: 'https://api.example.com/data',
method: 'GET',
headers: { 'X-Custom': 'value' },
timeout: 10000,
onload: function(response) {
console.log('状态:', response.status);
console.log('响应:', response.responseText);
console.log('响应头:', response.responseHeaders);
},
onerror: function(error) {
console.error('请求失败:', error);
}
});4.4 其他方法 方法 说明 GM_log(message) 输出日志到 App 控制台 GM_setClipboard(text) 复制文本到系统剪贴板 GM_openInTab(url, background) 在新标签页打开 URL(background: true 后台打开) GM_notification(details, onclick) 显示浏览器通知 GM_info 获取脚本管理器信息对象 GM_registerMenuCommand(caption, callback) 注册菜单命令 GM_getResourceText(name) 获取 @resource 定义的文本资源内容 GM_getResourceURL(name) 获取 @resource 定义的资源 URL
javascript
// 日志
GM_log('调试信息');
// 复制到剪贴板
GM_setClipboard('已复制的文本');
// 通知
GM_notification({ title: '标题', text: '内容' });
// 脚本管理器信息
console.log(GM_info.scriptHandler); // "Cat Browser"
console.log(GM_info.version); // "1.0.0"
// 菜单命令
GM_registerMenuCommand('我的功能', function() {
alert('菜单被点击');
});--- 5. 环境定制 API(新增) Cat Browser 提供了环境定制功能,允许脚本查询和临时切换当前页面的伪装环境。 5.1 GM_getEnvironment() · 参数:无 · 返回值:字符串,当前应用的环境名称(如 "微信环境"),未应用环境时返回 "默认" · 说明:用于检测当前页面是否处于环境伪装状态,方便脚本根据环境名称调整行为
javascript
const currentEnv = GM_getEnvironment();
console.log('当前环境:', currentEnv);
if (currentEnv === '微信环境') {
// 在微信环境下执行特定逻辑
console.log('检测到微信环境,启用微信专用功能');
}5.2 GM_setEnvironment(name) · 参数:name — 字符串,要临时切换的环境名称 · 返回值:无 · 说明:临时切换当前 WebView 的环境(仅对当前标签页生效,刷新页面后恢复全局域名规则匹配)
javascript
// 根据域名自动切换环境
if (location.hostname.includes('weixin.qq.com')) {
GM_setEnvironment('微信环境');
} else if (location.hostname.includes('bilibili.com')) {
GM_setEnvironment('哔哩哔哩环境');
}
// 验证切换结果
const newEnv = GM_getEnvironment();
console.log('切换后环境:', newEnv);注意:GM_setEnvironment 仅对当前页面生效,不会改变全局环境配置。页面刷新后恢复为按域名规则匹配的全局环境。如果指定名称的环境不存在或未启用,切换无效。 5.3 与特权对象的配合使用 环境定制 API 可以与其他特权对象配合使用,实现更强大的功能:
javascript
// 在特定环境下启用资源嗅探
if (GM_getEnvironment() === '哔哩哔哩环境' && typeof cyc_privileges !== 'undefined') {
// 订阅视频资源更新
window.__cyc_on_resource_update = function(type, urls) {
if (type === 'video') {
console.log('B站视频资源:', urls);
}
};
}--- 6. 特权对象:cyc_privileges 注入条件: 仅在 http:// 和 https:// 页面可用。 6.1 UI 交互 toast(message) · 参数:message — 字符串,要显示的文本 · 返回值:无 · 说明:弹出 Android 原生短 Toast(约 2 秒),线程安全 toastLong(message) · 参数:message — 字符串,要显示的文本 · 返回值:无 · 说明:弹出 Android 原生长 Toast(约 3.5 秒),线程安全
javascript
cyc_privileges.toast('操作完成');
cyc_privileges.toastLong('这是一条较长的提示信息');6.2 资源嗅探(阶段 B) getSniffedVideos() · 参数:无 · 返回值:JSON 字符串,解析后为数组 [{url, mimeType, size}, …] · 说明:获取当前页面已检测到的视频资源 getSniffedAudios() · 参数:无 · 返回值:JSON 字符串,解析后为数组 · 说明:获取当前页面已检测到的音频资源 getSniffedImages() · 参数:无 · 返回值:JSON 字符串,解析后为数组 · 说明:获取当前页面已检测到的图片资源 返回值对象结构: 字段 类型 说明 url string 资源 URL mimeType string MIME 类型(如 video/mp4) size number 文件大小(字节,-1 表示未知)
javascript
const videos = JSON.parse(cyc_privileges.getSniffedVideos());
console.log('检测到 ' + videos.length + ' 个视频');
videos.forEach(function(v, i) {
console.log('视频 ' + (i+1) + ':', v.url, v.mimeType, v.size + '字节');
});资源更新事件:window.__cyc_on_resource_update · 回调参数: · type — 字符串,资源类型:"video" | "audio" | "image" · urls — 字符串数组,新增的资源 URL · 说明:当页面新加载了匹配的资源时自动触发
javascript
window.__cyc_on_resource_update = function(type, urls) {
console.log('新发现 ' + type + ': ' + urls.length + ' 个');
if (type === 'video') {
urls.forEach(function(url) { console.log(' ' + url); });
}
};6.3 视频悬浮窗(阶段 C) getCurrentVideo() · 参数:无 · 返回值:字符串,当前检测到的第一个视频 URL,无视频时返回空字符串 "" · 说明:获取页面上检测到的视频地址 playInFloatWindow(url) · 参数:url — 视频 URL · 返回值:无 · 说明:将指定视频在系统悬浮窗中播放
javascript
const videoUrl = cyc_privileges.getCurrentVideo();
if (videoUrl) {
cyc_privileges.playInFloatWindow(videoUrl);
cyc_privileges.toast('已开启悬浮窗播放');
} else {
cyc_privileges.toast('当前页面未检测到视频');
}注意事项: · 悬浮窗需要系统"悬浮窗权限",Cat Browser 通常会引导用户授权 · 视频格式需为系统播放器支持的格式(mp4、m3u8、ts 等) · 如果页面有多个视频,getCurrentVideo() 返回第一个检测到的 6.4 下载能力(阶段 D) download(url, filename, optionsJson) · 参数: · url — 下载地址 · filename — 保存的文件名 · optionsJson — 可选,JSON 字符串,包含额外选项: · convertTo — 目标格式(不含点号),如 "mp4"、"mp3"、"png"。不传或为 null 则不转换 · 返回值:无 · 说明:触发原生下载管理器下载文件 batchDownload(urlsJson) · 参数:urlsJson — JSON 字符串数组,要批量下载的 URL 列表 · 返回值:无 · 说明:批量触发下载,会弹出确认提示
javascript
// 普通下载(不转换格式)
cyc_privileges.download('https://example.com/video.m4s', '精彩视频.m4s');
// 下载后自动转换为 MP4(视频)
cyc_privileges.download('https://example.com/video.m4s', '精彩视频.mp4',
JSON.stringify({ convertTo: 'mp4' }));
// 下载后自动转换为 MP3(音频)
cyc_privileges.download('https://example.com/audio.m4s', '背景音乐.mp3',
JSON.stringify({ convertTo: 'mp3' }));
// 图片格式转换
cyc_privileges.download('https://example.com/image.webp', '图片.png',
JSON.stringify({ convertTo: 'png' }));
// 批量下载
const urls = ['https://example.com/img1.jpg', 'https://example.com/img2.jpg'];
cyc_privileges.batchDownload(JSON.stringify(urls));支持的转换格式: 类别 支持的输出格式 视频 mp4、mkv、avi、mov、webm、flv、3gp 音频 mp3、m4a、aac、ogg、opus、flac、wav、wma 图片 png、jpg、jpeg、webp、bmp、gif、tiff 其他 先尝试流复制,失败则使用通用编码器 转换策略: 1. 第 1 次:流复制(-c copy),无损且极快 2. 第 2 次:根据目标格式选择合适的编码器重新编码 3. 第 3 次:降级保底参数(更低码率、更快预设),确保不超时 注意事项: · optionsJson 必须使用 JSON.stringify() 序列化,不能直接传 JS 对象 · 转换在后台进行,用户会在下载列表中看到"转换中"状态 · 转换失败时原文件保留,任务显示在已完成列表并提示"格式转换失败,原文件已保留" · 转换过程中不可暂停,但可取消 6.5 网络调试(阶段 A) setRecordBody(enabled) · 参数:enabled — 布尔值,是否开启响应体记录 · 返回值:无 · 说明:开启后,cyc_monitor 的回调中会包含 responseBody 字段
javascript
// 开启响应体记录(会影响少量性能,调试完建议关闭)
cyc_privileges.setRecordBody(true);
// 关闭
cyc_privileges.setRecordBody(false);--- 7. 特权对象:cyc_http 注入条件: 仅在 http:// 和 https:// 页面可用。 发起完全不受浏览器同源策略、CORS 限制、SameSite Cookie 策略限制的 HTTP 请求。使用 OkHttp 原生引擎,性能优于 GM_xmlhttpRequest。 request(requestId, optionsJson) · 参数: · requestId — 整数,自定义请求 ID(用于在回调中区分不同请求) · optionsJson — JSON 字符串,包含: · url — 必填,请求地址 · method — 可选,GET/POST/PUT/DELETE 等,默认 "GET" · headers — 可选,请求头对象,默认 {} · body — 可选,请求体字符串(POST 时使用) · timeout — 可选,超时毫秒数,默认 10000 · 返回值:无(异步,通过回调获取结果) · 回调:需全局定义 window.__cyc_http_callback = function(requestId, success, resultJson) 回调参数说明: 参数 类型 说明 requestId number 请求 ID,对应 request() 调用时的 ID success boolean 请求是否成功(网络层无异常) resultJson string JSON 字符串,解析后包含 status、responseText 等 resultJson 解析后的字段: 字段 类型 说明 status number HTTP 状态码(0 表示网络错误) statusText string 状态文本或错误信息 responseText string 响应体文本 responseHeaders object 响应头键值对
javascript
// 1. 定义全局回调
window.__cyc_http_callback = function(requestId, success, resultJson) {
const result = JSON.parse(resultJson);
if (requestId === 1) {
if (success && result.status === 200) {
const data = JSON.parse(result.responseText);
console.log('请求成功:', data);
console.log('响应头:', result.responseHeaders);
} else {
console.error('请求失败:', result.status, result.statusText);
}
}
};
// 2. 发起 GET 请求
cyc_http.request(1, JSON.stringify({
url: 'https://jsonplaceholder.typicode.com/posts/1',
method: 'GET',
headers: { 'X-Custom-Header': 'my-value' },
timeout: 10000
}));
// 3. POST 请求示例
cyc_http.request(2, JSON.stringify({
url: 'https://api.example.com/submit',
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'test', value: 123 }),
timeout: 15000
}));与 GM_xmlhttpRequest 的区别: 特性 cyc_http.request() GM_xmlhttpRequest() 引擎 OkHttp(原生层) WebView 内部 CORS 完全不受限制 受浏览器策略限制 Cookie 不受 SameSite 限制 受 Cookie 策略限制 回调方式 全局单回调函数 每个请求独立回调 --- 8. 特权对象:cyc_monitor 注入条件: 仅在 http:// 和 https:// 页面可用。 实时监控页面发出的所有网络请求,采用"订阅制"模式,脚本需先注册关心的 URL 模式。 8.1 订阅管理方法 subscribe(scriptId, subscriptionId, pattern) · 参数: · scriptId — 脚本 ID(整数,用于区分不同脚本,可自定义) · subscriptionId — 订阅 ID(字符串,用于后续取消订阅) · pattern — URL 匹配模式(字符串,忽略大小写,包含即匹配) · 返回值:无 · 限制:每个脚本最多 10 个订阅,全局最多 100 个 unsubscribe(scriptId, subscriptionId) · 参数:与 subscribe 一致 · 返回值:无 unsubscribeAll(scriptId) · 参数:scriptId — 脚本 ID · 返回值:无 · 说明:取消该脚本的所有订阅 8.2 回调:window.__cyc_on_request 回调参数: info 对象,包含以下字段: 字段 类型 说明 scriptId number 脚本 ID subscriptionId string 匹配的订阅 ID url string 请求 URL(敏感参数已擦除) method string 请求方法(GET/POST 等) status number 响应状态码(-1 表示仅请求阶段) duration number 请求耗时(毫秒) requestHeaders object 请求头键值对 responseHeaders object 响应头键值对 responseBody string 响应体文本(需开启 setRecordBody(true))
javascript
// 1. 定义回调
window.__cyc_on_request = function(info) {
console.log('══════════════════════════════');
console.log('请求方法:', info.method);
console.log('请求 URL:', info.url);
console.log('状态码:', info.status);
console.log('耗时:', info.duration + 'ms');
console.log('请求头:', JSON.stringify(info.requestHeaders));
console.log('响应头:', JSON.stringify(info.responseHeaders));
if (info.responseBody) {
console.log('响应体 (前100字符):', info.responseBody.substring(0, 100));
}
};
// 2. 开启响应体记录(阶段 A)
cyc_privileges.setRecordBody(true);
// 3. 订阅特定 API 接口
cyc_monitor.subscribe(1, 'api-monitor', '/api/');
// 4. 订阅所有请求(调试用,注意性能)
cyc_monitor.subscribe(1, 'all-requests', '');
// 5. 取消单个订阅
cyc_monitor.unsubscribe(1, 'api-monitor');
// 6. 取消全部订阅
cyc_monitor.unsubscribeAll(1);敏感参数保护: 以下参数名会自动替换为 ***:token, access_token, refresh_token, key, api_key, secret, password, auth, sign, signature 等 20+ 个常见敏感参数名。 --- 9. 完整脚本示例 9.1 万能视频小窗播放器
javascript
// ==UserScript==
// @name 万能视频小窗播放器
// @match :///*
// @grant none
// ==/UserScript==
(function() {
'use strict';
if (typeof cyc_privileges === 'undefined') return;// 监听资源更新 window.__cyc_on_resource_update = function(type, urls) { if (type !== 'video' || urls.length === 0) return; showFloatButton(urls[0]); }; // 页面加载后检查已有视频 setTimeout(function() { const videoUrl = __cyc_privileges__.getCurrentVideo(); if (videoUrl) showFloatButton(videoUrl); }, 2000); function showFloatButton(videoUrl) { if (document.getElementById('cyc-float-btn')) return; const btn = document.createElement('div'); btn.id = 'cyc-float-btn'; btn.textContent = '▶️ 小窗播放'; btn.style.cssText = 'position:fixed;bottom:20px;right:20px;z-index:99999;background:#ff6600;color:#fff;padding:12px 18px;border-radius:25px;cursor:pointer;font-size:15px;font-family:sans-serif;box-shadow:0 4px 15px rgba(0,0,0,0.3);'; btn.onclick = function() { __cyc_privileges__.playInFloatWindow(videoUrl); __cyc_privileges__.toast('已开启悬浮窗播放'); }; document.body.appendChild(btn); }
})();9.2 网络请求实时监控面板
javascript
// ==UserScript==
// @name 网络请求实时监控
// @match :///*
// @grant none
// ==/UserScript==
(function() {
'use strict';
if (typeof cyc_monitor === 'undefined') return;if (typeof __cyc_privileges__ !== 'undefined') { __cyc_privileges__.setRecordBody(true); } const panel = document.createElement('div'); panel.style.cssText = 'position:fixed;bottom:10px;right:10px;z-index:99999;background:rgba(0,0,0,0.9);color:#0f0;font-size:11px;font-family:monospace;padding:10px;border-radius:6px;max-width:400px;max-height:40vh;overflow-y:auto;min-width:250px;'; panel.innerHTML = ' 网络监控<br>'; document.body.appendChild(panel); const maxLines = 20; const lines = []; window.__cyc_on_request = function(info) { const color = info.status >= 400 ? '#f44' : info.status >= 200 ? '#4f4' : '#ff4'; const text = `${info.method.toUpperCase()} ${info.status} ${info.url.substring(0, 60)} ${info.duration}ms`; lines.push(text); if (lines.length > maxLines) lines.shift(); panel.innerHTML = ' 网络监控<br>' + lines.join('<br>'); }; __cyc_monitor__.subscribe(999, 'ui-monitor', '');
})();9.3 批量下载页面图片
javascript
// ==UserScript==
// @name 批量下载页面图片
// @match :///*
// @grant none
// ==/UserScript==
(function() {
'use strict';
if (typeof cyc_privileges === 'undefined') {
alert('请使用 Cat Browser 运行此脚本');
return;
}const images = JSON.parse(__cyc_privileges__.getSniffedImages()); if (images.length === 0) { __cyc_privileges__.toast('未检测到图片'); return; } const urls = images.map(function(img) { return img.url; }); __cyc_privileges__.batchDownload(JSON.stringify(urls)); __cyc_privileges__.toast('开始下载 ' + urls.length + ' 张图片');
})();9.4 B站视频下载(带格式转换)
javascript
// ==UserScript==
// @name B站全格式下载(自动转码)
// @match ://.bilibili.com/video/*
// @match ://bilibili.com/video/
// @grant none
// ==/UserScript==
(function() {
'use strict';
if (typeof cyc_privileges === 'undefined') return;let currentCookie = document.cookie; function getBvid() { let m = location.href.match(/\/video\/(BV\w+)/i); return m ? m[1] : null; } function downloadFile(url, filename, type) { let convertTo = null; if (filename.endsWith('.m4s')) { if (type === 'video') { filename = filename.replace(/\.m4s$/, '.mp4'); convertTo = 'mp4'; } else { filename = filename.replace(/\.m4s$/, '.mp3'); convertTo = 'mp3'; } } if (convertTo) { __cyc_privileges__.download(url, filename, JSON.stringify({ convertTo: convertTo })); } else { __cyc_privileges__.download(url, filename); } } async function fetchStreams() { const bvid = getBvid(); const viewResp = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: `https://api.bilibili.com/x/web-interface/view?bvid=${bvid}`, headers: { 'Referer': 'https://www.bilibili.com/', 'Cookie': currentCookie }, onload: resolve, onerror: reject }); }); const viewData = JSON.parse(viewResp.responseText); const cid = viewData.data.cid; const title = viewData.data.title; const streamResp = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: `https://api.bilibili.com/x/player/playurl?bvid=${bvid}&cid=${cid}&qn=127&fnval=4048&fourk=1`, headers: { 'Referer': 'https://www.bilibili.com/', 'Cookie': currentCookie }, onload: resolve, onerror: reject }); }); const data = JSON.parse(streamResp.responseText); const dash = data.data.dash; return { videos: dash.video.map(v => ({ url: v.baseUrl, desc: `${v.height}p ${(v.bandwidth/1000).toFixed(0)}kbps ${v.codecs.split('.')[0]}` })), audios: dash.audio.map(a => ({ url: a.baseUrl, desc: `${(a.bandwidth/1000).toFixed(0)}kbps ${a.codecs.split('.')[0]}` })), title }; } // 悬浮球 UI 和面板逻辑...
})();9.5 自动环境切换(新增)
javascript
// ==UserScript==
// @name 自动环境切换
// @match :///*
// @grant none
// ==/UserScript==
(function() {
'use strict';
if (typeof GM_getEnvironment === 'undefined') return;const host = location.hostname; // 根据域名自动切换环境 if (host.includes('weixin.qq.com') || host.includes('qq.com')) { GM_setEnvironment('微信环境'); } else if (host.includes('bilibili.com')) { GM_setEnvironment('哔哩哔哩环境'); } else if (host.includes('tieba.baidu.com')) { GM_setEnvironment('贴吧环境'); } else if (host.includes('alipay.com') || host.includes('taobao.com')) { GM_setEnvironment('支付宝环境'); } else if (host.includes('douyin.com')) { GM_setEnvironment('抖音环境'); } else if (host.includes('jd.com')) { GM_setEnvironment('京东环境'); } else if (host.includes('weibo.com')) { GM_setEnvironment('微博环境'); } // 显示当前环境 const env = GM_getEnvironment(); if (env !== '默认' && typeof __cyc_privileges__ !== 'undefined') { setTimeout(function() { __cyc_privileges__.toast(' 已切换至:' + env); }, 2000); }
})();--- 10. 限制与注意事项 10.1 注入限制 限制项 说明 协议限制 仅在 http:// 和 https:// 页面可用,file:// 或 about:blank 不可用 注入时机 特权对象在页面开始加载后注入 每页一次 每个页面只注入一次,刷新页面后重新注入 10.2 监控限制 限制项 说明 单脚本订阅数 最多 10 个 URL 模式 全局订阅数 总计最多 100 个 响应体大小 最大 50KB,超过部分截断 响应体类型 仅文本类型(HTML/JSON/XML/JS 等),二进制类型不包含 敏感参数保护 URL 中的 token、key、password 等自动擦除为 *** 10.3 下载与转换限制 限制项 说明 转换引擎 使用 FFmpeg Kit,支持主流音视频/图片格式 转换策略 优先流复制(无损快速),失败后降级重新编码 转换时长 视频重新编码可能较慢(取决于文件大小和设备性能) 转换失败 原文件保留,任务进入已完成列表并显示提示 optionsJson 必须使用 JSON.stringify() 序列化,不能直接传 JS 对象 10.4 环境定制 API 限制 限制项 说明 临时切换 GM_setEnvironment 仅对当前标签页生效 刷新恢复 页面刷新后恢复为按域名规则匹配的全局环境 环境不存在 指定名称的环境不存在或未启用时,切换无效 总开关依赖 环境定制总开关必须开启,否则环境 API 返回"默认" 10.5 最佳实践 实践 说明 环境检测 始终用 typeof __cyc_privileges__ !== 'undefined' 检测 降级处理 在普通浏览器中提供替代方案或提示用户使用 Cat Browser @grant none Cat Browser 已默认开放所有 GM API,不需要额外声明 资源释放 脚本禁用/卸载时建议调用 unsubscribeAll() 清理订阅 性能考虑 不要在页面高频事件(如 scroll、mousemove)中频繁调用特权方法 响应体调试 调试完成后记得 setRecordBody(false) 关闭响应体记录 作用域隔离 始终将脚本代码包裹在 (function(){ 'use strict'; … })() 中 JSON 序列化 download() 的第三个参数必须使用 JSON.stringify() 格式兼容 转换格式建议使用通用格式(mp4、mp3、png),避免小众格式 @require 仅支持 HTTPS URL 环境查询 使用 GM_getEnvironment() 判断当前是否处于伪装环境 环境切换 使用 GM_setEnvironment(name) 临时切换,刷新后恢复 --- 此文档涵盖 Cat Browser 全部脚本开发能力,包括新增的环境定制 API。脚本作者可直接复制示例代码,替换 URL 和参数即可开发出可用的脚本。
看了你的文档,整体结构清晰、内容详实,但确实有几个地方需要更新。
---
需要更新的地方
1. 阶段 D:下载能力暴露 —— 缺少 downloadBlob
你的 CycPrivilegesBridge 已新增了 downloadBlob 方法,但文档里没有提到。
建议在 6.4 节 download 方法后面增加:
```markdown
#### downloadBlob(base64Data, fileName, mimeType)
* **参数:**
* `base64Data` — Base64 编码的数据(**不含** `data:...;base64,` 前缀)
* `fileName` — 保存的文件名
* `mimeType` — 文件的 MIME 类型,如 `"video/mp4"`、`"image/png"`
* **返回值:** 无
* **说明:** 将 Base64 编码的数据直接写入文件。用于下载网页中的 Blob 资源(如视频切片、加密内容等),脚本需要先通过 JS 将 Blob 读取为 Base64。
```javascript
// 先通过 JS 将 Blob 转为 Base64
async function downloadBlobUrl(blobUrl, fileName) {
const response = await fetch(blobUrl);
const blob = await response.blob();
const reader = new FileReader();
reader.onloadend = () => {
const base64 = reader.result.split(',')[1]; // 去掉 data:... 前缀
__cyc_privileges__.downloadBlob(base64, fileName, blob.type);
};
reader.readAsDataURL(blob);
}
// 使用
downloadBlobUrl('blob:http://...', 'video.mp4');
```
注意事项:
· base64Data 不能包含 data:image/png;base64, 这类前缀,必须是纯 Base64 字符串。
· 大文件(如长视频)转 Base64 可能消耗大量内存,建议仅用于小文件或分片下载场景。
· 文件保存到系统 Download 目录,保存成功后会触发系统相册/文件管理器扫描。
```
---
### 2. 限制与注意事项 —— 下载与转换限制需要补充
**在 10.3 节"下载与转换限制"表格中增加一行:**
```markdown
| Blob 下载 | 需脚本先将 Blob 转为 Base64,再调用 downloadBlob,大文件慎用 |
```
---
总结
更新项 位置 内容
新增 downloadBlob 方法文档 6.4 节 参数说明、示例代码、注意事项
补充 Blob 下载限制 10.3 节 大文件慎用的提醒
其余内容(GM_getEnvironment、GM_setEnvironment、三个特权对象的其他方法)文档中已正确覆盖,无需修改。



Comments NOTHING