JavaScript 插件
本规范说明为 Mankai 提供在线漫画内容的插件所使用的 JSON 格式和 JavaScript API。
JavaScript 插件是只读内容源。应用从网址或粘贴的 JSON 加载清单,然后在隐藏的 WebKit 网页视图中执行回调脚本。
清单
章节「清单」清单是一个 JSON 对象。解码器只要求提供 id 字段,但仍需提供实际使用的操作所要求的回调脚本。
type ScriptName = | 'isOnline' | 'getSuggestion' | 'search' | 'getList' | 'getMangas' | 'getMangaUpdates' | 'getDetailedManga' | 'getChapter' | 'getImage'
type PluginCapability = | 'onlineCheck' | 'suggestions' | 'list' | 'listByGenre' | 'listByStatus' | 'search' | 'searchByGenre' | 'searchByStatus' | 'searchByAuthor' | 'mangaDetails' | 'batchMangas' | 'mangaUpdates' | 'chapter' | 'image'
interface JsPluginManifest { id: string name?: string version?: string description?: string authors?: string[] repository?: string updatesUrl?: string availableGenres?: Genre[] scripts?: Record<ScriptName, string> configs?: Config[] getImageHeaders?: Record<string, string> cooldown?: Cooldown capabilities?: PluginCapability[]}
元数据字段
章节「元数据字段」| 字段 | 类型 | 说明 |
|---|---|---|
id |
string |
稳定且全局唯一的插件标识符,也用于划分持久存储的作用域。 |
name |
string |
显示名称,省略时使用 ID。 |
version |
string |
在应用中显示并用于检查更新的插件版本。 |
description |
string |
在来源设置中显示的简短描述。 |
authors |
string[] |
插件作者,默认值为 []。 |
repository |
string |
源代码或项目的网址。 |
updatesUrl |
string |
用于检查更新的清单网址。 |
availableGenres |
Genre[] |
内容源支持的漫画类型,默认值为 []。 |
scripts |
Record<string, string> |
以以下脚本名称为键的 JavaScript 源代码,未知的键会被忽略。 |
configs |
Config[] |
向脚本提供的用户可配置值,默认值为 []。 |
getImageHeaders |
Record<string, string> |
如果存在,Mankai 使用这些原生请求标头下载所有图片网址,而不调用 getImage 脚本。 |
cooldown |
Cooldown |
可选的请求节流和图片并发限制。 |
capabilities |
PluginCapability[] |
插件支持的操作,默认启用除 mangaUpdates 以外的所有能力。 |
脚本格式
章节「脚本格式」每个脚本值都是包含函数和导出标记的字符串。
async function isOnline() { const response = await fetch('https://example.com/') return response.ok}
export{isOnline as default};标记必须使用 export{functionName as default}; 的形式。Mankai 在执行前移除该标记并调用指定函数。函数可以是同步或异步函数,Mankai 会等待返回结果。除了导出的函数,脚本还可以包含辅助函数。
回调脚本
章节「回调脚本」可选字段 capabilities 接受下列值。
onlineCheck, suggestions, list, listByGenre, listByStatus, search, searchByGenre, searchByStatus, searchByAuthor, mangaDetails, batchMangas, mangaUpdates, chapter, image键与函数签名如下。
| 键 | 函数签名 | 返回值 |
|---|---|---|
isOnline |
isOnline() |
boolean |
getSuggestion |
getSuggestion(query) |
string[] |
search |
search(query, page, genre, status, isAuthor) |
Manga[] |
getList |
getList(page, genre, status) |
Manga[] |
getMangas |
getMangas(ids) |
Manga[] |
getMangaUpdates |
getMangaUpdates(mangas) |
MangaUpdate[] 元数据补丁与更新标记 |
getDetailedManga |
getDetailedManga(id) |
DetailedManga |
getChapter |
getChapter(manga, chapter) |
图片网址数组 string[] |
getImage |
getImage(url) |
Base64 图片数据或图片代理请求对象 |
以下说明各参数与返回结果。
isOnline
章节「isOnline」当内容源可访问且可用时返回 true,否则返回 false。抛出异常或返回非布尔值会被视为插件调用失败。
getSuggestion(query)
章节「getSuggestion(query)」返回适合用作搜索建议的字符串数组。清单中的键使用单数形式 getSuggestion。
async function getSuggestion(query) { const response = await fetch( `https://example.com/suggestions?q=${encodeURIComponent(query)}`, ) if (!response.ok) return [] return await response.json()}
export{getSuggestion as default};search(query, page, genre, status, isAuthor)
章节「search(query, page, genre, status, isAuthor)」按标题或作者搜索漫画。page 是从 1 开始的页码。genre 是漫画类型中的一个字符串,status 是状态中的一个数值。当 isAuthor 为 true 时,搜索内容源的作者字段而不是标题字段。返回轻量的 Manga 对象数组。
getList(page, genre, status)
章节「getList(page, genre, status)」返回内容源的分页列表,使用与 search 相同的 page、genre 和 status 参数。
getMangas(ids)
章节「getMangas(ids)」根据请求的字符串 ID 返回轻量漫画对象。如果内容源已不再包含某些 ID,返回的条目数可以少于请求数。
getMangaUpdates(mangas)
章节「getMangaUpdates(mangas)」每个请求条目包含漫画 ID 和 Mankai 当前已知的最新章节。此回调让插件控制更新行为。如果希望使用 Mankai 默认的批量比较方式,请省略 mangaUpdates 能力。
interface MangaUpdateRequest { id: string latestChapter: Chapter}返回 MangaUpdate[]。每个结果必须包含 id 和 updates。Mankai 仅根据 updates 的值决定是否将已保存的漫画标记为有更新。Mankai 会根据所有返回结果刷新元数据,包括 updates 为 false 的结果。
interface MangaUpdate extends Manga { updates: boolean}async function getMangaUpdates(mangas) { const response = await fetch('https://example.com/manga/updates', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(mangas), }) if (!response.ok) return [] return await response.json()}
export{getMangaUpdates as default};例如,插件可以返回 { id: "completed-series", updates: false, status: 2 },只刷新状态而不显示未读更新。
getDetailedManga(id)
章节「getDetailedManga(id)」返回完整的 DetailedManga 对象。Mankai 会先将返回值序列化为 JSON 再解码,因此应返回 JavaScript 对象,而不是 JSON 字符串。
getChapter(manga, chapter)
章节「getChapter(manga, chapter)」按顺序返回 chapter 的图片网址。manga 参数是 getDetailedManga 返回的完整对象,chapter 是所选章节对象。图片网址应为绝对网址,因为 Mankai 随后会逐一将其传入 getImage。
async function getChapter(manga, chapter) { const response = await fetch( `https://example.com/manga/${encodeURIComponent(manga.id)}/chapter/${encodeURIComponent(chapter.id)}`, ) if (!response.ok) return [] return await response.json()}
export{getChapter as default};getImage(url)
章节「getImage(url)」未提供 getImageHeaders 时,Mankai 对封面和章节页面调用此脚本。接受以下两种结果。
-
包含原始图片字节的 Base64 字符串,不要包含
data:image/...;base64,前缀。 -
指示 Mankai 发起原生请求的对象。
interface ImageProxyRequest {url: stringheaders: Record<string, string>}
例如:
async function getImage(url) { return { url, headers: { Referer: 'https://example.com/', 'User-Agent': 'Mozilla/5.0', }, }}
export{getImage as default};如果清单包含 getImageHeaders,系统会自动对每个图片网址发起等效的原生请求。
{ "getImageHeaders": { "Referer": "https://example.com/", "User-Agent": "Mozilla/5.0" }}
数据类型
章节「数据类型」回调结果接受以下结构,可选属性可以省略。
interface Chapter { id: string title?: string locked?: boolean}
interface Manga { id: string title?: string cover?: string status?: Status latestChapter?: Chapter meta?: string}
interface ChapterGroup { id?: string title: string chapters: Chapter[]}
interface DetailedManga { id: string title?: string cover?: string status?: Status readingDirection?: ReadingDirection latestChapter?: Chapter description?: string externalLink?: string updatedAt?: number // Unix timestamp in milliseconds authors?: string[] genres?: Genre[] chapters?: ChapterGroup[] remarks?: string meta?: string}
漫画类型
章节「漫画类型」Mankai 识别以下字符串值。
all, action, romance, yuri, boysLove, otokonoko, schoolLife, adventure,harem, speculativeFiction, war, suspense, fanFiction, comedy, magic, horror,historical, sports, mature, mecha
状态
章节「状态」| 名称 | 值 |
|---|---|
any |
0 |
onGoing |
1 |
completed |
2 |
阅读方向
章节「阅读方向」| 名称 | 值 |
|---|---|
leftToRight |
1 |
rightToLeft |
2 |
vertical |
3 |
运行时辅助函数
章节「运行时辅助函数」Mankai 在每个回调脚本中注入以下辅助函数。
fetch(url, options)
章节「fetch(url, options)」运行环境提供使用原生网络的 fetch 实现,支持以下请求选项。
interface FetchOptions { method?: string // Defaults to "GET" headers?: Record<string, string> | Headers body?: string}返回的响应具有以下属性和方法。
interface FetchResponse { ok: boolean status: number statusText: string url: string headers: Headers text(): Promise<string> json(): Promise<any> blob(): Promise<Blob> arrayBuffer(): Promise<ArrayBuffer>}console.log(...values)
章节「console.log(...values)」console.log 会重定向至 Mankai 的 JavaScript 插件日志,可用于开发期间诊断插件。
console.log('searching', query)
s2t(text) 与 t2s(text)
章节「s2t(text) 与 t2s(text)」这些异步辅助函数使用 OpenCC 转换中文文本。
const traditional = await s2t('简体转繁体')const simplified = await t2s('繁體轉簡體')s2t 将简体中文转换为繁体中文,t2s 执行反向转换。
getConfigs()
章节「getConfigs()」以数组形式返回清单所声明的配置项的当前值。
interface ConfigValue { key: string value: unknown}示例:
function configValue(key) { return getConfigs().find((config) => config.key === key)?.value}
const username = configValue('username')
持久存储
章节「持久存储」这些辅助函数在以插件 id 为作用域的键值存储中保存字符串值。
getValue(key: string): Promise<string | null>;setValue(key: string, value: string): Promise<void>;removeValue(key: string): Promise<boolean>;三个辅助函数都应使用 await。应用重启和插件更新后,值仍然保留。删除插件时,这些值也会删除。
const token = await getValue('token')if (!token) { await setValue('token', 'new-token')}await removeValue('temporary-value')
配置
章节「配置」配置项采用以下结构。
interface Config { key: string name: string description?: string type: 'text' | 'password' | 'number' | 'slider' | 'boolean' | 'select' | 'color' defaultValue: unknown options?: string[] min?: number max?: number step?: number supportsOpacity?: boolean}options 用于 select 配置。min、max 和 step 指定 slider 配置的范围和步长。应用使用 defaultValue 初始化每个配置项,持久保存用户在来源设置中的修改,并通过 getConfigs() 提供当前值。
示例:
{ "configs": [ { "key": "language", "name": "Language", "description": "Language used when requesting titles.", "type": "select", "defaultValue": "en", "options": ["en", "zh-Hans", "zh-Hant"] }, { "key": "imageQuality", "name": "Image quality", "type": "slider", "defaultValue": 80, "min": 10, "max": 100, "step": 5 }, { "key": "includeMature", "name": "Include mature content", "type": "boolean", "defaultValue": false } ]}
冷却时间
章节「冷却时间」冷却时间以毫秒为单位。
interface Cooldown { default?: number getImage?: number getImageConcurrency?: number}| 字段 | 说明 |
|---|---|
default |
搜索、列表、详情和章节请求等非图片操作之间的最小间隔。 |
getImage |
图片请求之间的最小间隔,图片与其他操作使用独立的调度。 |
getImageConcurrency |
同时进行的图片请求数量上限,小于 1 的值表示不设置限制。 |
使用这些字段遵守内容源的请求频率限制,例如:
{ "cooldown": { "default": 500, "getImage": 100, "getImageConcurrency": 2 }}