@mr-quin/dango-manifests 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,39 +1,58 @@
1
1
  # @mr-quin/dango-manifests
2
2
 
3
- Built-in dango manifests for the danmaku sources the extension ships with. Each manifest is a JSON file declaring the search/episodes/danmaku pipelines a [@mr-quin/dango](../dango/README.md) `ManifestRunner` interprets at runtime — no per-source TypeScript fetching code.
3
+ Built-in dango manifests for a curated set of danmaku sources. Each manifest is a JSON file declaring the search/episodes/danmaku pipelines a [@mr-quin/dango](../dango/README.md) `ManifestRunner` interprets at runtime, with no per-source TypeScript fetching code.
4
4
 
5
- This package is data, not code. There is no `index.ts` — consumers import each manifest JSON directly via the `./manifests/*` subpath export.
5
+ This package is data, not code. There is no `index.ts`: consumers import each manifest JSON directly via the `./manifests/*` subpath export.
6
6
 
7
7
  ## Shipped manifests
8
8
 
9
- | Id | Endpoint | Notes |
10
- | -------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
11
- | `builtin:dandanplay` | `api.danmaku.weeblify.app/ddp/v1` (proxy) | Proxy holds DDP credentials; client carries none. |
12
- | `builtin:ddp-compat` | User-supplied `baseUrl` (configSchema) | Template for user-added `DanDanPlayCompatible` provider configs; auth headers pass through as inputs. |
13
- | `builtin:bilibili` | `api.bilibili.com` | Parallel media_bangumi + media_ft search; xml + protobuf danmaku variants. |
14
- | `builtin:tencent` | `pbaccess.video.qq.com` + `dm.video.qq.com` | POST bodies, page-cursor episode pagination via forEach `breakOn`, two-phase danmaku (segment index → per-segment fetch). |
9
+ | Id | Endpoint | Notes |
10
+ | ------------ | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
11
+ | `dandanplay` | `baseUrl` (default `api.dandanplay.net`) | Unified DDP source. Default official; optional `appId`/`appSecret` signing, `auth` headers, or none (proxy). `baseUrl` overrides the endpoint (proxy / self-hosted). |
12
+ | `bilibili` | `api.bilibili.com` | Parallel media_bangumi + media_ft search; xml + protobuf danmaku variants. |
13
+ | `tencent` | `pbaccess.video.qq.com` + `dm.video.qq.com` | POST bodies, page-cursor episode pagination via forEach `breakOn`, two-phase danmaku (segment index then per-segment fetch). |
14
+ | `hanjutv` | `hxqapi.hiyun.tv` + `hxqapi.zmdcq.com` | Korean-drama source; search, detail-driven episodes, per-segment danmaku. |
15
+ | `mango` | `*.mgtv.com` + `*.hitv.com` | Mango TV; source=imgo search, two-stage showlist episodes, per-minute CDN danmaku shards. |
16
+ | `migu` | `*.migu.cn` + `*.miguvideo.com` | Migu Video; content-info lookup, episodes list, segmented danmaku. |
17
+ | `youku` | `*.youku.com` + `log.mmstat.com` | Youku; HTML-stripped search, openapi episodes, signed per-minute danmaku POSTs (cna guid + \_m_h5_tk token). |
18
+ | `iqiyi` | `*.iqiyi.com` + `mesh.if.iqiyi.com` | iQIYI; /v\_ watch-page search, signed base_info episodes, zlib-deflated XML bullet shards. |
19
+ | `aiyifan` | `yfsp.tv` + `rankv21.tripdata.app` | Aiyifan; homepage-scraped pub/priv keys sign each JSON request; getBarrage danmaku. |
20
+ | `bahamut` | `api.gamer.com.tw` | Bahamut Anime (Traditional Chinese); single danmu.php fetch, time in tenths of a second. |
21
+ | `maiduidui` | `mob.mddcloud.com.cn` | Maiduidui; nested search, listVodSactions episodes, per-minute vodBarrage shards. |
22
+ | `renren` | `api.gorafie.com` + `static-dm.qwdjapp.com` | Renren (foreign drama); array-rooted danmaku with CSV `p` strings. |
23
+ | `sohu` | `*.sohu.com` | Sohu TV; site=1 search, album episodes, paginated dmListAll danmaku (decimal/hex color handling). |
15
24
 
16
25
  ## Layout
17
26
 
18
27
  ```
19
28
  src/
20
29
  manifests/
21
- builtin-dandanplay.json
22
- builtin-ddp-compat.json
23
- builtin-bilibili.json
24
- builtin-tencent.json
30
+ aiyifan.json
31
+ bahamut.json
32
+ bilibili.json
33
+ dandanplay.json
34
+ hanjutv.json
35
+ iqiyi.json
36
+ maiduidui.json
37
+ mango.json
38
+ migu.json
39
+ renren.json
40
+ sohu.json
41
+ tencent.json
42
+ youku.json
25
43
  __tests__/
26
- builtin-*.test.ts # per-manifest pipeline tests
44
+ *.test.ts # per-manifest pipeline tests
27
45
  fixtures/ # captured representative responses
46
+ mockFetcher.ts # shared test fetcher (string + Uint8Array bodies)
28
47
  scripts/
29
- smoke-test.ts # manual end-to-end test against the real backend
48
+ smoke-test.ts # manual end-to-end test against the real API
30
49
  ```
31
50
 
32
51
  ## Usage
33
52
 
34
53
  ```ts
35
54
  import { ManifestRunner, zManifest } from '@mr-quin/dango'
36
- import builtinDandanplay from '@mr-quin/dango-manifests/manifests/builtin-dandanplay.json' with { type: 'json' }
55
+ import builtinDandanplay from '@mr-quin/dango-manifests/manifests/dandanplay.json' with { type: 'json' }
37
56
 
38
57
  const manifest = zManifest.parse(builtinDandanplay)
39
58
  const runner = new ManifestRunner(manifest, { fetcher })
@@ -47,29 +66,64 @@ const danmaku = await runner.runDanmaku({
47
66
  })
48
67
  ```
49
68
 
50
- ## Routing notes
69
+ ## CDN consumption
70
+
71
+ A host can discover and fetch manifests over a CDN without bundling this package at build time. jsDelivr and unpkg serve the published package files, so a host reads `catalog.json` for the index, then fetches each entry's `file` path.
72
+
73
+ ```
74
+ https://cdn.jsdelivr.net/npm/@mr-quin/dango-manifests@<version>/catalog.json
75
+ https://cdn.jsdelivr.net/npm/@mr-quin/dango-manifests@<version>/<file>
76
+ ```
77
+
78
+ `catalog.json` is generated from the manifests on disk and ships with the package:
79
+
80
+ ```jsonc
81
+ {
82
+ "packageVersion": "0.3.0",
83
+ "manifests": [
84
+ {
85
+ "id": "bilibili",
86
+ "name": "Bilibili",
87
+ "version": "0.3.0",
88
+ "apiVersion": 1,
89
+ "file": "src/manifests/bilibili.json",
90
+ },
91
+ // ...
92
+ ],
93
+ }
94
+ ```
51
95
 
52
- **DanDanPlay** (`builtin:dandanplay`) goes through the backend proxy at `api.danmaku.weeblify.app/ddp/v1`. The proxy holds `X-AppId` / signing credentials via a Cloudflare service binding to the DDP microservice — clients carry no secret. The upstream path + query is embedded as the value of the `path` query param (e.g. `?path=/v2/search/anime?keyword=frieren`); the microservice splits that into the real upstream request.
96
+ Each `file` is the in-package path to fetch, e.g.:
97
+
98
+ ```
99
+ https://cdn.jsdelivr.net/npm/@mr-quin/dango-manifests@0.3.0/src/manifests/bilibili.json
100
+ ```
101
+
102
+ unpkg works the same way (`https://unpkg.com/@mr-quin/dango-manifests@<version>/...`). Pin a `<version>` for reproducibility; omit it to track the latest published release.
103
+
104
+ `catalog.json` is regenerated as part of `bun run build`; `bun run check` fails if the committed copy is stale (run `bun run catalog` and commit the result).
105
+
106
+ ## Routing notes
53
107
 
54
- **DanDanPlay-compatible self-hosted** (`builtin:ddp-compat`) takes `baseUrl` via `configSchema` and `authHeaders` as a per-call inputs array of `{key, value}` pairs. One manifest serves any number of user-added `DanDanPlayCompatible` provider configs; the extension passes the user's config blob into each pipeline run.
108
+ **DanDanPlay** (`dandanplay`) is one manifest for every DDP endpoint over the `/api/v2` paths. `baseUrl` (config) defaults to the official `api.dandanplay.net`; point it at a proxy or self-hosted server to override. Auth is picked by config: `appId` + `appSecret` sign each request (`X-Signature: Base64(SHA256(appId + timestamp + path + appSecret))`, secret never sent); else `auth.enabled` + `auth.headers` attach custom headers; else no auth (for a proxy that signs server-side). The official API is just the case with no `baseUrl` and credentials set.
55
109
 
56
- **Bilibili** and **Tencent** call their public APIs directly. Both rely on `rewriteHeaders` for `Origin` / `Referer`, which the extension's `FetchLike` applies via `chrome.declarativeNetRequest`. The smoke script merges those headers in directly since Node fetch lets you set them.
110
+ **Bilibili** and **Tencent** call their public APIs directly. Both rely on `rewriteHeaders` for `Origin` / `Referer`, which a browser host's `FetchLike` applies via a request-header rewrite mechanism such as `chrome.declarativeNetRequest`. The smoke script merges those headers in directly since Node fetch lets you set them.
57
111
 
58
112
  ## Smoke testing
59
113
 
60
- `bun run smoke <source> [keyword]` walks search → first result → episodes → first episode → danmaku against the real backend. Not in CI; useful when adding a manifest or verifying after an upstream change.
114
+ `bun run smoke <source> [keyword]` walks search → first result → episodes → first episode → danmaku against the real API. Not in CI; useful when adding a manifest or verifying after an upstream change.
61
115
 
62
- | Source | Command | Status |
63
- | -------- | ------------------------------- | ----------------------------------------------------- |
64
- | ddp | `bun run smoke ddp Frieren` | works |
65
- | bilibili | `bun run smoke bilibili Naruto` | works (after a cookie warm-up via `www.bilibili.com`) |
66
- | tencent | `bun run smoke tencent 庆余年` | works |
116
+ | Source | Command | Status |
117
+ | -------- | ------------------------------- | -------------------------------------------------------- |
118
+ | ddp | `bun run smoke ddp Frieren` | needs `DDP_APP_ID` / `DDP_APP_SECRET` env (official API) |
119
+ | bilibili | `bun run smoke bilibili Naruto` | works (after a cookie warm-up via `www.bilibili.com`) |
120
+ | tencent | `bun run smoke tencent 庆余年` | works |
67
121
 
68
- The smoke fetcher keeps a per-host cookie jar so bilibili's anti-bot wall lets us through, and it merges `rewriteHeaders` into the outgoing request (Node has no `Origin`/`Referer` restriction). `ddp-compat` has no live smoke because there's no canonical self-hosted server to point at.
122
+ The smoke fetcher keeps a per-host cookie jar so bilibili's anti-bot wall lets us through, and it merges `rewriteHeaders` into the outgoing request (Node has no `Origin`/`Referer` restriction). The default `ddp` smoke hits the official API and needs `DDP_APP_ID` / `DDP_APP_SECRET`.
69
123
 
70
124
  ## Adding a new manifest
71
125
 
72
- 1. Drop the JSON file in `src/manifests/`. Use `id: 'builtin:<source>'`.
126
+ 1. Drop the JSON file in `src/manifests/`. Use `id: '<source>'`.
73
127
  2. Capture representative responses from the source's API to `src/__tests__/fixtures/`.
74
128
  3. Write a per-manifest test that:
75
129
  - Parses the manifest against `zManifest` (catches schema drift)
@@ -77,18 +131,18 @@ The smoke fetcher keeps a per-host cookie jar so bilibili's anti-bot wall lets u
77
131
  - Asserts the canonical shape the engine emits
78
132
  4. (Optional) Add a section to `scripts/smoke-test.ts` to exercise the new manifest against a live endpoint.
79
133
 
80
- Each manifest's tests own their own fixtures — don't share fixtures across manifests.
134
+ Each manifest's tests own their own fixtures, don't share fixtures across manifests.
81
135
 
82
136
  ## Trust model
83
137
 
84
- Manifests in this package are vetted at PR review time and shipped with the extension. They are not user-installable. User-installed manifests are a separate, future feature with explicit consent UX. See dango's [trust model](../dango/README.md#trust-model) for the engine-level invariants that apply regardless of where a manifest comes from.
138
+ Manifests in this package are vetted at PR review time and shipped as built-ins. They are not user-installable. User-installed manifests are a separate concern a host may add with explicit consent UX. See dango's [trust model](../dango/README.md#trust-model) for the engine-level invariants that apply regardless of where a manifest comes from.
85
139
 
86
140
  ## Scripts
87
141
 
88
- | Command | Description |
89
- | ------------------------ | ------------------------------------------------------------------ |
90
- | `bun test` | bun test, fixture-backed pipeline tests |
91
- | `bun run smoke <source>` | Live end-to-end test against the real backend (manual, **not CI**) |
92
- | `bun run type-check` | tsc --noEmit |
93
- | `bun run lint` | oxlint |
94
- | `bun run build` | Compile with tsc |
142
+ | Command | Description |
143
+ | ------------------------ | -------------------------------------------------------------- |
144
+ | `bun test` | bun test, fixture-backed pipeline tests |
145
+ | `bun run smoke <source>` | Live end-to-end test against the real API (manual, **not CI**) |
146
+ | `bun run type-check` | tsc --noEmit |
147
+ | `bun run catalog` | Regenerate `catalog.json` from the manifests on disk |
148
+ | `bun run catalog:check` | Fail if the committed `catalog.json` is stale |
package/catalog.json ADDED
@@ -0,0 +1,96 @@
1
+ {
2
+ "packageVersion": "0.3.0",
3
+ "manifests": [
4
+ {
5
+ "id": "aiyifan",
6
+ "name": "Aiyifan",
7
+ "version": "0.3.0",
8
+ "apiVersion": 1,
9
+ "file": "src/manifests/aiyifan.json"
10
+ },
11
+ {
12
+ "id": "bahamut",
13
+ "name": "Bahamut",
14
+ "version": "0.3.0",
15
+ "apiVersion": 1,
16
+ "file": "src/manifests/bahamut.json"
17
+ },
18
+ {
19
+ "id": "bilibili",
20
+ "name": "Bilibili",
21
+ "version": "0.3.0",
22
+ "apiVersion": 1,
23
+ "file": "src/manifests/bilibili.json"
24
+ },
25
+ {
26
+ "id": "dandanplay",
27
+ "name": "DanDanPlay",
28
+ "version": "0.3.0",
29
+ "apiVersion": 1,
30
+ "file": "src/manifests/dandanplay.json"
31
+ },
32
+ {
33
+ "id": "hanjutv",
34
+ "name": "Hanjutv",
35
+ "version": "0.3.0",
36
+ "apiVersion": 1,
37
+ "file": "src/manifests/hanjutv.json"
38
+ },
39
+ {
40
+ "id": "iqiyi",
41
+ "name": "iQIYI",
42
+ "version": "0.3.0",
43
+ "apiVersion": 1,
44
+ "file": "src/manifests/iqiyi.json"
45
+ },
46
+ {
47
+ "id": "maiduidui",
48
+ "name": "Maiduidui",
49
+ "version": "0.3.0",
50
+ "apiVersion": 1,
51
+ "file": "src/manifests/maiduidui.json"
52
+ },
53
+ {
54
+ "id": "mango",
55
+ "name": "Mango TV",
56
+ "version": "0.3.0",
57
+ "apiVersion": 1,
58
+ "file": "src/manifests/mango.json"
59
+ },
60
+ {
61
+ "id": "migu",
62
+ "name": "Migu Video",
63
+ "version": "0.3.0",
64
+ "apiVersion": 1,
65
+ "file": "src/manifests/migu.json"
66
+ },
67
+ {
68
+ "id": "renren",
69
+ "name": "Renren",
70
+ "version": "0.3.0",
71
+ "apiVersion": 1,
72
+ "file": "src/manifests/renren.json"
73
+ },
74
+ {
75
+ "id": "sohu",
76
+ "name": "Sohu",
77
+ "version": "0.3.0",
78
+ "apiVersion": 1,
79
+ "file": "src/manifests/sohu.json"
80
+ },
81
+ {
82
+ "id": "tencent",
83
+ "name": "Tencent Video",
84
+ "version": "0.3.0",
85
+ "apiVersion": 1,
86
+ "file": "src/manifests/tencent.json"
87
+ },
88
+ {
89
+ "id": "youku",
90
+ "name": "Youku",
91
+ "version": "0.3.0",
92
+ "apiVersion": 1,
93
+ "file": "src/manifests/youku.json"
94
+ }
95
+ ]
96
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mr-quin/dango-manifests",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Built-in dango manifests for danmaku sources",
5
5
  "author": "Mr-Quin",
6
6
  "license": "MIT",
@@ -17,15 +17,18 @@
17
17
  "./manifests/*.json": "./src/manifests/*.json"
18
18
  },
19
19
  "files": [
20
- "src/manifests"
20
+ "src/manifests",
21
+ "catalog.json"
21
22
  ],
22
23
  "scripts": {
23
24
  "type-check": "tsc -p tsconfig.json",
24
25
  "test": "bun test",
26
+ "catalog": "bun run scripts/gen-catalog.ts",
27
+ "catalog:check": "bun run scripts/gen-catalog.ts --check",
25
28
  "smoke": "bun run scripts/smoke-test.ts"
26
29
  },
27
30
  "devDependencies": {
28
- "@mr-quin/dango": "0.1.0",
29
- "protobufjs": "^7.5.4"
31
+ "@bufbuild/protobuf": "^2",
32
+ "@mr-quin/dango": "0.1.0"
30
33
  }
31
34
  }
@@ -0,0 +1,152 @@
1
+ {
2
+ "apiVersion": 1,
3
+ "id": "aiyifan",
4
+ "name": "Aiyifan",
5
+ "version": "0.3.0",
6
+ "hosts": ["www.yfsp.tv", "rankv21.tripdata.app", "m10.yfsp.tv"],
7
+ "search": {
8
+ "inputs": ["q"],
9
+ "steps": [
10
+ {
11
+ "type": "http",
12
+ "id": "config",
13
+ "request": {
14
+ "method": "GET",
15
+ "url": "'https://www.yfsp.tv/'",
16
+ "format": "text",
17
+ "rewriteHeaders": {
18
+ "User-Agent": "'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0 Safari/537.36'"
19
+ }
20
+ },
21
+ "extract": {
22
+ "pub": "$regexExtract($, '\"pConfig\"\\\\s*:\\\\s*\\\\{\\\\s*\"publicKey\"\\\\s*:\\\\s*\"([^\"]+)\"')",
23
+ "priv": "$regexExtract($, '\"privateKey\"\\\\s*:\\\\s*\\\\[\\\\s*\"([^\"]+)\"')"
24
+ }
25
+ },
26
+ {
27
+ "type": "assign",
28
+ "id": "qs",
29
+ "values": {
30
+ "canon": "'tags=' & q & '&orderby=4&page=1&size=10&desc=1&isserial=-1'"
31
+ }
32
+ },
33
+ {
34
+ "type": "http",
35
+ "id": "search",
36
+ "request": {
37
+ "method": "GET",
38
+ "url": "'https://rankv21.tripdata.app/v3/list/briefsearch'",
39
+ "query": "{ 'tags': q, 'orderby': '4', 'page': '1', 'size': '10', 'desc': '1', 'isserial': '-1', 'vv': $md5(config.pub & '&' & $lowercase(qs.canon) & '&' & config.priv), 'pub': config.pub }",
40
+ "rewriteHeaders": {
41
+ "User-Agent": "'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0 Safari/537.36'"
42
+ }
43
+ }
44
+ }
45
+ ],
46
+ "output": "[search.data.info.result[contxt and title].{ 'providerIds': { 'vid': contxt }, 'indexedId': contxt, 'title': title, 'type': atypeName ? atypeName : 'Aiyifan', 'year': postTime ? $number($substring(postTime, 0, 4)) : undefined, 'imageUrl': imgPath }]"
47
+ },
48
+ "episodes": {
49
+ "inputs": ["vid"],
50
+ "steps": [
51
+ {
52
+ "type": "http",
53
+ "id": "config",
54
+ "request": {
55
+ "method": "GET",
56
+ "url": "'https://www.yfsp.tv/'",
57
+ "format": "text",
58
+ "rewriteHeaders": {
59
+ "User-Agent": "'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0 Safari/537.36'"
60
+ }
61
+ },
62
+ "extract": {
63
+ "pub": "$regexExtract($, '\"pConfig\"\\\\s*:\\\\s*\\\\{\\\\s*\"publicKey\"\\\\s*:\\\\s*\"([^\"]+)\"')",
64
+ "priv": "$regexExtract($, '\"privateKey\"\\\\s*:\\\\s*\\\\[\\\\s*\"([^\"]+)\"')"
65
+ }
66
+ },
67
+ {
68
+ "type": "assign",
69
+ "id": "qs",
70
+ "values": {
71
+ "canon": "'cinema=1&vid=' & vid & '&lsk=1&taxis=0&cid=0,1,4,152'"
72
+ }
73
+ },
74
+ {
75
+ "type": "http",
76
+ "id": "playlist",
77
+ "request": {
78
+ "method": "GET",
79
+ "url": "'https://m10.yfsp.tv/v3/video/languagesplaylist'",
80
+ "query": "{ 'cinema': '1', 'vid': vid, 'lsk': '1', 'taxis': '0', 'cid': '0,1,4,152', 'vv': $md5(config.pub & '&' & $lowercase(qs.canon) & '&' & config.priv), 'pub': config.pub }",
81
+ "rewriteHeaders": {
82
+ "User-Agent": "'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0 Safari/537.36'"
83
+ }
84
+ }
85
+ }
86
+ ],
87
+ "output": "[playlist.data.info.playList[key].{ 'providerIds': { 'epKey': key }, 'indexedId': key, 'title': name and $length(name) > 0 ? name : ('第' & $string($$.i + 1) & '集'), 'episodeNumber': $exists(id) ? $number(id) : undefined }]"
88
+ },
89
+ "danmaku": {
90
+ "inputs": ["epKey"],
91
+ "steps": [
92
+ {
93
+ "type": "http",
94
+ "id": "config",
95
+ "request": {
96
+ "method": "GET",
97
+ "url": "'https://www.yfsp.tv/'",
98
+ "format": "text",
99
+ "rewriteHeaders": {
100
+ "User-Agent": "'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0 Safari/537.36'"
101
+ }
102
+ },
103
+ "extract": {
104
+ "pub": "$regexExtract($, '\"pConfig\"\\\\s*:\\\\s*\\\\{\\\\s*\"publicKey\"\\\\s*:\\\\s*\"([^\"]+)\"')",
105
+ "priv": "$regexExtract($, '\"privateKey\"\\\\s*:\\\\s*\\\\[\\\\s*\"([^\"]+)\"')"
106
+ }
107
+ },
108
+ {
109
+ "type": "assign",
110
+ "id": "qs1",
111
+ "values": {
112
+ "canon": "'cinema=1&id=' & epKey & '&a=0&lang=none&usersign=1&region=GL.&device=0&isMasterSupport=1'"
113
+ }
114
+ },
115
+ {
116
+ "type": "http",
117
+ "id": "video",
118
+ "request": {
119
+ "method": "GET",
120
+ "url": "'https://m10.yfsp.tv/v3/video/play'",
121
+ "query": "{ 'cinema': '1', 'id': epKey, 'a': '0', 'lang': 'none', 'usersign': '1', 'region': 'GL.', 'device': '0', 'isMasterSupport': '1', 'vv': $md5(config.pub & '&' & $lowercase(qs1.canon) & '&' & config.priv), 'pub': config.pub }",
122
+ "rewriteHeaders": {
123
+ "User-Agent": "'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0 Safari/537.36'"
124
+ }
125
+ },
126
+ "extract": {
127
+ "uniqueKey": "data.info[0].uniqueKey"
128
+ }
129
+ },
130
+ {
131
+ "type": "assign",
132
+ "id": "qs2",
133
+ "values": {
134
+ "canon": "'cinema=1&page=1&size=30000&uniqueKey=' & video.uniqueKey"
135
+ }
136
+ },
137
+ {
138
+ "type": "http",
139
+ "id": "bar",
140
+ "request": {
141
+ "method": "GET",
142
+ "url": "'https://m10.yfsp.tv/api/video/getBarrage'",
143
+ "query": "{ 'cinema': '1', 'page': '1', 'size': '30000', 'uniqueKey': video.uniqueKey, 'vv': $md5(config.pub & '&' & $lowercase(qs2.canon) & '&' & config.priv), 'pub': config.pub }",
144
+ "rewriteHeaders": {
145
+ "User-Agent": "'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0 Safari/537.36'"
146
+ }
147
+ }
148
+ }
149
+ ],
150
+ "output": "[bar.data.info[contxt or content].{ 'p': $string(second ? second : 0) & ',' & (position = 1 ? '5' : '1') & ',' & $string($hexToInt($replace(color ? color : '#FFFFFF', /^#/, ''))) & ',', 'm': contxt ? contxt : content }]"
151
+ }
152
+ }
@@ -0,0 +1,61 @@
1
+ {
2
+ "apiVersion": 1,
3
+ "id": "bahamut",
4
+ "name": "Bahamut",
5
+ "version": "0.3.0",
6
+ "hosts": ["api.gamer.com.tw"],
7
+ "search": {
8
+ "inputs": ["q"],
9
+ "steps": [
10
+ {
11
+ "type": "http",
12
+ "id": "search",
13
+ "request": {
14
+ "method": "GET",
15
+ "url": "'https://api.gamer.com.tw/mobile_app/anime/v1/search.php'",
16
+ "query": "{ 'kw': q }",
17
+ "rewriteHeaders": {
18
+ "User-Agent": "'Anime/2.29.2 (7N5749MM3F.tw.com.gamer.anime; build:972; iOS 26.0.0) Alamofire/5.6.4'"
19
+ }
20
+ }
21
+ }
22
+ ],
23
+ "output": "[search.anime[video_sn and title].{ 'providerIds': { 'videoSn': $string(video_sn) }, 'indexedId': $string(video_sn), 'title': title, 'type': '动漫', 'year': $exists(info) ? $number($regexExtract(info, '([12][890][0-9][0-9])')) : undefined, 'imageUrl': cover, 'episodeCount': $exists(info) ? $number($regexExtract(info, '共\\\\s*(\\\\d+)\\\\s*集')) : undefined }]"
24
+ },
25
+ "episodes": {
26
+ "inputs": ["videoSn"],
27
+ "steps": [
28
+ {
29
+ "type": "http",
30
+ "id": "video",
31
+ "request": {
32
+ "method": "GET",
33
+ "url": "'https://api.gamer.com.tw/anime/v1/video.php'",
34
+ "query": "{ 'videoSn': videoSn }",
35
+ "rewriteHeaders": {
36
+ "User-Agent": "'Anime/2.29.2 (7N5749MM3F.tw.com.gamer.anime; build:972; iOS 26.0.0) Alamofire/5.6.4'"
37
+ }
38
+ }
39
+ }
40
+ ],
41
+ "output": "[video.data.anime.episodes.*[$exists(videoSn)].{ 'providerIds': { 'videoSn': $string(videoSn) }, 'indexedId': $string(videoSn), 'title': '第' & $string(episode) & '集', 'episodeNumber': $exists(episode) ? $number(episode) : undefined }]"
42
+ },
43
+ "danmaku": {
44
+ "inputs": ["videoSn"],
45
+ "steps": [
46
+ {
47
+ "type": "http",
48
+ "id": "fetch",
49
+ "request": {
50
+ "method": "GET",
51
+ "url": "'https://api.gamer.com.tw/anime/v1/danmu.php'",
52
+ "query": "{ 'geo': 'TW,HK', 'videoSn': videoSn }",
53
+ "rewriteHeaders": {
54
+ "User-Agent": "'Anime/2.29.2 (7N5749MM3F.tw.com.gamer.anime; build:972; iOS 26.0.0) Alamofire/5.6.4'"
55
+ }
56
+ }
57
+ }
58
+ ],
59
+ "output": "[fetch.data.danmu[text].{ 'p': $string(time / 10) & ',' & (position = 1 ? '5' : (position = 2 ? '4' : '1')) & ',' & $string($hexToInt($replace(color ? color : '#FFFFFF', /^#/, ''))) & ',' & (userid ? userid : ''), 'm': text }]"
60
+ }
61
+ }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "apiVersion": 1,
3
- "id": "builtin:bilibili",
3
+ "id": "bilibili",
4
4
  "name": "Bilibili",
5
- "version": "1.0.0",
5
+ "version": "0.3.0",
6
6
  "hosts": ["api.bilibili.com"],
7
7
  "urlMatch": [
8
8
  {
@@ -26,8 +26,8 @@
26
26
  }
27
27
  }
28
28
  },
29
- "protoSchemas": {
30
- "bili": "syntax = \"proto3\"; package dm.v1; message DanmakuElem { int64 id = 1; int32 progress = 2; int32 mode = 3; int32 fontsize = 4; uint32 color = 5; string midHash = 6; string content = 7; int64 ctime = 8; int32 weight = 9; string action = 10; int32 pool = 11; string idStr = 12; } message DmSegMobileReply { repeated DanmakuElem elems = 1; }"
29
+ "protoDescriptors": {
30
+ "bili": "Cp4CCgtkbV92MS5wcm90bxIFZG0udjEiyAEKC0Rhbm1ha3VFbGVtEgoKAmlkGAEgASgDEhAKCHByb2dyZXNzGAIgASgFEgwKBG1vZGUYAyABKAUSEAoIZm9udHNpemUYBCABKAUSDQoFY29sb3IYBSABKA0SDwoHbWlkSGFzaBgGIAEoCRIPCgdjb250ZW50GAcgASgJEg0KBWN0aW1lGAggASgDEg4KBndlaWdodBgJIAEoBRIOCgZhY3Rpb24YCiABKAkSDAoEcG9vbBgLIAEoBRINCgVpZFN0chgMIAEoCSI1ChBEbVNlZ01vYmlsZVJlcGx5EiEKBWVsZW1zGAEgAygLMhIuZG0udjEuRGFubWFrdUVsZW1iBnByb3RvMw=="
31
31
  },
32
32
  "search": {
33
33
  "inputs": ["q"],
@@ -44,7 +44,8 @@
44
44
  "extract": {
45
45
  "imgKey": "$regexExtract(data.wbi_img.img_url, '/([a-f0-9]+)\\\\.png')",
46
46
  "subKey": "$regexExtract(data.wbi_img.sub_url, '/([a-f0-9]+)\\\\.png')"
47
- }
47
+ },
48
+ "cache": { "key": "bilibili:wbi-nav", "ttlSeconds": 43200 }
48
49
  },
49
50
  {
50
51
  "type": "assign",
@@ -102,7 +103,7 @@
102
103
  }
103
104
  }
104
105
  ],
105
- "output": "[season.result.episodes[$not($contains(badge ? badge : '', '预告'))].{ 'providerIds': { 'cid': cid, 'aid': aid, 'bvid': bvid, 'epid': id }, 'indexedId': $string(cid), 'title': long_title and long_title != '' ? long_title : show_title, 'episodeNumber': $match(show_title, /^\\d+$/) ? $number(show_title) : (show_title and show_title != '' ? show_title : undefined), 'imageUrl': cover, 'alternativeTitle': share_copy and share_copy != '' ? [share_copy] : undefined }]"
106
+ "output": "[season.result.episodes[$not($contains(badge ? badge : '', '预告'))].{ 'providerIds': { 'cid': cid, 'aid': aid, 'bvid': bvid, 'epid': id }, 'indexedId': $string(cid), 'title': long_title and long_title != '' ? long_title : show_title, 'episodeNumber': $match(title, /^\\d+$/) ? $number(title) : (title and title != '' ? title : undefined), 'imageUrl': cover, 'alternativeTitle': share_copy and share_copy != '' ? [share_copy] : undefined }]"
106
107
  },
107
108
  "season": {
108
109
  "inputs": ["seasonId"],
@@ -119,7 +120,7 @@
119
120
  }
120
121
  }
121
122
  ],
122
- "output": "season.result ? { 'providerIds': { 'seasonId': season.result.season_id, 'mediaId': season.result.media_id }, 'indexedId': $string(season.result.season_id), 'title': $replace(season.result.title, /<[^>]+>/, ''), 'type': $string(season.result.type), 'imageUrl': season.result.cover, 'episodeCount': $count(season.result.episodes) } : undefined"
123
+ "output": "season.result ? { 'providerIds': { 'seasonId': season.result.season_id, 'mediaId': season.result.media_id }, 'indexedId': $string(season.result.season_id), 'title': $replace(season.result.title, /<[^>]+>/, ''), 'type': season.result.season_type_name ? season.result.season_type_name : $string(season.result.type), 'imageUrl': season.result.cover, 'episodeCount': $count(season.result.episodes[$not($contains(badge ? badge : '', '预告'))]) } : undefined"
123
124
  },
124
125
  "danmaku": [
125
126
  {
@@ -219,6 +220,6 @@
219
220
  }
220
221
  }
221
222
  ],
222
- "output": "{ 'seasonInsert': { 'providerIds': { 'seasonId': season.result.season_id }, 'indexedId': $string(season.result.season_id), 'title': $replace(season.result.title, /<[^>]+>/, ''), 'type': $string(season.result.type), 'imageUrl': season.result.cover, 'episodeCount': $count(season.result.episodes) }, 'episodeMeta': $exists(picked.ep) ? { 'providerIds': { 'cid': picked.ep.cid, 'aid': picked.ep.aid, 'bvid': picked.ep.bvid, 'epid': picked.ep.id }, 'indexedId': $string(picked.ep.cid), 'title': picked.ep.long_title and picked.ep.long_title != '' ? picked.ep.long_title : picked.ep.show_title, 'episodeNumber': $match(picked.ep.show_title, /^\\d+$/) ? $number(picked.ep.show_title) : (picked.ep.show_title and picked.ep.show_title != '' ? picked.ep.show_title : undefined), 'imageUrl': picked.ep.cover, 'externalLink': picked.ep.link } : undefined }"
223
+ "output": "{ 'seasonInsert': { 'providerIds': { 'seasonId': season.result.season_id }, 'indexedId': $string(season.result.season_id), 'title': $replace(season.result.title, /<[^>]+>/, ''), 'type': $string(season.result.type), 'imageUrl': season.result.cover, 'episodeCount': $count(season.result.episodes) }, 'episodeMeta': $exists(picked.ep) ? { 'providerIds': { 'cid': picked.ep.cid, 'aid': picked.ep.aid, 'bvid': picked.ep.bvid, 'epid': picked.ep.id }, 'indexedId': $string(picked.ep.cid), 'title': picked.ep.long_title and picked.ep.long_title != '' ? picked.ep.long_title : picked.ep.show_title, 'episodeNumber': $match(picked.ep.title, /^\\d+$/) ? $number(picked.ep.title) : (picked.ep.title and picked.ep.title != '' ? picked.ep.title : undefined), 'imageUrl': picked.ep.cover, 'externalLink': picked.ep.link } : undefined }"
223
224
  }
224
225
  }