JavaScript 刮削插件开发指南

JavaScript 插件是 Ting Reader 中开发最简单、调试最方便的插件类型。它运行在内置的轻量级 JavaScript 运行时中,非常适合编写 HTTP 请求驱动的刮削逻辑。

1. 快速开始

1.1 插件目录结构

创建一个新文件夹 my-scraper-js,并在其中创建两个文件:

  • plugin.json: 插件配置文件(详情请参考 插件开发指南
  • plugin.js: 插件代码文件

1.2 核心代码 (plugin.js)

// 1. 初始化
function initialize(context) {
    Ting.log.info('插件已加载');
}

function shutdown() {
    Ting.log.info('插件已卸载');
}

// 2. 搜索书籍
async function search(args) {
    // args 包含 query (关键词), page (页码), narrator (演播者筛选), author (作者筛选)
    const { query, page, narrator, author } = args;
    
    Ting.log.info(`搜索: ${query}, 页码: ${page}`);

    // 发送请求 (fetch 是内置的)
    const resp = await fetch(`https://api.example.com/search?q=${encodeURIComponent(query)}&page=${page}`);
    const data = await resp.json();
    
    // 转换数据结构
    const items = data.results.map(item => ({
        id: String(item.id),
        title: cleanText(item.title), // 建议进行文本清洗
        author: item.author_name,
        cover_url: item.cover_image,
        intro: item.description,
        tags: item.categories || [],
        narrator: item.narrator_name || null,
        chapter_count: item.total_chapters,
        duration: null
    }));
    
    // 3. 结果优化 (最佳实践)
    if (items.length > 0) {
        // 3.1 演播者/作者筛选与重排
        if (narrator) {
            const idx = items.findIndex(i => i.narrator && i.narrator.includes(narrator));
            if (idx > -1) {
                // 将匹配项移到首位
                const [match] = items.splice(idx, 1);
                items.unshift(match);
            }
        }

        // 3.2 首项增强 (Search Result Enhancement)
        // 搜索列表返回的信息通常不完整,建议主动获取第一条结果的详情来补充信息
        try {
            const detail = await _fetchDetail(items[0].id);
            Object.assign(items[0], detail);
            Ting.log.info('已增强第一条结果的元数据');
        } catch (e) {
            Ting.log.warn('增强元数据失败: ' + e.message);
        }
        
        // 3.3 图片 URL 清洗与防盗链绕过 (Cover Image Handling)
        // 建议去除图片 URL 后的缩放参数(如 !200),并升级为 https
        // 如果目标网站有防盗链,可以在 URL 后追加 #referer=目标网站地址,让阅读器后端代理请求
        items.forEach(item => {
            if (item.cover_url) {
                // 示例:移除 !200 等缩放后缀
                item.cover_url = item.cover_url.split('!')[0];
                // 示例:绕过防盗链 (将由后端使用指定的 referer 代理下载)
                item.cover_url = item.cover_url.replace('http:', 'https:') + '#referer=https://www.example.com/';
            }
        });
    }

    return {
        items: items,
        total: data.total_count,
        page: page,
        page_size: items.length
    };
}

// 4. 内部辅助函数
async function _fetchDetail(bookId) {
    // ... 获取详情的逻辑
    return { 
        intro: "详细简介...",
        tags: ["标签1", "标签2"]
    };
}

function cleanText(text) {
    // 移除广告、特殊符号等
    return text ? text.replace(/【.*?】/g, '').trim() : '';
}

// 5. 导出函数 (必须!)
globalThis.initialize = initialize;
globalThis.shutdown = shutdown;
globalThis.search = search;

2. API 参考

全局对象 Ting

  • Ting.log.info(msg): 打印信息日志
  • Ting.log.warn(msg): 打印警告日志
  • Ting.log.error(msg): 打印错误日志

全局函数 fetch

完全兼容标准的 Fetch API。

const response = await fetch('https://api.example.com', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ key: 'value' })
});

3. 常见问题

Q: 支持 npm 包吗?

A: 不支持直接 require/import npm 包。这是一个轻量级运行时。如果需要复杂依赖,请考虑打包成单文件或使用 WASM。

Q: 如何调试?

A: 使用 Ting.log 输出日志,日志会显示在 Ting Reader 的控制台或日志文件中。