api: "csp_Demo"類別放在 com.github.catvod.spider.Demo,並繼承抽象 Spider;主配置必須提供 spider 或站點的 jar。使用 catvod-api.jar 作為 compileOnly 編譯依賴,由 TV 根任務 :apiJar 產生於 build/outputs/api/catvod-api.jar。
從 Java、Python、QuickJS 範例開始,了解載入、共用接口與回傳格式。
sites[] 物件片段{
"key": "demo",
"name": "Demo",
"type": 3,
"api": "./demo.js",
"ext": {}
}api: "csp_Demo"類別放在 com.github.catvod.spider.Demo,並繼承抽象 Spider;主配置必須提供 spider 或站點的 jar。使用 catvod-api.jar 作為 compileOnly 編譯依賴,由 TV 根任務 :apiJar 產生於 build/outputs/api/catvod-api.jar。
api: "./demo.py"相對路徑以配置檔所在位置為基準;輸出 Spider 類別並繼承 base.spider.Spider。
api: "./demo.js"JavaScript 由 QuickJS 執行;相對路徑以配置檔所在位置為基準。 使用 ES module,預設匯出物件或建立物件的函式。
在 App 的模組頁安裝 Python、QuickJS 或 Node.js 的獨立 APK。 Thunder、mpv 與 BD-J 也在此管理,按需要安裝。
選擇模組、下載並完成系統安裝。 版本需與 App 的模組接口及裝置 ABI 相容。
安裝後執行模組測試,確認能初始化。 若提示重新啟動,重開 App 後再載入爬蟲。
QuickJS 使用 QuickJS-NG 引擎,不能直接執行 Node.js 套件; Node.js 請依 獨立載入方式 設定。 使用新接口時,請一併更新 App 與對應模組。
連線 App 的網頁,從「設定 → 爬蟲測試」開啟 /spider.html。 Python 與 QuickJS 需先安裝對應模組。
App 不編譯 Java,修改 .java 後需重新產生並載入 JAR。 測試逾時預設 30 秒,可設為 1–300 秒;重新載入、取消或離開會清理測試工作。 來源若已被其他頁面修改,先重新讀取再儲存。
範例流程為「分類 → 列表 → 詳情 → 播放」。請將 example.com 換成自己的來源, 並補上搜尋與錯誤處理。
JSONObject/JSONArray 組裝 JSON 字串;Python 回傳 dict/list,QuickJS 回傳物件/陣列,由 App 序列化。 直播可回傳 Group 陣列或 TXT/M3U 原始文字。// 最小點播範例;example.com 為示意網址。
package com.github.catvod.spider;
import com.github.catvod.crawler.Spider;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.List;
public class Demo extends Spider {
// 回傳分類清單。
@Override
public String homeContent(boolean filter) throws Exception {
JSONObject item = new JSONObject().put("type_id", "movie").put("type_name", "電影");
return new JSONObject().put("class", new JSONArray().put(item)).toString();
}
// 範例只有一頁;第二頁起回傳空清單。
@Override
public String categoryContent(String tid, String pg, boolean filter,
HashMap<String, String> extend) throws Exception {
int page = Math.max(1, Integer.parseInt(pg));
JSONArray items = new JSONArray();
if (page == 1) items.put(card("demo-1", "範例項目"));
return new JSONObject().put("list", items).put("page", page)
.put("pagecount", 1).put("limit", 20).put("total", 1).toString();
}
// 集數先使用穩定 ID,播放時再取得網址。
@Override
public String detailContent(List<String> ids) throws Exception {
JSONObject item = card(ids.get(0), "範例項目").put("vod_play_from", "Demo")
.put("vod_play_url", "第 1 集$demo://episode/1");
return new JSONObject().put("list", new JSONArray().put(item)).toString();
}
// 將播放 ID 換成實際網址。
@Override
public String playerContent(String flag, String id, List<String> vipFlags) throws Exception {
String url = id.replace("demo://", "https://example.com/");
return new JSONObject().put("parse", 0).put("url", url).toString();
}
private JSONObject card(String id, String name) throws Exception {
return new JSONObject().put("vod_id", id).put("vod_name", name)
.put("vod_pic", "https://example.com/poster.jpg");
}
}# 最小點播範例;example.com 為示意網址。
from base.spider import Spider as BaseSpider
class Spider(BaseSpider):
def init(self, extend=""):
pass
# 回傳分類清單。
def homeContent(self, filter):
return {
"class": [{"type_id": "movie", "type_name": "電影"}]
}
# 完整清單交給共用方法分頁。
def categoryContent(self, tid, pg, filter, extend):
items = [self.card("demo-1", "範例項目")]
return self.page(items, pg, 20)
# 集數先使用穩定 ID,播放時再取得網址。
def detailContent(self, ids):
item = self.card(ids[0], "範例項目")
item.update({
"vod_play_from": "Demo",
"vod_play_url": "第 1 集$demo://episode/1"
})
return {"list": [item]}
# 將播放 ID 換成實際網址。
def playerContent(self, flag, id, vipFlags):
url = id.replace("demo://", "https://example.com/")
return {"parse": 0, "url": url}
def card(self, id, name):
return {
"vod_id": id,
"vod_name": name,
"vod_pic": "https://example.com/poster.jpg"
}// 最小點播範例;example.com 為示意網址。
const card = (id, name) => ({
vod_id: id,
vod_name: name,
vod_pic: "https://example.com/poster.jpg"
});
export default {
init(ext) {},
// 回傳分類清單。
home(filter) {
return {
class: [{ type_id: "movie", type_name: "電影" }]
};
},
// 完整清單交給共用方法分頁。
category(tid, pg, filter, extend) {
const items = [card("demo-1", "範例項目")];
return page(items, pg, 20);
},
// 集數先使用穩定 ID,播放時再取得網址。
detail(id) {
const item = {
...card(id, "範例項目"),
vod_play_from: "Demo",
vod_play_url: "第 1 集$demo://episode/1"
};
return { list: [item] };
},
// 將播放 ID 換成實際網址。
play(flag, id, vipFlags) {
const url = id.replace("demo://", "https://example.com/");
return { parse: 0, url };
}
};完整範例見 CatVodSpider 的 Java、Python 與 QuickJS, 涵蓋篩選、搜尋分頁、直播、快取與網頁操作。
本表適用 Java、Python 與 QuickJS;Node.js 使用 獨立 HTTP 端點。
在 destroy 釋放自建 Session、WebSocket 與背景工作。 QuickJS 最多等待 5 秒(含 Promise),逾時仍會繼續清理引擎。
| 方法 | 何時呼叫 | 責任 | 主要回傳 |
|---|---|---|---|
init | 初始化爬蟲時 | 讀取 ext、建立連線或快取 | 無 |
homeContent / home | 進入首頁 | 分類與選填 filters | Result.class |
homeVideoContent / homeVod | 首頁分類完成 | 首頁推薦卡片 | Result.list |
categoryContent / category | 分類、換頁、變更篩選 | 該頁卡片與總頁數 | list + pagecount |
detailContent / detail | 點擊卡片 | 完整資訊與播放分組 | list[0] |
searchContent / search | 搜尋 | 搜尋結果,可支援頁碼 | list + pagecount |
playerContent / play | 選擇集數 | 最終 URL 與播放參數 | Result |
danmaku | 選用爬蟲即時彈幕來源 | 彈幕或訂閱狀態物件 | Java AutoCloseable/Python、QuickJS 停止函式 |
liveContent / live | 載入直播配置 | TXT、M3U 或 Group JSON | 文字 / Group 陣列 |
proxy / localProxy | 本地代理收到請求 | 依執行環境提供狀態、類型、內容與標頭 | 陣列 / HTTP 回應 |
action | 自訂操作 | 可由 Result 解析的結果 | Java JSON 字串/Python、QuickJS 物件 |
destroy | 重新載入或清除 | 釋放執行緒與連線;QuickJS 清理 hook 最長 5 秒 | 無 |
playerContent/play 可在播放結果的 list[0] 帶回 VOD 詳情。 App 採用結果時更新頁面;預載結果在切集時套用,取消或過期的結果會丟棄。
播放結果附帶詳情{
"parse": 0,
"url": "https://example.com/video.mp4",
"list": [
{
"vod_id": "demo-1",
"vod_name": "範例項目",
"vod_pic": "https://example.com/cover.jpg",
"type_name": "動畫",
"vod_year": "2026",
"vod_area": "台灣",
"vod_director": "示範導演",
"vod_actor": "演員甲、演員乙",
"vod_content": "完整簡介",
"vod_remarks": "更新至第 2 集",
"vod_play_from": "主要線",
"vod_play_url": "第1集$episode-1#第2集$episode-2"
}
]
}Java 使用 Response.vod(vod),Python/QuickJS 使用 list: [vod]; Node.js 的 /play 與 type=4 API 使用相同的 JSON 格式。 範例見 CatVodSpider Demo。
同名播放分組合併 episode,相同名稱與播放值去重,其餘追加,保留目前選集位置。vod_id 可省略,提供時會連動歷史與收藏識別。
App 在初始化前提供網路接口:Java 使用 net、Python 使用 self.net、QuickJS 使用 net。 時間單位為毫秒。完整參數與範例見 CatVodSpider 開發說明。
| 方法 | 用途 |
|---|---|
req / http | 取得 code、headers、content 與 error;HTTP 非 2xx 需自行判斷。body 支援文字或位元組陣列。 |
json | 取得 JSON;連線失敗、HTTP 非 2xx 或無效 JSON 會拋出例外。 |
get / post | Java 取得文字正文;HTTP 非 2xx 仍保留正文。 |
getUserAgent | Python/QuickJS 取得 App 的 User-Agent,按需放入 headers。 |
getCookie / setCookie | 讀寫 WebView Cookie;Session 使用隔離 Cookie。 |
session | 建立隔離 Cookie 的請求流程,用完關閉。 |
batch | 批次 HTTP 請求,預設最多 4 筆並行,按輸入順序回傳。 |
map | 三語言逐項執行回呼,預設最多 4 項並行,保留輸入順序。 |
download | 直接下載至檔案,由共用下載器管理暫存、續傳與分段。 |
openStream | Python/QuickJS 分塊讀取,用完關閉 reader。 |
requestStreamAsync | Java 傳入 NetOptions,Future 回傳 Net.StreamResult。 |
stream | Python/QuickJS 原樣轉送網址,或由產生器提供 proxy 正文。 |
ws | 一次 WebSocket 訊息交換,取得首個完整訊息後關閉。 |
connect | 持續 WebSocket 連線,可設定斷線恢復與接收逾時。 |
cached / peek / refresh | TTL 快取、讀取已有值與背景更新。 |
cachedText | Python 取得快取的 JSON 原文。 |
clearCache | 清除一筆;省略 key 清除目前站點全部 TTL 快取並取消載入。 |
sleep | 可取消的等待,單位為毫秒。 |
只取 JSON 用 net.json(url, options);連線失敗、HTTP 非 2xx 或無效 JSON 會拋出例外。 需要狀態碼、錯誤正文或 bytes 時用 net.req,QuickJS 用 await net.http。 Java 的 options 除特別標示外使用 JSON 字串,json/req 也回傳 JSON 字串。
QuickJS 的 req 同步執行;http/json 以 await 取得結果。Python/QuickJS 使用物件傳 options, 二進位回應用 buffer: 3,分別取得 bytes/ArrayBuffer。net.getUserAgent() 取得 App 的 UA,需要時自行放入 headers。
原始 POST 用 body,與 data 擇一並提供 Content-Type。 body 支援文字或 0~255 整數陣列,例如 body: [0, 127, 128, 255];buffer 只控制回應格式。
cookie: true 使用並更新 WebView Cookie;以 getCookie(url) 讀取、setCookie(url, cookie) 寫入單筆 Set-Cookie。net.session() 自動管理隔離 Cookie,不共用站點的 Cookie 或 TTL 快取,也不支援 persist。
Session 用完須關閉:Java 用 try-with-resources、Python 用 with、QuickJS 用 finally。 關閉會取消其請求、串流與連線,之後不可繼續使用。
net.batch(requests, limit) 每筆傳入 {url, options},預設最多 4 筆並行,按輸入順序回傳。 每項還需快取、簽名或解析時用 net.map(items, mapper, limit),回呼收 item/index,各項自行檢查 HTTP 狀態。 拋錯後停止派送,已開始的工作繼續完成;Java/Python 在背景執行緒回呼,QuickJS 可用 async。
Java 下載用 net.download(url, optionsJson, file),Python/QuickJS 用 net.download(url, path, options)。threads 為 0 自動、1 單連線、2–4 並行上限;size 指定完整 bytes,maxBytes 限制寫入大小。 續傳與分段依來源的 Range 與驗證資訊決定,cookie:true/Session 不續傳;完成後仍須確認檔案內容。
Java 用 net.requestStreamAsync(url, NetOptions),Future 回傳繼承 InputStream 的 Net.StreamResult。 Python 用 self.net.openStream(url, options),QuickJS 用 await net.openStream(url, options)。 用完以 try-with-resources、with 或 finally 關閉。
將下列片段放入對應的 proxy 方法。成功交付後由 App 關閉 stream,不交付時自行 close。 QuickJS handle 只供當次 proxy 使用一次,不存入 Local/快取;Java 需引入 Net 與 NetOptions。
NetOptions options = NetOptions.from("{\"timeout\":15000,\"callTimeout\":15000}");
Net.StreamResult response = net.requestStreamAsync("https://example.com/data.bin", options).get();
return new Object[]{response.code, response.contentType, response, response.headers};response = self.net.stream("https://example.com/data.bin", {
"timeout": 15000, "callTimeout": 15000
})
return [response["code"], response["contentType"], response["stream"], response["headers"]]const response = await net.stream("https://example.com/data.bin", {
timeout: 15000, callTimeout: 15000
});
return [response.code, response.contentType, response.stream, response.headers];Python 用 self.net.stream(generator()),yield bytes/bytearray/連續 memoryview; QuickJS 用 await net.stream(asyncGenerator()),yield ArrayBuffer/TypedArray。 將回應的 stream 放入 proxy 第三格;Java 直接回傳自訂 InputStream。
產生器在播放器讀取時才執行,不接受 HTTP options。reader 在產生器內開啟,以 with/finally 關閉; App 關閉 body 時取消其等待中的串流請求與讀取,再關閉產生器。
直播可用 callTimeout: 0 關閉整次 HTTP 期限,保留 timeout 防止讀取卡住;Demo 使用 15 秒期限。 讀取大小、EOF 與取消限制見 開發說明。
一次交換用 net.ws(url, options),取得首個完整訊息後關閉。 持續收訊用 net.connect(url, options, callback),回呼接收 (socket, event), 事件為 open/message/retry/error/close。
content 為文字,二進位則為 Java byte[]、Python bytes 或 QuickJS Uint8Array。 使用回呼傳入的 socket.send() 發送、socket.close() 關閉,需立即中斷時用 cancel。 站點或 Session 釋放時也會取消連線。
timeout 只限制建立連線;ping 是協定 Ping 的毫秒間隔。 idle 是未收到文字/二進位訊息的逾時毫秒數,預設 0 關閉;送出訊息與 Ping/Pong 不會重設期限。 平台訂閱與應用層心跳由爬蟲處理,只在來源保證持續回訊時設定 idle。
reconnect: true 啟用斷線恢復,預設關閉,等待從 1 秒倍增至 30 秒。 每次 open 重新訂閱,retry 時停止心跳並清除殘包。 主動關閉、釋放或認證/憑證/協定/回呼錯誤後不再重連。
net.cached(key, options, loader) 按站點快取 JSON,同 key 合併載入。 ttl 必填;stale 預設 true,過期先回舊值並背景更新;persist 預設 false,啟用時持久化公開資料。 播放網址使用短 TTL、stale: false,不持久化。
peek(key) 只讀已有值,包括過期值;refresh(key, options, loader) 立即返回,依 TTL 背景更新。clearCache(key) 清一筆,省略 key 清除目前站點全部 TTL 快取並取消載入。
Python 的 self.net.cachedText(key, options, loader) 取得 JSON 原文;cached 則解碼為 Python 值,兩者共用 TTL 與清除規則。 Java 的 cached 回傳 JSON 字串。
小型設定用 Java 的 local、Python 的 self.local 或 QuickJS 的 local。 以 get(key)/set(key, value)/delete(key) 讀寫,App 按站點隔離;Java 使用 JSON 字串,Python/QuickJS 使用 JSON 值。 清 TTL 快取不會刪除 Local。
用 js.eval(source, options) 在獨立環境執行短段 JavaScript。 Java 的 options 與回傳值使用 JSON 字串;Python 用 self.js.eval,QuickJS 用 await js.eval。
globals 傳 JSON 變數,timeout 為正整數毫秒,預設 5000;內建 md5X。 回傳最後一個運算式的 JSON 值,不接受 Promise。環境不提供網路、DOM 或模組匯入,完成、逾時或取消後即關閉。 三種語言都需要 QuickJS 模組。
共用 SDK 的 com.github.catvod.Runtime 以 Runtime.context()取得 Application Context、Runtime.activity() 取得目前 Activity,Runtime.toast() 顯示文字提示。Java 需明確匯入這個類別;Python 使用 jclass 取得。這些接口由宿主提供,爬蟲 JAR 使用compileOnly 依賴 SDK。
繼承 Spider 的 Java/Python 爬蟲也可使用 getActivity()/self.getActivity()。Activity 可能為 null/None;自訂原生 UI 在主執行緒顯示時才取得 Activity,不保存頁面引用。Runtime.toast() 由 App 切到主執行緒顯示。 QuickJS 的文字提示可從 action 回傳 msg 欄位。
三語言共用 Dialog 參數與宿主介面,可顯示文字、QR Code、圖片與自訂表單。 Java 用 dialog.show(options),Python 用 self.dialog(options) 同步取得結果; QuickJS 用 await dialog(options),onAction 放在 options 內。
關窗時回傳 {button, values}:button 是按鈕 id,values 以欄位 name 對應字串,多選欄位使用字串陣列。 允許的返回或點外側操作、預設取消按鈕及頁面銷毀時回傳 null/None。 宿主、參數或回呼錯誤時,Java/Python 拋出例外,QuickJS Promise reject。 呼叫取消或爬蟲釋放會關窗並停止等待。
Java/Python 在爬蟲工作執行緒呼叫,回呼也在原工作執行緒執行;宿主負責顯示 UI。 Java 的 options/event/結果使用 JSONObject,Python 使用 dict,QuickJS 使用物件。 Java/Python 回呼同步回傳更新,QuickJS 回呼可回傳 Promise;皆可省略回呼。
Java 需更新編譯用 SDK 與 App,Python/QuickJS 需使用配套 App 與模組。
JSONObject options = new JSONObject("""
{"title":"輸入內容","inputs":[{"name":"text","label":"內容","required":true}]}
""");
JSONObject result = dialog.show(options);
if (result == null) return "{}";
String text = result.getJSONObject("values").getString("text");options = {
"title": "輸入內容",
"inputs": [{"name": "text", "label": "內容", "required": True}],
}
result = self.dialog(options)
if result is None:
return {}
text = result["values"]["text"]const result = await dialog({
title: "輸入內容",
inputs: [{name: "text", label: "內容", required: true}]
});
if (result === null) return {};
const text = result.values.text;| 欄位 | 類型 | 用途 | 預設 |
|---|---|---|---|
title / message | string | 選填標題與說明。 | — |
image | string | 選填 base64 圖片 data URI,如 data:image/png;base64,...;由爬蟲準備內容。onAction 可更新,空字串移除;解碼失敗時,尚未結束的 dialog 會回報錯誤。 | — |
qrcode | string | 選填非空白字串,在本機產生 QR Code;內容不必是網址,無法編碼時回報錯誤。 | — |
copyable | boolean | 可選取 message,並提供複製全文按鈕;複製不關窗,也不呼叫 onAction。 | false |
cancelable | boolean | 允許返回鍵取消;false 時也阻止點外側取消。頁面銷毀及呼叫取消仍會清理視窗。 | true |
inputs | object[] | 選填輸入欄位;name 必填且不可重複,label 預設 name;value 為字串,預設空字串;多選為字串陣列,預設空陣列。 | — |
inputs[].helperText | string | 欄位下方的說明文字,省略或空字串時隱藏。 | — |
inputs[].type | string | text、phone、number、password 為單行,textarea 為多行,select 為單選,multiselect 為多選清單;格式與業務驗證由爬蟲負責。 | text |
inputs[].options | object[] | select/multiselect 必填非空陣列;value 為非空白且唯一的字串,label 預設 value 且不可空白。顯示 label,單選回傳字串,多選依選項順序回傳字串陣列;非空初始值須存在於選項,多選不可重複。 | — |
inputs[].enabled | boolean | 允許操作欄位;停用時略過內建驗證,values 仍包含其值。onAction 可回傳 inputs 陣列,依 name 更新 enabled。 | true |
inputs[].required | boolean | 不可為空或全空白,多選至少選一項。 | false |
inputs[].minLength / maxLength | integer | 字串欄位的非負整數限制,以 Unicode 字元計算;maxLength 不可小於 minLength。留空時略過 minLength。 | 0 / 不限制 |
inputs[].minItems / maxItems | integer | 多選欄位的非負整數限制,以已選數量計算;maxItems 不可小於 minItems。留空時略過 minItems。 | 0 / 不限制 |
buttons | object[] | 選填 1~3 個按鈕,依序對應 positive、negative、neutral;id 與 label 必填且不可為空,id 不可重複。省略時提供確定(ok)與取消。 | — |
buttons[].close | boolean | false 時保留視窗,必須提供 onAction。 | true |
buttons[].enabled | boolean | 允許操作按鈕;onAction 可回傳 buttons 陣列,依 id 更新 enabled。 | true |
buttons[].validate | string[] | 驗證指定的欄位 name;空陣列略過驗證。 | 全部欄位 |
onAction | function | 驗證通過後接收 {button, values},button 為按鈕 id。Java/Python 以第二參數傳入同步回呼,QuickJS 放在 options 內,可回傳 Promise。回傳 {message, qrcode, image, values, errors, inputs, buttons, close} 更新內容或關窗設定。 | — |
三語言 Demo 的 dialog 示範欄位更新與驗證,qrcode 示範 QR Code 更新與複製,dialog_steps 示範圖片更新與連續對話框。 以下以 QuickJS 展示共用參數;範例不記錄或保存輸入內容。
let version = 1;
const initial = "https://example.com/?v=" + version;
await dialog({
title: "QR Code 範例",
message: initial,
qrcode: initial,
copyable: true,
buttons: [{id: "refresh", label: "更新", close: false}, {id: "close", label: "關閉"}],
async onAction({button}) {
if (button !== "refresh") return;
await net.sleep(250);
const content = "https://example.com/?v=" + (++version);
return {qrcode: content, message: content};
}
});QR Code 在本機產生,內容不必是網址。message、QR Code、圖片與輸入欄共用捲動區域。 copyable 為 true 且 message 非空時提供複製全文按鈕;複製不關窗,也不觸發 onAction。 onAction 更新 message 後複製的是最新全文,保留換行與前導零。 要複製 QR Code 原始內容,可將相同字串填入 message;省略 qrcode 即可顯示純文字。
const result = await dialog({
title: "表單範例",
cancelable: true,
inputs: [
{name: "text", label: "內容", helperText: "可輸入多行文字,換行會保留", type: "textarea", enabled: false, required: true, maxLength: 80},
{name: "choice", label: "選項", type: "select", required: true,
options: [{value: "first", label: "選項一"}, {value: "second", label: "選項二"}]},
{name: "choices", label: "多選", type: "multiselect", required: true, maxItems: 2,
options: [{value: "first", label: "選項一"}, {value: "second", label: "選項二"}, {value: "third", label: "選項三"}]}
],
buttons: [
{id: "fill", label: "帶入範例", close: false, validate: []},
{id: "confirm", label: "確認", enabled: false},
{id: "error", label: "錯誤提示", close: false, validate: []}
],
async onAction({button}) {
await net.sleep(250);
if (button === "fill") return {
values: {text: "範例內容", choice: "first", choices: ["first", "second"]},
inputs: [{name: "text", enabled: true}],
buttons: [{id: "confirm", enabled: true}]
};
if (button === "error") return {errors: {text: "這是欄位錯誤提示示範"}};
return {close: true};
}
});
if (result === null) return {};
const text = result.values.text;宿主只負責顯示表單與回傳;按鈕用途、站點請求及業務驗證由爬蟲決定。 onAction 可回傳 message/qrcode/image 更新顯示內容、values 更新欄位值、errors 顯示欄位錯誤,close 覆蓋按鈕的關窗設定。 有非空欄位錯誤時保留視窗與輸入值;回傳 null/None 沿用按鈕設定,QuickJS 也可回傳 undefined。
回呼期間停用欄位與按鈕並顯示處理中,結束後恢復各自的 enabled 設定。 可修正的欄位錯誤用 errors 顯示;回呼拋出例外時會關窗並回報錯誤。
cancelable 預設 true,允許返回或點外側取消;false 時兩種操作都不取消。 預設取消按鈕、頁面銷毀及呼叫取消仍會清理視窗。
textarea 保留換行;select 顯示 label,回傳所選 value,長度驗證也依 value 計算。 multiselect 開啟原生勾選清單,確認後才套用,取消保留原值;回傳依 options 順序排列的字串陣列。
上下鍵移動焦點,多行輸入在首尾行才移出欄位;確認鍵開啟單選/多選清單,返回後回到原欄位。
values 只更新指定欄位,空字串可清空文字或取消單選,空陣列可清空多選。 整批 values 先核對欄位、型別與選項再套用,未知欄位、型別錯誤、不存在或重複的選項會關窗並回報錯誤。 更新不觸發 onAction,必填與長度檢查仍在按鈕操作時執行;帶入或重設按鈕可設 validate:[]。 更新欄位會清除其舊錯誤,同次回傳的 errors 隨後套用;關窗結果包含更新後的 values。
回傳 inputs 陣列以 name、buttons 陣列以 id 指定既有控制項,只更新 enabled;省略保持原值。 未知控制項、重複更新或無效布林值會關窗並回報錯誤;布林參數不接受字串或數字。 停用欄位略過內建驗證,values 仍包含其值。
回傳 qrcode 可更新或新增 QR Code;省略 qrcode 保留原 QR Code,提供時須為可編碼的非空白字串,否則關窗並回報錯誤。 更新 QR Code 保留輸入值;按鈕設定 close:false 或回傳 close:false 可繼續操作。
等待輸入暫停目前呼叫及 App 工作的期限,onAction 執行及 QuickJS 等待 Promise 時恢復計時;網路期限照常生效。
const first = await dialog({
title: "第一步",
image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAAAwCAIAAABhdOiYAAAAbklEQVR4nO3QwQkAIAwEQUuzdDvTDgQJRtE59hsIU1qtmlSOf3B5gAABAvQlUF8ZIECAAAHKFUm7BQQIECBAgAABAgToKaDIAAECBAjQZqC0W0CAAAECBAgQIECAAAECBAgQIAECBAjQLQECFGsAofuPsjrQFugAAAAASUVORK5CYII=",
inputs: [{name: "text", label: "內容", helperText: "確認後進入下一步", required: true}]
});
if (first === null) return {};
const second = await dialog({
title: "第二步",
message: first.values.text,
copyable: true
});
if (second === null) return {};連續對話框依序呼叫即可,QuickJS 使用 await;前一個視窗完成關閉後才回傳。 每一步都檢查 null/None,可在取消時停止後續步驟;步驟與資料傳遞由爬蟲控制。
image 接收爬蟲提供的 base64 圖片 data URI,使用 Android 支援的點陣圖片;圖片取得由爬蟲負責。 onAction 可回傳 image 更新圖片,省略時保留原圖,空字串移除;解碼失敗時,尚未結束的 dialog 會關窗並回報錯誤。 關窗會取消並釋放該圖片請求。
helperText 顯示欄位說明,錯誤清除後會恢復顯示。
手機驗證可把 phone 與 code 放在同一表單,發碼按鈕使用 close: false 與 validate: ["phone"]。 登入成功後關窗,確認 dialog 回傳後才保存憑據;取消時不套用登入結果。驗證碼保持字串,保留前導零。
JAR 自己的 com.github.catvod.spider.Init.init(Context)是可選的初始化入口,與 Runtime 分開;每個爬蟲仍由 App 呼叫 Spider.init(Context, ext)。
home · category{
"class": [
{ "type_id": "movie", "type_name": "電影" }
],
"filters": {
"movie": [
{
"key": "area",
"name": "地區",
"init": "",
"value": [
{ "n": "全部", "v": "" },
{ "n": "台灣", "v": "tw" }
]
}
]
},
"list": [
{
"vod_id": "demo-1",
"vod_name": "範例項目",
"vod_pic": "https://example.com/poster.jpg",
"vod_remarks": "示意"
}
],
"pagecount": 1,
"msg": "選填提示"
}homeContent 使用 class / filters;列表、搜尋、詳情使用 list。filters 第一層鍵必須等於分類的 type_id 值,本例是 movie。
playerContent{
"url": "https://example.com/video/demo-1.m3u8",
"parse": 0,
"header": {
"User-Agent": "ExampleClient/1.0",
"Referer": "https://example.com/"
},
"format": "application/x-mpegURL",
"subs": [
{
"url": "https://example.com/subs/demo-1.vtt",
"name": "繁體中文",
"lang": "zh-TW",
"format": "text/vtt",
"flag": 0
}
],
"danmaku": [
{ "url": "https://example.com/danmaku/demo-1.xml", "name": "示意彈幕" }
],
"artwork": "https://example.com/poster.jpg",
"desc": "示意播放資訊",
"position": 120000
}url 是核心欄位;parse=0 表示此欄位未要求解析,但 jx=1 或配置旗標比對仍可能觸發解析。
| 欄位 | 類型 | 用途 | 預設 |
|---|---|---|---|
web | {url, mode?, cookie?: {url?, keys: string[]}[]} | action 回應的開網頁指令,只執行一次;詳見開網頁章節。 | — |
class | Class[] | 首頁分類清單。 | [] |
list | Vod[] | 首頁、分類與搜尋卡片;詳情取 list[0]。播放結果可用 list[0] 更新該片詳情,預載時延至採用結果才套用。 | [] |
filters | object | 以分類 ID 為鍵,值為 Filter 陣列或單一 Filter。 | {} |
url | string | string[] | Url | 播放 URL 或多畫質清單,三種寫法見下方範例。 | — |
skips | SkipSegment[] | 每集片頭、片中、片尾的跳過區間,start / end 都用毫秒;格式錯誤的項目會忽略。 | [] |
timeshift | object | 直播回看範圍與取源網址 {url, start, window},格式見直播回看章節。 | — |
header | object | string | 播放請求標頭;也接受 JSON 物件字串。整份為空時回退 Site.header,不逐鍵合併。 | {} |
msg | string | 顯示提示文字;只有 code 為 0 時顯示。 | 空字串 |
code | integer | 訊息控制值,不是 HTTP 狀態碼;非 0 時隱藏 msg。 | 0 |
danmaku | Danmaku[] | string | 彈幕清單、單一 URL 或 JSON 陣列字串,可使用檔案或爬蟲即時來源;受 App 彈幕載入設定控制。 | [] |
subs | Sub[] | 外掛字幕清單。 | [] |
playUrl | string | 解析前綴;json: 後接 JSON 解析端點,parse: 後接具名解析器。 | 空字串 |
artwork | string | 更新播放頁顯示的圖片。 | 空字串 |
jxFrom | string | 擴展 JSON 解析結果回報的解析器名稱,用於成功提示;不是播放分組旗標。 | 空字串 |
flag | string | 播放分組與解析比對旗標;未填時 App 補入呼叫 playerContent 時的 flag。 | 由 App 補入 |
desc | string | 清理後更新播放頁描述。 | 空字串 |
format | string | 媒體 MIME type 提示,請填完整類型,例如 application/x-mpegURL。 | — |
click | string | WebView 載入後執行的 JavaScript;Site.click 非空時優先。 | 空字串 |
key | string | App 內部回填的 Site key;爬蟲不必提供,提供也會被覆寫。 | 由 App 補入 |
position | integer | 播放起點,單位毫秒;120000 為 2 分鐘。未填時不覆蓋既有播放進度。 | — |
pagecount | integer | 總頁數;0 或未填表示未知,仍可繼續載入更多。 | 0 |
parse | integer | 1 要求解析;0 仍可能因 jx=1 或配置旗標比對進入解析。 | 0 |
jx | integer | 1 要求進入解析,即使 parse=0。 | 0 |
drm | Drm | 播放 DRM 設定,與 Channel.drm 使用相同物件,見配置字典。 | — |
單一 URL 見上方播放範例。陣列必須以「名稱、URL」成對排列,不是單純的 URL 清單;也可改用 values 物件。頂層 position 是毫秒,Url.position 則是從 0 起算的畫質索引,實際選取仍會由 App 調整。
playerContent{
"parse": 0,
"url": [
"主畫質", "https://example.com/video/main.m3u8",
"低畫質", "https://example.com/video/low.m3u8"
]
}playerContent{
"parse": 0,
"url": {
"values": [
{ "n": "主畫質", "v": "https://example.com/video/main.m3u8" },
{ "n": "低畫質", "v": "https://example.com/video/low.m3u8" }
],
"position": 0
}
}演員或分類超連結使用 Java 的 Json.link(name, target)、 Python 的 self.link(name, target) 或 QuickJS 的 link(name, target)。 target 包含目標分類及篩選,可用 id、name、type_flag、filter。
list[] 中的 Vod 欄位片段{
"vod_play_from": "主線路$$$備用線路",
"vod_play_url": "第 01 集$demo://ep/1#第 02 集$demo://ep/2$$$第 01 集$backup://ep/1"
}使用者點擊某集後,$ 右側的 value 會成為 playerContent(flag, id, …) 的 id。
proxy / localProxy[
200,
"text/plain; charset=utf-8",
"demo response",
{ "Cache-Control": "no-cache" }
]上例是 Python/QuickJS 一般陣列模式:狀態碼、Content-Type、內容、選填標頭;第 5 格 1 表示 Base64 內容。Python 方法名稱是 localProxy,第三格也可直接回傳 bytes。
Java 的一般 Object[] 回傳格式中,第三格必須是 InputStream,第四格標頭選填,不能直接放字串或 bytes;也可只回傳一格 NanoHTTPD.Response。QuickJS 的 from=catvod 另有 JSON response 模式;Node Proxy 則直接轉接 HTTP 回應。
使用 getProxyUrl(params) 產生網址,App 自動加入 do、siteKey 並編碼參數。JAR 模式沒有 siteKey 時會找 com.github.catvod.spider.Proxy.proxy(Map) 靜態入口。
Java/Python/QuickJS 的播放回應沿用 danmaku: [{name, url}]。 即時來源的 url 用 getDanmakuUrl(id) 產生,Python 使用 self.getDanmakuUrl(id);App 會呼叫該站點的 danmaku(id, emit)。 HTTP(S) 來源仍按彈幕檔案載入。
emit(item){
"text": "即時彈幕",
"color": 6737151,
"size": 25,
"type": 1,
"colors": [6737151, 13408767]
}| 欄位 | 類型 | 用途 | 預設 |
|---|---|---|---|
text必要 | string | 解碼後的文字;不得全空白,最多 4096 字元。 | — |
color | integer | RGB 顏色,範圍 0~16777215。 | 16777215(白色) |
size | number | 字級,單位 sp,範圍 0~96;0 跟隨使用者設定。 | 0 |
type | integer | 1 滾動、4 底部、5 頂部、6 反向。 | 1 |
colors | integer[] | 2~8 個 RGB 顏色組成漸層;省略時使用 color。 | — |
狀態也使用 emit,不可同時包含 text。 state 為 ready(訂閱可用)、retrying(恢復中)或 error(終止失敗); msg 選填,最多 512 字元,不放 Cookie、token 或簽名網址。 ready 須確認平台訂閱可用,不能僅以 WebSocket open 判斷。
emit(item){
"state": "retrying",
"msg": "連線中斷,等待恢復"
}App 自行設定 connecting、合併相同 state/msg;初始化例外轉成 error。 error 後停止訂閱並忽略後續回呼,主動停止則清除狀態,不需回報 closed。 狀態記錄在日誌,不會當成彈幕或反覆跳出提示。
emit 接收 Java JSONObject、Python dict 或 QuickJS 物件;Java 用 emit.accept(item) 並回傳 AutoCloseable,Python/QuickJS 回傳停止函式。 切台或關閉時,App 呼叫停止;爬蟲須取消自己的連線與心跳。
爬蟲負責協定、解碼與樣式轉換,App 負責顯示限制,超量或過期訊息可能丟棄。 完整範例見 CatVodSpider 的三語言 Demo。
來源支援依時間取源時,在播放回應或直播列表的 Channel 加入 timeshift。 App 會顯示可拖曳的時間軸,最右側為直播。
playerContent{
"parse": 0,
"url": "https://example.com/live.flv",
"timeshift": {
"url": "https://example.com/replay?time={time}",
"start": 1792700000000,
"window": 7200000
}
}start 是本場直播開始的 Unix 毫秒,window 是最多可回看的毫秒數。 時間軸從本場開始累加,最長不超過 window;可用範圍由來源決定。
App 將網址的 {time} 換成目標 Unix 毫秒後 GET;0 表示重新取得直播來源。 端點回傳直接播放的 url,並可附上 header 與 format。 使用本地代理時,以 getProxyUrl 產生網址。
連續拖曳或切台會取消舊請求;失敗時提示並保留目前播放。 回看期間暫停爬蟲即時彈幕,回到直播後恢復。 從節目表選取回看則使用 catchup,時間軸以該節目為範圍。
在每集的 playerContent 回應頂層加入 skips:
playerContent{
"parse": 0,
"url": "https://example.com/video.m3u8",
"skips": [
{"type": "opening", "start": 0, "end": 90000},
{"type": "middle", "start": 600000, "end": 630000},
{"type": "ending", "start": 1200000}
]
}opening 為片頭、middle 為片中、ending 為片尾。 時間單位是毫秒;start 是開始跳過的時間,end 是要跳到的時間。 上例片中會從 10:00 跳到 10:30。
片頭與片中必填 end,且須晚於 start;片中可提供多段。 片尾可在 start 大於 0 時省略 end,表示換下一集,沒有下一集則跳到片尾。
使用者手動設定的片頭或片尾會優先套用;片中跳過不受影響。
卡片的 action 觸發後,在回應頂層提供 web。
action{
"web": {
"url": "https://example.com/"
}
}action{
"web": {
"url": "https://example.com/game",
"mode": "view"
}
}url 必填,只接受 HTTP 或 HTTPS。mode 省略或填 browser 為一般網頁;view 為獨立內容視窗, 不啟動外部 App,也不會自動全螢幕。兩者返回都先退出全螢幕, 再依網頁歷史回上一頁;沒有歷史才關閉。
action{
"web": {
"url": "https://example.com/login",
"cookie": [
{"keys": ["sid", "token"]},
{"url": "https://login.example.com/", "keys": ["auth"]}
]
}
}cookie 為選填陣列,每組 url 省略時使用開啟網址。同組 keys 必須全部非空, 不同組任一組完成即可;至少一個指定 Cookie 比開啟前改變,且目前頁面與該組的 scheme、host、port 相同,才自動關閉。此條件不代表站點已驗證帳號授權。
一般 ./demo.js 仍由 QuickJS 執行。Node.js 必須以 index.js.md5 作為配置入口,同目錄提供 index.js、index.config.js 與 index.config.js.md5。
App 啟動 bundle 後,讀取 /config 回應的 video 或 data.video,把 video.sites 映射為 node: 站點路由。爬蟲以 HTTP JSON 處理接在各站點路由後的 /init、/home、/category、/detail、/search 與 /play。
/play 的詳情回傳格式見 播放詳情更新。