用户脚本(UserScript)开发技能

SkillDev tools

Skill for writing User.JS user scripts

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the 用户脚本(UserScript)开发技能 skill

What this skill tells your AI

The instructions your AI receives, as published by ckylinmc/userjs in .agents/skills/userscript/SKILL.md and read by ahel’s review.

本技能文档基于作者的实际脚本编写经验,包含Tampermonkey脚本开发的核心知识、编码规范和最佳实践。

目录

  1. 脚本结构规范
  2. 元数据配置
  3. 编码风格指南
  4. 常用工具函数
  5. GM_* API 使用
  6. DOM 操作技巧
  7. 库文件使用
  8. 调试技巧
  9. 最佳实践

脚本结构规范

基本结构模板

// ==UserScript==
// @name         脚本名称
// @name:zh-CN   脚本中文名称
// @namespace    ckylin-script-命名空间
// @version      0.1.0
// @description  脚本描述
// @description:zh-CN 中文描述
// @author       CKylinMC
// @match        https://example.com/*
// @grant        unsafeWindow
// @grant        GM_getValue
// @grant        GM_setValue
// @license      GPL-3.0-only
// @icon         https://example.com/favicon.ico
// @run-at       document-end
// ==/UserScript==

(function(unsafeWindow, document) {
    'use strict';

    // #region 工具函数
    const wait = t => new Promise(r => setTimeout(r, t));
    const get = q => document.querySelector(q);
    const getAll = q => [...document.querySelectorAll(q)];
    const log = (...args) => console.log('[脚本名]', ...args);
    // #endregion

    // #region 主要功能
    async function init() {
        // 初始化代码
    }
    // #endregion

    // 启动脚本
    init();

})(unsafeWindow, document);

结构说明

  1. IIFE 包装:使用立即执行函数表达式,接收 unsafeWindowdocument 作为参数
  2. 严格模式:始终使用 'use strict'; 启用严格模式
  3. 代码分区:使用 #region#endregion 注释组织代码块
  4. 工具函数优先:在文件顶部定义常用工具函数
  5. 异步处理:优先使用 async/await 而非回调

元数据配置

必需的元数据标签

// @name         脚本名称(必需)
// @namespace    唯一命名空间(必需)
// @version      版本号(必需,格式: 主.次.修订)
// @match        匹配的URL(必需,推荐使用@match而非@include)
// @grant        需要的权限(必需,至少指定unsafeWindow或none)

推荐的可选标签

// @name:zh-CN   中文名称(国际化)
// @description  英文描述
// @description:zh-CN 中文描述
// @author       作者名
// @license      授权协议(推荐:GPL-3.0-only 或 Apache-2.0)
// @icon         脚本图标URL
// @supportURL   支持页面URL(GitHub仓库)
// @updateURL    更新检测URL
// @downloadURL  下载URL
// @run-at       运行时机(document-start/body/end/idle)

依赖和资源

// 引用外部库
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @require      https://update.greasyfork.org/scripts/脚本ID/库名.js

// 引用本地库(推荐方式)
// @require      https://update.greasyfork.org/scripts/564901/1749919/CKUI.js
// @require      https://greasyfork.org/scripts/429720-cktools/code/CKTools.js

// 预加载资源
// @resource     iconURL https://example.com/icon.png
// @resource     cssFile https://example.com/style.css

// 跨域请求白名单
// @connect      api.example.com
// @connect      self
// @connect      *

@match 和 @include 的使用

// 推荐:使用 @match(更安全,语法严格)
// @match        https://www.bilibili.com/*
// @match        https://*.bilibili.com/*
// @match        https://www.bilibili.com/video/*

// 备选:使用 @include(支持正则)
// @include      /^https:\/\/www\.bilibili\.com\/.*$/
// @include      https://www.bilibili.com/*

// 排除特定页面
// @exclude      https://www.bilibili.com/blacklist/*

@run-at 运行时机

// @run-at document-start  // 页面开始加载时(最早,DOM可能不完整)
// @run-at document-body   // body元素存在时
// @run-at document-end    // DOM加载完成时(推荐)
// @run-at document-idle   // 页面完全加载后(默认值)
// @run-at context-menu    // 右键菜单触发时

@grant 权限声明

// 常用权限
// @grant        unsafeWindow              // 访问页面window对象
// @grant        GM_getValue               // 读取存储
// @grant        GM_setValue               // 写入存储
// @grant        GM_deleteValue            // 删除存储
// @grant        GM_listValues             // 列出所有键
// @grant        GM_addStyle               // 添加CSS
// @grant        GM_registerMenuCommand    // 注册菜单命令
// @grant        GM_unregisterMenuCommand  // 注销菜单命令
// @grant        GM_xmlhttpRequest         // 跨域请求
// @grant        GM_notification           // 桌面通知
// @grant        GM_setClipboard           // 设置剪贴板
// @grant        GM_openInTab              // 打开新标签页
// @grant        GM_download               // 下载文件

// 无需权限(沙盒模式)
// @grant        none

编码风格指南

变量声明

// ✅ 推荐:使用 const 和 let
const CONFIG = { retries: 3 };
let counter = 0;

// ❌ 避免:使用 var
var oldStyle = 'bad';

// 对象解构
const { name, version } = GM_info.script;

// 数组解构
const [first, ...rest] = array;

函数定义

// ✅ 箭头函数(简洁语法)
const wait = t => new Promise(r => setTimeout(r, t));
const add = (a, b) => a + b;

// ✅ 异步箭头函数
const fetchData = async (url) => {
    const response = await fetch(url);
    return response.json();
};

// ✅ 普通函数(需要this绑定时)
function MyClass() {
    this.value = 1;
}

// ✅ 异步函数
async function loadPage() {
    await wait(1000);
    return 'loaded';
}

命名规范

// 常量:全大写+下划线
const MAX_RETRY_COUNT = 3;
const API_BASE_URL = 'https://api.example.com';

// 变量和函数:小驼峰
const userName = 'Alice';
const getUserInfo = () => {};

// 类和构造函数:大驼峰
class DataManager {}
const MyComponent = () => {};

// 私有变量:下划线前缀(约定)
const _privateVar = 'internal';

// DOM元素:加前缀
const btnSubmit = document.querySelector('#submit');
const divContainer = document.querySelector('.container');

字符串处理

// ✅ 模板字符串
const message = `Hello, ${userName}!`;
const html = `
    <div class="item">
        <span>${title}</span>
    </div>
`;

// ✅ 多行字符串
const sql = `
    SELECT * FROM users
    WHERE active = 1
    ORDER BY created_at DESC
`;

对象和数组

// ✅ 对象简写
const name = 'Alice';
const age = 25;
const user = { name, age }; // 等同于 { name: name, age: age }

// ✅ 扩展运算符
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1,2,3,4,5]
const obj1 = { a: 1 };
const obj2 = { ...obj1, b: 2 }; // { a:1, b:2 }

// ✅ 数组方法链
const result = items
    .filter(item => item.active)
    .map(item => item.name)
    .sort();

条件判断

// ✅ 简洁的条件语句
const isValid = value && value.length > 0;
const result = condition ? valueA : valueB;

// ✅ 可选链
const userName = user?.profile?.name;

// ✅ 空值合并
const displayName = userName ?? 'Anonymous';

// ✅ 卫语句(提前返回)
function process(data) {
    if (!data) return null;
    if (!data.isValid) return null;

    // 主要逻辑
    return processData(data);
}

错误处理

// ✅ Try-catch 包装
async function safeFetch(url) {
    try {
        const response = await fetch(url);
        return await response.json();
    } catch (error) {
        log('Fetch error:', error);
        return null;
    }
}

// ✅ 可选的错误记录
try {
    riskyOperation();
} catch (e) {
    console.error('[ScriptName] Error:', e);
}

// ✅ 静默错误(音效播放等)
try {
    sound.play();
} catch (e) {
    // 忽略音频播放失败
}

常用工具函数

时间延迟

// 基础延迟
const wait = t => new Promise(r => setTimeout(r, t));

// 使用示例
await wait(1000); // 等待1秒

// 带超时的等待
const waitWithTimeout = (ms, timeoutMs) => {
    return Promise.race([
        wait(ms),
        wait(timeoutMs).then(() => { throw new Error('Timeout'); })
    ]);
};

DOM 查询

// 单个元素
const get = q => document.querySelector(q);
const $ = get; // 别名

// 多个元素(返回数组)
const getAll = q => [...document.querySelectorAll(q)];
const $$ = getAll; // 别名

// 带上下文的查询
const $child = (parent, selector) => {
    if (typeof parent === 'string') {
        return document.querySelector(`${parent} ${selector}`);
    }
    return parent.querySelector(selector);
};

const $childAll = (parent, selector) => {
    if (typeof parent === 'string') {
        return [...document.querySelectorAll(`${parent} ${selector}`)];
    }
    return [...parent.querySelectorAll(selector)];
};

// 等待元素出现
const waitFor = async (selector, timeout = 10000, interval = 100) => {
    const startTime = Date.now();
    while (Date.now() - startTime < timeout) {
        const element = get(selector);
        if (element) return element;
        await wait(interval);
    }
    return null;
};

// 使用示例
const button = await waitFor('#submit-button');
if (button) {
    button.click();
}

日志输出

// 带前缀的日志
const log = (...args) => console.log('[ScriptName]', ...args);
const error = (...args) => console.error('[ScriptName]', ...args);
const warn = (...args) => console.warn('[ScriptName]', ...args);

// 带颜色的日志
const logger = {
    log(...args) {
        console.log('%c[ScriptName]', 'color: #4CAF50', ...args);
    },
    error(...args) {
        console.error('%c[ScriptName]', 'color: #F44336', ...args);
    },
    warn(...args) {
        console.warn('%c[ScriptName]', 'color: #FF9800', ...args);
    },
    debug(...args) {
        if (DEBUG_MODE) {
            console.log('%c[ScriptName Debug]', 'color: #2196F3', ...args);
        }
    }
};

URL 处理

// 修复协议
const fixUrlProtocol = (url) => {
    if (url.startsWith('http://') || url.startsWith('https://')) {
        return url;
    } else if (url.startsWith('//')) {
        return window.location.protocol + url;
    } else if (url.startsWith('/')) {
        return window.location.origin + url;
    }
    return url;
};

// 移除尾部斜杠
const removeTailingSlash = (str) => {
    return str.replace(/\/+$/, '');
};

// 获取URL参数
const getUrlParam = (name) => {
    const params = new URLSearchParams(window.location.search);
    return params.get(name);
};

// 解析URL
const parseUrl = (url) => {
    const parser = document.createElement('a');
    parser.href = url;
    return {
        protocol: parser.protocol,
        hostname: parser.hostname,
        port: parser.port,
        pathname: parser.pathname,
        search: parser.search,
        hash: parser.hash
    };
};

Cookie 操作

// 获取Cookie
const getCookie = (name) => {
    const value = `; ${document.cookie}`;
    const parts = value.split(`; ${name}=`);
    if (parts.length === 2) {
        return parts.pop().split(';').shift();
    }
    return null;
};

// B站CSRF Token
const getCSRFToken = () => getCookie('bili_jct');

页面判断

const pages = {
    isVideoPage() {
        return /\/video\//.test(location.pathname);
    },
    isProfilePage() {
        return location.hostname.startsWith('space.bilibili.com');
    },
    isPlayPage() {
        return /\/(video|list)\//.test(location.pathname);
    }
};

// 使用示例
if (pages.isVideoPage()) {
    // 视频页逻辑
}

重试机制

// 带重试的异步函数
const retry = async (fn, retries = 3, delay = 1000) => {
    for (let i = 0; i < retries; i++) {
        try {
            return await fn();
        } catch (error) {
            if (i === retries - 1) throw error;
            await wait(delay);
            delay *= 2; // 指数退避
        }
    }
};

// 使用示例
const data = await retry(() => fetchData(url), 3, 500);

GM_* API 使用

数据存储

// 保存数据(支持任意类型)
GM_setValue('config', { theme: 'dark', fontSize: 14 });
GM_setValue('counter', 42);
GM_setValue('enabled', true);

// 读取数据(提供默认值)
const config = GM_getValue('config', { theme: 'light', fontSize: 12 });
const counter = GM_getValue('counter', 0);
const enabled = GM_getValue('enabled', false);

// 删除数据
GM_deleteValue('oldKey');

// 列出所有键
const keys = GM_listValues();
keys.forEach(key => {
    console.log(key, GM_getValue(key));
});

// Promise 版本
const config = await GM.getValue('config', {});
await GM.setValue('config', newConfig);
await GM.deleteValue('oldKey');

存储最佳实践

// ✅ 封装存储访问
const storage = {
    get(key, defaultValue) {
        const val = GM_getValue(key);
        if (typeof val === 'undefined' || val === null) {
            return defaultValue;
        }
        return val;
    },
    set(key, val) {
        GM_setValue(key, val);
    },
    del(key) {
        if (typeof GM_removeValue === 'function') {
            GM_removeValue(key);
        } else {
            GM_setValue(key, undefined);
        }
    }
};

// 使用示例
const settings = {
    get autoExtendInfo() {
        return storage.get('autoExtendInfo', true);
    },
    set autoExtendInfo(val) {
        storage.set('autoExtendInfo', val);
    },
    get batchDelay() {
        return storage.get('batchDelay', 0.5);
    },
    set batchDelay(val) {
        storage.set('batchDelay', val);
    }
};

菜单命令

// 注册菜单
const menuIds = [];

const registerMenu = (text, callback) => {
    const id = GM_registerMenuCommand(text, callback);
    menuIds.push(id);
    return id;
};

// 清除所有菜单
const clearMenus = () => {
    menuIds.forEach(id => GM_unregisterMenuCommand(id));
    menuIds.length = 0;
};

// 动态更新菜单
function updateMenu() {
    clearMenus();
    registerMenu(`✓ 功能${enabled ? '已启用' : '已禁用'}`, toggleFeature);
    registerMenu('⚙ 打开设置', openSettings);
}

// 使用示例
let enabled = GM_getValue('enabled', true);

function toggleFeature() {
    enabled = !enabled;
    GM_setValue('enabled', enabled);
    updateMenu();
}

// 初始化菜单
updateMenu();

跨域请求

// GET 请求
GM_xmlhttpRequest({
    method: 'GET',
    url: 'https://api.example.com/data',
    onload: (response) => {
        if (response.status === 200) {
            const data = JSON.parse(response.responseText);
            console.log(data);
        }
    },
    onerror: (error) => {
        console.error('Request failed:', error);
    }
});

// POST 请求
GM_xmlhttpRequest({
    method: 'POST',
    url: 'https://api.example.com/submit',
    headers: {
        'Content-Type': 'application/json'
    },
    data: JSON.stringify({ key: 'value' }),
    onload: (response) => {
        console.log('Response:', response.responseText);
    }
});

// Promise 封装
const request = (options) => {
    return new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
            ...options,
            onload: resolve,
            onerror: reject,
            ontimeout: reject
        });
    });
};

// 使用示例
try {
    const response = await request({
        method: 'GET',
        url: 'https://api.example.com/data'
    });
    const data = JSON.parse(response.responseText);
    console.log(data);
} catch (error) {
    console.error('Request failed:', error);
}

添加样式

// 添加CSS
GM_addStyle(`
    .my-custom-class {
        color: red;
        font-size: 16px;
    }

    .my-button {
        background: #4CAF50;
        border: none;
        padding: 10px 20px;
        cursor: pointer;
    }

    .my-button:hover {
        background: #45a049;
    }
`);

// 动态样式
const addStyleDynamic = (selector, rules) => {
    GM_addStyle(`${selector} { ${rules} }`);
};

addStyleDynamic('.target', 'display: none !important;');

通知

// 简单通知
GM_notification(
    '操作成功',
    '提示',
    'https://example.com/icon.png'
);

// 完整选项
GM_notification({
    text: '这是通知内容',
    title: '通知标题',
    image: 'https://example.com/icon.png',
    timeout: 5000,
    onclick: () => {
        console.log('通知被点击');
    },
    ondone: () => {
        console.log('通知已关闭');
    }
});

剪贴板

// 复制到剪贴板
GM_setClipboard('要复制的文本');

// 现代 API(需要用户交互)
const copyToClipboard = async (text) => {
    try {
        await navigator.clipboard.writeText(text);
        return true;
    } catch (e) {
        // 降级到 GM API
        GM_setClipboard(text);
        return true;
    }
};

打开新标签页

// 在后台打开
GM_openInTab('https://example.com', { active: false });

// 在前台打开(获取焦点)
GM_openInTab('https://example.com', { active: true });

// 在当前标签页打开
GM_openInTab('https://example.com', { insert: true });

DOM 操作技巧

创建元素

// 原生方法
const button = document.createElement('button');
button.textContent = '点击我';
button.className = 'my-button';
button.addEventListener('click', handleClick);
document.body.appendChild(button);

// 使用 CKTools.domHelper(推荐)
const button = CKTools.domHelper({
    tag: 'button',
    text: '点击我',
    classList: ['my-button', 'primary'],
    on: {
        click: handleClick
    },
    append: document.body
});

// HTML 字符串方式
const container = document.createElement('div');
container.innerHTML = `
    <div class="card">
        <h3 class="title">${title}</h3>
        <p class="content">${content}</p>
        <button class="btn">操作</button>
    </div>
`;
container.querySelector('.btn').addEventListener('click', handleClick);
document.body.appendChild(container);

样式操作

const element = get('.target');

// 单个样式
element.style.color = 'red';
element.style.fontSize = '16px';

// CSS 文本
element.style.cssText = 'color: red; font-size: 16px; margin: 10px;';

// 批量设置
Object.assign(element.style, {
    color: 'red',
    fontSize: '16px',
    margin: '10px'
});

// CSS类操作
element.classList.add('active');
element.classList.remove('disabled');
element.classList.toggle('selected');
element.classList.contains('active'); // 检查

// 获取计算后的样式
const bgColor = getComputedStyle(element).backgroundColor;

事件监听

// 添加事件
const button = get('#button');
button.addEventListener('click', (event) => {
    console.log('clicked', event.target);
});

// 移除事件(需要保存函数引用)
const handleClick = (event) => {
    console.log('clicked');
};
button.addEventListener('click', handleClick);
button.removeEventListener('click', handleClick);

// 一次性事件
button.addEventListener('click', handleClick, { once: true });

// 事件委托
document.body.addEventListener('click', (event) => {
    if (event.target.matches('.dynamic-button')) {
        console.log('动态按钮被点击');
    }
});

// 阻止默认行为
link.addEventListener('click', (event) => {
    event.preventDefault();
    // 自定义逻辑
});

元素操作

// 插入元素
parent.appendChild(child); // 末尾插入
parent.insertBefore(child, referenceNode); // 指定位置插入
parent.prepend(child); // 开头插入
parent.append(child1, child2); // 末尾插入多个

// 替换元素
parent.replaceChild(newChild, oldChild);
oldElement.replaceWith(newElement);

// 删除元素
element.remove();
parent.removeChild(child);

// 克隆元素
const clone = element.cloneNode(true); // true: 深克隆(包含子元素)

属性操作

// HTML 属性
element.setAttribute('data-id', '123');
const id = element.getAttribute('data-id');
element.removeAttribute('data-id');
element.hasAttribute('data-id');

// Data 属性
element.dataset.userId = '123'; // 对应 data-user-id
const userId = element.dataset.userId;

// 属性直接赋值
element.id = 'my-id';
element.className = 'my-class';
element.href = 'https://example.com';

页面滚动

// 滚动到元素
element.scrollIntoView({ behavior: 'smooth', block: 'start' });

// 滚动到顶部
window.scrollTo({ top: 0, behavior: 'smooth' });

// 滚动到指定位置
window.scrollTo({ top: 500, left: 0, behavior: 'smooth' });

// 监听滚动
window.addEventListener('scroll', () => {
    const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
    console.log('当前滚动位置:', scrollTop);
});

MutationObserver 监听DOM变化

// 监听DOM变化
const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
        if (mutation.type === 'childList') {
            mutation.addedNodes.forEach((node) => {
                if (node.nodeType === 1) { // 元素节点
                    console.log('新增元素:', node);
                }
            });
        } else if (mutation.type === 'attributes') {
            console.log('属性变化:', mutation.attributeName);
        }
    });
});

// 开始监听
observer.observe(document.body, {
    childList: true,      // 监听子节点变化
    subtree: true,        // 监听所有后代节点  attributes: true,     // 监听属性变化
    attributeOldValue: true, // 记录旧属性值
    characterData: true   // 监听文本内容变化
});

// 停止监听
observer.disconnect();

// 使用示例:等待元素加载后处理
const waitForElement = (selector, callback) => {
    const element = get(selector);
    if (element) {
        callback(element);
        return;
    }

    const observer = new MutationObserver(() => {
        const element = get(selector);
        if (element) {
            callback(element);
            observer.disconnect();
        }
    });

    observer.observe(document.body, { childList: true, subtree: true });
};

// 使用
waitForElement('.dynamic-content', (element) => {
    console.log('元素加载完成:', element);
});

库文件使用

本项目提供了多个实用的库文件,位于 libs/ 目录下。

CKUI - UI 组件库

CKUI 是一个现代化、无依赖的 UI 组件库,适用于创建美观的用户界面。

// 在脚本头部引入
// @require https://update.greasyfork.org/scripts/564901/1749919/CKUI.js

// 创建模态框
const modal = ckui.modal({
    title: '设置',
    content: '这里是模态框内容',
    buttons: [
        {
            text: '确定',
            primary: true,
            onClick: () => {
                console.log('确定被点击');
                modal.close();
            }
        },
        {
            text: '取消',
            onClick: () => modal.close()
        }
    ]
});

modal.show();

// 创建浮动窗口
const floatWindow = ckui.floatWindow({
    title: '工具面板',
    content: '<div>面板内容</div>',
    width: 400,
    height: 300,
    draggable: true
});

floatWindow.show();

// 创建表单
const form = ckui.form({
    fields: [
        {
            type: 'text',
            name: 'username',
            label: '用户名',
            placeholder: '请输入用户名'
        },
        {
            type: 'checkbox',
            name: 'remember',
            label: '记住我'
        }
    ],
    onSubmit: (data) => {
        console.log('表单数据:', data);
    }
});

CKTools - 工具函数库

CKTools 提供了丰富的DOM操作和工具函数。

// 在脚本头部引入
// @require https://greasyfork.org/scripts/429720-cktools/code/CKTools.js

// DOM 查询
const element = CKTools.get('#my-element');
const elements = CKTools.getAll('.my-class');

// 创建DOM元素
const button = CKTools.domHelper({
    tag: 'button',
    text: '点击我',
    classList: ['btn', 'btn-primary'],
    style: {
        backgroundColor: '#4CAF50',
        color: 'white',
        padding: '10px 20px'
    },
    on: {
        click: () => console.log('clicked')
    },
    append: document.body
});

// 从HTML字符串创建元素
const div = CKTools.domHelper({
    tag: 'div',
    from: '<span>Hello</span><span>World</span>'
});

CKLogger - 日志系统

提供结构化的日志记录功能。

// 创建日志记录器
const logger = new Logger('MyScript');

// 记录日志
logger.log('普通日志');
logger.info('信息日志');
logger.warn('警告日志');
logger.error('错误日志');
logger.debug('调试日志');

// 创建子日志记录器
const subLogger = logger.getSubLogger('API');
subLogger.log('API调用'); // 输出: [MyScript/API] API调用

// 控制可见性
logger.setVisible(false); // 隐藏所有日志
logger.setVisible(true);  // 显示日志

// 订阅日志事件
logger.subscribe({
    levels: ['error', 'warn'],
    onlog: (level, namespace, ...args) => {
        // 自定义处理,如发送到服务器
        sendToServer({ level, namespace, message: args });
    }
});

CKEventEmitter - 事件发射器

实现发布-订阅模式。

const emitter = new EventEmitter();

// 订阅事件
emitter.on('dataLoaded', (data) => {
    console.log('数据已加载:', data);
});

// 发射事件
emitter.emit('dataLoaded', { items: [] });

// 取消订阅
const handler = (data) => console.log(data);
emitter.on('event', handler);
emitter.off('event', handler);

// 清除所有事件
emitter.clean('event'); // 清除特定事件
emitter.clean();        // 清除所有事件

CKSimpleFilter - 数据过滤器

提供复杂的数据过滤功能。

// 创建过滤器
const filter = new SimpleFilter({
    $and: [
        (item) => item.age > 18,
        (item) => item.active === true,
        {
            $or: [
                (item) => item.role === 'admin',
                (item) => item.role === 'moderator'
            ]
        }
    ]
});

// 应用过滤器
const users = [
    { age: 20, active: true, role: 'admin' },
    { age: 17, active: true, role: 'user' },
    { age: 25, active: false, role: 'admin' }
];

const results = await filter.applyFilterToAll(users);
console.log(results); // [true, false, false]

// 过滤数组
const filtered = users.filter((user, i) => results[i]);

CKAutoLoader - B站页面加载检测

专门用于检测 B站播放器加载完成。

// 注册加载回调
CKAutoLoader.reg('myScript', () => {
    console.log('播放器加载完成');
    // 执行需要播放器的代码
});

// 监听评论区加载
window.addEventListener('ckBilibiliCommentLoaded', () => {
    console.log('评论区加载完成');
});

// 监听播放器加载
window.addEventListener('ckBilibiliPlayerLoaded', () => {
    console.log('播放器加载完成');
});

调试技巧

开启调试模式

// 方式1:通过全局变量
unsafeWindow.DEBUG_MODE = true;

// 方式2:通过存储
const DEBUG = GM_getValue('debug', false);

// 方式3:通过URL参数
const DEBUG = new URLSearchParams(location.search).has('debug');

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
101
Forks
5
Last commit
Jun 2026
Advanced
Catalog kind
skill
Gateway key
userscript
Source
github.com/ckylinmc/userjs