@mr-quin/dango-manifests 0.1.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 +94 -0
- package/package.json +31 -0
- package/src/manifests/builtin-bilibili.json +224 -0
- package/src/manifests/builtin-dandanplay.json +64 -0
- package/src/manifests/builtin-ddp-compat.json +96 -0
- package/src/manifests/builtin-hanjutv.json +77 -0
- package/src/manifests/builtin-migu.json +104 -0
- package/src/manifests/builtin-tencent.json +178 -0
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# @mr-quin/dango-manifests
|
|
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.
|
|
4
|
+
|
|
5
|
+
This package is data, not code. There is no `index.ts` — consumers import each manifest JSON directly via the `./manifests/*` subpath export.
|
|
6
|
+
|
|
7
|
+
## Shipped manifests
|
|
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). |
|
|
15
|
+
|
|
16
|
+
## Layout
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
src/
|
|
20
|
+
manifests/
|
|
21
|
+
builtin-dandanplay.json
|
|
22
|
+
builtin-ddp-compat.json
|
|
23
|
+
builtin-bilibili.json
|
|
24
|
+
builtin-tencent.json
|
|
25
|
+
__tests__/
|
|
26
|
+
builtin-*.test.ts # per-manifest pipeline tests
|
|
27
|
+
fixtures/ # captured representative responses
|
|
28
|
+
scripts/
|
|
29
|
+
smoke-test.ts # manual end-to-end test against the real backend
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { ManifestRunner, zManifest } from '@mr-quin/dango'
|
|
36
|
+
import builtinDandanplay from '@mr-quin/dango-manifests/manifests/builtin-dandanplay.json' with { type: 'json' }
|
|
37
|
+
|
|
38
|
+
const manifest = zManifest.parse(builtinDandanplay)
|
|
39
|
+
const runner = new ManifestRunner(manifest, { fetcher })
|
|
40
|
+
|
|
41
|
+
const results = await runner.runSearch({ q: 'frieren' })
|
|
42
|
+
const episodes = await runner.runEpisodes({
|
|
43
|
+
bangumiId: results[0].providerIds.bangumiId,
|
|
44
|
+
})
|
|
45
|
+
const danmaku = await runner.runDanmaku({
|
|
46
|
+
episodeId: episodes[0].providerIds.episodeId,
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Routing notes
|
|
51
|
+
|
|
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.
|
|
53
|
+
|
|
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.
|
|
55
|
+
|
|
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.
|
|
57
|
+
|
|
58
|
+
## Smoke testing
|
|
59
|
+
|
|
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.
|
|
61
|
+
|
|
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 |
|
|
67
|
+
|
|
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.
|
|
69
|
+
|
|
70
|
+
## Adding a new manifest
|
|
71
|
+
|
|
72
|
+
1. Drop the JSON file in `src/manifests/`. Use `id: 'builtin:<source>'`.
|
|
73
|
+
2. Capture representative responses from the source's API to `src/__tests__/fixtures/`.
|
|
74
|
+
3. Write a per-manifest test that:
|
|
75
|
+
- Parses the manifest against `zManifest` (catches schema drift)
|
|
76
|
+
- Runs each pipeline with a mocked `FetchLike` against the captured fixtures
|
|
77
|
+
- Asserts the canonical shape the engine emits
|
|
78
|
+
4. (Optional) Add a section to `scripts/smoke-test.ts` to exercise the new manifest against a live endpoint.
|
|
79
|
+
|
|
80
|
+
Each manifest's tests own their own fixtures — don't share fixtures across manifests.
|
|
81
|
+
|
|
82
|
+
## Trust model
|
|
83
|
+
|
|
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.
|
|
85
|
+
|
|
86
|
+
## Scripts
|
|
87
|
+
|
|
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 |
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mr-quin/dango-manifests",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Built-in dango manifests for danmaku sources",
|
|
5
|
+
"author": "Mr-Quin",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Mr-Quin/dango.git",
|
|
11
|
+
"directory": "packages/dango-manifests"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
"./manifests/*.json": "./src/manifests/*.json"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"src/manifests"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"type-check": "tsc -p tsconfig.json",
|
|
24
|
+
"test": "bun test",
|
|
25
|
+
"smoke": "bun run scripts/smoke-test.ts"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@mr-quin/dango": "0.1.0",
|
|
29
|
+
"protobufjs": "^7.5.4"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
{
|
|
2
|
+
"apiVersion": 1,
|
|
3
|
+
"id": "builtin:bilibili",
|
|
4
|
+
"name": "Bilibili",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"hosts": ["api.bilibili.com"],
|
|
7
|
+
"urlMatch": [
|
|
8
|
+
{
|
|
9
|
+
"host": "www.bilibili.com",
|
|
10
|
+
"path": "^/bangumi/play/ss(?<ssid>\\d+)"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"host": "www.bilibili.com",
|
|
14
|
+
"path": "^/bangumi/play/ep(?<epid>\\d+)"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"configSchema": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"properties": {
|
|
20
|
+
"danmakuFormat": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"enum": ["xml", "protobuf"],
|
|
23
|
+
"default": "protobuf",
|
|
24
|
+
"title": "Danmaku format",
|
|
25
|
+
"description": "xml is the legacy endpoint capped at ~3000 comments; protobuf paginates and returns everything"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
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; }"
|
|
31
|
+
},
|
|
32
|
+
"search": {
|
|
33
|
+
"inputs": ["q"],
|
|
34
|
+
"steps": [
|
|
35
|
+
{
|
|
36
|
+
"type": "http",
|
|
37
|
+
"id": "nav",
|
|
38
|
+
"request": {
|
|
39
|
+
"method": "GET",
|
|
40
|
+
"url": "'https://api.bilibili.com/x/web-interface/nav'",
|
|
41
|
+
"credentials": "include",
|
|
42
|
+
"rewriteHeaders": { "Referer": "'https://www.bilibili.com/'" }
|
|
43
|
+
},
|
|
44
|
+
"extract": {
|
|
45
|
+
"imgKey": "$regexExtract(data.wbi_img.img_url, '/([a-f0-9]+)\\\\.png')",
|
|
46
|
+
"subKey": "$regexExtract(data.wbi_img.sub_url, '/([a-f0-9]+)\\\\.png')"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"type": "assign",
|
|
51
|
+
"id": "wbi",
|
|
52
|
+
"values": {
|
|
53
|
+
"mixinKey": "$substring($permute([46,47,18,2,53,8,23,32,15,50,10,31,58,3,45,35,27,43,5,49,33,9,42,19,29,28,14,39,12,38,41,13,37,48,7,16,24,55,40,61,26,17,0,1,60,51,30,4,22,25,54,21,56,59,6,63,57,62,11,36,20,34,44,52], nav.imgKey & nav.subKey), 0, 32)",
|
|
54
|
+
"wts": "$string($now())"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"type": "assign",
|
|
59
|
+
"id": "params",
|
|
60
|
+
"values": {
|
|
61
|
+
"bangumi": "($p := { 'keyword': q, 'search_type': 'media_bangumi', 'wts': wbi.wts }; $merge([$p, { 'w_rid': $md5($sortedQueryString($p) & wbi.mixinKey) }]))",
|
|
62
|
+
"ft": "($p := { 'keyword': q, 'search_type': 'media_ft', 'wts': wbi.wts }; $merge([$p, { 'w_rid': $md5($sortedQueryString($p) & wbi.mixinKey) }]))"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"type": "http",
|
|
67
|
+
"id": "media_bangumi",
|
|
68
|
+
"request": {
|
|
69
|
+
"method": "GET",
|
|
70
|
+
"url": "'https://api.bilibili.com/x/web-interface/wbi/search/type'",
|
|
71
|
+
"query": "params.bangumi",
|
|
72
|
+
"credentials": "include",
|
|
73
|
+
"rewriteHeaders": { "Referer": "'https://www.bilibili.com/'" }
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"type": "http",
|
|
78
|
+
"id": "media_ft",
|
|
79
|
+
"request": {
|
|
80
|
+
"method": "GET",
|
|
81
|
+
"url": "'https://api.bilibili.com/x/web-interface/wbi/search/type'",
|
|
82
|
+
"query": "params.ft",
|
|
83
|
+
"credentials": "include",
|
|
84
|
+
"rewriteHeaders": { "Referer": "'https://www.bilibili.com/'" }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
"output": "[[media_bangumi.data.result, media_ft.data.result].{ 'providerIds': { 'seasonId': season_id, 'mediaId': media_id }, 'indexedId': $string(season_id), 'title': $replace(title, /<[^>]+>/, ''), 'type': season_type_name, 'typeDescription': season_type_name, 'imageUrl': cover, 'episodeCount': ep_size, 'year': pubtime > 0 ? $number($substring($string($fromMillis(pubtime * 1000)), 0, 4)) : undefined }]"
|
|
89
|
+
},
|
|
90
|
+
"episodes": {
|
|
91
|
+
"inputs": ["seasonId"],
|
|
92
|
+
"steps": [
|
|
93
|
+
{
|
|
94
|
+
"type": "http",
|
|
95
|
+
"id": "season",
|
|
96
|
+
"request": {
|
|
97
|
+
"method": "GET",
|
|
98
|
+
"url": "'https://api.bilibili.com/pgc/view/web/season'",
|
|
99
|
+
"query": "{ 'season_id': $string(seasonId) }",
|
|
100
|
+
"credentials": "include",
|
|
101
|
+
"rewriteHeaders": { "Referer": "'https://www.bilibili.com/'" }
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
],
|
|
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
|
+
},
|
|
107
|
+
"season": {
|
|
108
|
+
"inputs": ["seasonId"],
|
|
109
|
+
"steps": [
|
|
110
|
+
{
|
|
111
|
+
"type": "http",
|
|
112
|
+
"id": "season",
|
|
113
|
+
"request": {
|
|
114
|
+
"method": "GET",
|
|
115
|
+
"url": "'https://api.bilibili.com/pgc/view/web/season'",
|
|
116
|
+
"query": "{ 'season_id': $string(seasonId) }",
|
|
117
|
+
"credentials": "include",
|
|
118
|
+
"rewriteHeaders": { "Referer": "'https://www.bilibili.com/'" }
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
],
|
|
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
|
+
},
|
|
124
|
+
"danmaku": [
|
|
125
|
+
{
|
|
126
|
+
"when": "danmakuFormat = 'xml'",
|
|
127
|
+
"inputs": ["cid", "danmakuFormat"],
|
|
128
|
+
"steps": [
|
|
129
|
+
{
|
|
130
|
+
"type": "http",
|
|
131
|
+
"id": "dm",
|
|
132
|
+
"request": {
|
|
133
|
+
"method": "GET",
|
|
134
|
+
"url": "'https://api.bilibili.com/x/v1/dm/list.so'",
|
|
135
|
+
"query": "{ 'oid': $string(cid) }",
|
|
136
|
+
"format": "xml",
|
|
137
|
+
"credentials": "include",
|
|
138
|
+
"rewriteHeaders": { "Referer": "'https://www.bilibili.com/'" }
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"type": "assign",
|
|
143
|
+
"id": "items",
|
|
144
|
+
"values": {
|
|
145
|
+
"all": "[dm.i.d.{ 'parts': $split(`@_p`, ','), 'm': `#text` }]"
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
],
|
|
149
|
+
"output": "[items.all.($parts := parts; $m := $number($parts[1]); { 'p': $parts[0] & ',' & (($m = 2) or ($m = 3) ? '1' : $string($m)) & ',' & $parts[3] & ',' & ($parts[6] ? $parts[6] : ''), 'm': m })]"
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"inputs": ["cid"],
|
|
153
|
+
"steps": [
|
|
154
|
+
{
|
|
155
|
+
"type": "forEach",
|
|
156
|
+
"id": "segments",
|
|
157
|
+
"in": "$range(1, 101)",
|
|
158
|
+
"as": "segmentIndex",
|
|
159
|
+
"request": {
|
|
160
|
+
"method": "GET",
|
|
161
|
+
"url": "'https://api.bilibili.com/x/v2/dm/web/seg.so'",
|
|
162
|
+
"query": "{ 'type': '1', 'oid': $string(cid), 'segment_index': $string(segmentIndex) }",
|
|
163
|
+
"format": "proto",
|
|
164
|
+
"protoSchema": "bili",
|
|
165
|
+
"protoMessage": "dm.v1.DmSegMobileReply",
|
|
166
|
+
"credentials": "include",
|
|
167
|
+
"rewriteHeaders": { "Referer": "'https://www.bilibili.com/'" },
|
|
168
|
+
"acceptStatus": [304]
|
|
169
|
+
},
|
|
170
|
+
"collect": "[elems]",
|
|
171
|
+
"concurrency": 1,
|
|
172
|
+
"throttleMs": 200,
|
|
173
|
+
"breakOn": "$count($) = 0",
|
|
174
|
+
"breakOnConsecutive": 3
|
|
175
|
+
}
|
|
176
|
+
],
|
|
177
|
+
"output": "[segments[mode in [1,2,3,4,5,6]].{ 'p': $string($number(progress) / 1000) & ',' & (($number(mode) = 2) or ($number(mode) = 3) ? '1' : $string(mode)) & ',' & $string(color) & ',' & (midHash ? midHash : ''), 'm': content }]"
|
|
178
|
+
}
|
|
179
|
+
],
|
|
180
|
+
"cookieSet": {
|
|
181
|
+
"url": "https://www.bilibili.com",
|
|
182
|
+
"title": "Log in to Bilibili"
|
|
183
|
+
},
|
|
184
|
+
"loginProbe": {
|
|
185
|
+
"inputs": [],
|
|
186
|
+
"steps": [
|
|
187
|
+
{
|
|
188
|
+
"type": "http",
|
|
189
|
+
"id": "nav",
|
|
190
|
+
"request": {
|
|
191
|
+
"method": "GET",
|
|
192
|
+
"url": "'https://api.bilibili.com/x/web-interface/nav'",
|
|
193
|
+
"credentials": "include",
|
|
194
|
+
"rewriteHeaders": { "Referer": "'https://www.bilibili.com/'" }
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
],
|
|
198
|
+
"output": "nav.data.isLogin = true"
|
|
199
|
+
},
|
|
200
|
+
"parseUrl": {
|
|
201
|
+
"inputs": [],
|
|
202
|
+
"steps": [
|
|
203
|
+
{
|
|
204
|
+
"type": "http",
|
|
205
|
+
"id": "season",
|
|
206
|
+
"request": {
|
|
207
|
+
"method": "GET",
|
|
208
|
+
"url": "'https://api.bilibili.com/pgc/view/web/season'",
|
|
209
|
+
"query": "{ 'season_id': ssid ? $string(ssid) : undefined, 'ep_id': epid ? $string(epid) : undefined }",
|
|
210
|
+
"credentials": "include",
|
|
211
|
+
"rewriteHeaders": { "Referer": "'https://www.bilibili.com/'" }
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"type": "assign",
|
|
216
|
+
"id": "picked",
|
|
217
|
+
"values": {
|
|
218
|
+
"ep": "epid ? ($epIdNum := $number(epid); season.result.episodes[id = $epIdNum]) : season.result.episodes[0]"
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
],
|
|
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
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"apiVersion": 1,
|
|
3
|
+
"id": "builtin:dandanplay",
|
|
4
|
+
"name": "DanDanPlay",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"hosts": ["api.danmaku.weeblify.app"],
|
|
7
|
+
"configSchema": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"properties": {
|
|
10
|
+
"chConvert": {
|
|
11
|
+
"type": "integer",
|
|
12
|
+
"enum": [0, 1, 2],
|
|
13
|
+
"default": 0,
|
|
14
|
+
"title": "Chinese conversion",
|
|
15
|
+
"description": "0 = no conversion, 1 = convert to simplified, 2 = convert to traditional"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"search": {
|
|
20
|
+
"inputs": ["q"],
|
|
21
|
+
"steps": [
|
|
22
|
+
{
|
|
23
|
+
"type": "http",
|
|
24
|
+
"id": "search",
|
|
25
|
+
"request": {
|
|
26
|
+
"method": "GET",
|
|
27
|
+
"url": "'https://api.danmaku.weeblify.app/ddp/v1'",
|
|
28
|
+
"query": "{ 'path': '/v2/search/anime?' & $sortedQueryString({ 'keyword': q }) }"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"output": "[search.animes.{ 'providerIds': { 'animeId': animeId, 'bangumiId': bangumiId }, 'indexedId': $string(animeId), 'title': animeTitle, 'type': type, 'typeDescription': typeDescription, 'imageUrl': imageUrl, 'episodeCount': episodeCount, 'year': $number($substringBefore(startDate, '-')) }]"
|
|
33
|
+
},
|
|
34
|
+
"episodes": {
|
|
35
|
+
"inputs": ["bangumiId"],
|
|
36
|
+
"steps": [
|
|
37
|
+
{
|
|
38
|
+
"type": "http",
|
|
39
|
+
"id": "bangumi",
|
|
40
|
+
"request": {
|
|
41
|
+
"method": "GET",
|
|
42
|
+
"url": "'https://api.danmaku.weeblify.app/ddp/v1'",
|
|
43
|
+
"query": "{ 'path': '/v2/bangumi/' & $string(bangumiId) }"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"output": "[bangumi.bangumi.episodes.{ 'providerIds': { 'episodeId': episodeId, 'animeId': %.animeId, 'bangumiId': %.bangumiId }, 'indexedId': $string(episodeId), 'title': episodeTitle, 'episodeNumber': episodeNumber }]"
|
|
48
|
+
},
|
|
49
|
+
"danmaku": {
|
|
50
|
+
"inputs": ["episodeId"],
|
|
51
|
+
"steps": [
|
|
52
|
+
{
|
|
53
|
+
"type": "http",
|
|
54
|
+
"id": "comments",
|
|
55
|
+
"request": {
|
|
56
|
+
"method": "GET",
|
|
57
|
+
"url": "'https://api.danmaku.weeblify.app/ddp/v1'",
|
|
58
|
+
"query": "{ 'path': '/v2/comment/' & $string(episodeId) & '?' & $sortedQueryString({ 'withRelated': 'true', 'chConvert': chConvert ? $string(chConvert) : undefined }) }"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
"output": "[comments.comments]"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"apiVersion": 1,
|
|
3
|
+
"id": "builtin:ddp-compat",
|
|
4
|
+
"name": "DanDanPlay Compatible",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"hosts": ["*"],
|
|
7
|
+
"configSchema": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"required": ["baseUrl"],
|
|
10
|
+
"properties": {
|
|
11
|
+
"baseUrl": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"title": "Base URL",
|
|
14
|
+
"description": "Root of the DanDanPlay-compatible API (e.g. https://my-server.com)"
|
|
15
|
+
},
|
|
16
|
+
"chConvert": {
|
|
17
|
+
"type": "integer",
|
|
18
|
+
"enum": [0, 1, 2],
|
|
19
|
+
"default": 0,
|
|
20
|
+
"title": "Chinese conversion",
|
|
21
|
+
"description": "0 = no conversion, 1 = convert to simplified, 2 = convert to traditional"
|
|
22
|
+
},
|
|
23
|
+
"auth": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"title": "Authentication",
|
|
26
|
+
"description": "Custom request headers (e.g. Authorization) sent on every request",
|
|
27
|
+
"properties": {
|
|
28
|
+
"enabled": {
|
|
29
|
+
"type": "boolean",
|
|
30
|
+
"default": false,
|
|
31
|
+
"title": "Enable auth headers"
|
|
32
|
+
},
|
|
33
|
+
"headers": {
|
|
34
|
+
"type": "array",
|
|
35
|
+
"title": "Headers",
|
|
36
|
+
"items": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"required": ["key", "value"],
|
|
39
|
+
"properties": {
|
|
40
|
+
"key": { "type": "string", "title": "Header name" },
|
|
41
|
+
"value": { "type": "string", "title": "Value" }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"search": {
|
|
50
|
+
"inputs": ["q", "baseUrl", "auth"],
|
|
51
|
+
"steps": [
|
|
52
|
+
{
|
|
53
|
+
"type": "http",
|
|
54
|
+
"id": "search",
|
|
55
|
+
"request": {
|
|
56
|
+
"method": "GET",
|
|
57
|
+
"url": "$replace(baseUrl, /\\/$/, '') & '/v2/search/anime'",
|
|
58
|
+
"query": "{ 'keyword': q }",
|
|
59
|
+
"headers": "$merge((auth.enabled and auth.headers ? auth.headers : []).{ key: value })"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
"output": "[search.animes.{ 'providerIds': { 'animeId': animeId, 'bangumiId': bangumiId }, 'indexedId': $string(animeId), 'title': animeTitle, 'type': type, 'typeDescription': typeDescription, 'imageUrl': imageUrl, 'episodeCount': episodeCount, 'year': $number($substringBefore(startDate, '-')) }]"
|
|
64
|
+
},
|
|
65
|
+
"episodes": {
|
|
66
|
+
"inputs": ["bangumiId", "baseUrl", "auth"],
|
|
67
|
+
"steps": [
|
|
68
|
+
{
|
|
69
|
+
"type": "http",
|
|
70
|
+
"id": "bangumi",
|
|
71
|
+
"request": {
|
|
72
|
+
"method": "GET",
|
|
73
|
+
"url": "$replace(baseUrl, /\\/$/, '') & '/v2/bangumi/' & $string(bangumiId)",
|
|
74
|
+
"headers": "$merge((auth.enabled and auth.headers ? auth.headers : []).{ key: value })"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
"output": "[bangumi.bangumi.episodes.{ 'providerIds': { 'episodeId': episodeId, 'animeId': %.animeId, 'bangumiId': %.bangumiId }, 'indexedId': $string(episodeId), 'title': episodeTitle, 'episodeNumber': episodeNumber }]"
|
|
79
|
+
},
|
|
80
|
+
"danmaku": {
|
|
81
|
+
"inputs": ["episodeId", "baseUrl", "auth"],
|
|
82
|
+
"steps": [
|
|
83
|
+
{
|
|
84
|
+
"type": "http",
|
|
85
|
+
"id": "comments",
|
|
86
|
+
"request": {
|
|
87
|
+
"method": "GET",
|
|
88
|
+
"url": "$replace(baseUrl, /\\/$/, '') & '/v2/comment/' & $string(episodeId)",
|
|
89
|
+
"query": "{ 'withRelated': 'true', 'chConvert': chConvert ? $string(chConvert) : undefined }",
|
|
90
|
+
"headers": "$merge((auth.enabled and auth.headers ? auth.headers : []).{ key: value })"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
"output": "[comments.comments]"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"apiVersion": 1,
|
|
3
|
+
"id": "builtin:hanjutv",
|
|
4
|
+
"name": "Hanjutv",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"hosts": ["hxqapi.hiyun.tv", "hxqapi.zmdcq.com"],
|
|
7
|
+
"search": {
|
|
8
|
+
"inputs": ["q"],
|
|
9
|
+
"steps": [
|
|
10
|
+
{
|
|
11
|
+
"type": "http",
|
|
12
|
+
"id": "search",
|
|
13
|
+
"request": {
|
|
14
|
+
"method": "GET",
|
|
15
|
+
"url": "'https://hxqapi.hiyun.tv/api/search/s5'",
|
|
16
|
+
"query": "{ 'k': q, 'srefer': 'search_input', 'type': '0', 'page': '1' }",
|
|
17
|
+
"headers": "($uid := 'DA000000000000000000'; $uidMd5 := $md5($uid); $signKey := $base64Encode($substring($uidMd5, 0, 16)); $signIv := $base64Encode($substring($uidMd5, 16, 16)); $ts := $now() * 1000; $installTs := $ts - 1209600000; $said := 'fb3597b87601d5a7'; $oa := '0123456789abcdef'; $payload := { 'emu': 0, 'ou': 0, 'it': $installTs, 'iit': $installTs, 'bs': 0, 'uid': $uid, 'pc': 0, 'tm': 81, 'd8m': '0,0,0,0,0,0,0,4', 'md': 'Redmi Note 12', 'maker': 'Xiaomi', 'osv': '14', 'br': 95, 'rpc': 0, 'scc': 2, 'plc': 6, 'toc': 19, 'tsc': 10, 'ts': $ts, 'pa': 1, 'crec': 0, 'nw': 2, 'px': '0', 'isp': '', 'ai': $said, 'oa': $oa, 'dpc': 0, 'dsc': 0, 'qpc': 0, 'apad': 0, 'pk': 'com.babycloud.hanju' }; $uk := $aesCbcEncrypt($uid, 'ZjM0OXdnaGhlNzg0dHF3aA==', 'ZDN3OGhmOTRmaWRrMzhsaw=='); $sign := $aesCbcEncrypt($string($payload), $signKey, $signIv); { 'app': 'hj', 'ch': 'xiaomi', 'said': $said, 'uk': $uk, 'vn': '6.8.2', 'sign': $sign, 'vc': 'a_8280', 'Accept-Encoding': 'gzip', 'Connection': 'Keep-Alive' })",
|
|
18
|
+
"rewriteHeaders": {
|
|
19
|
+
"User-Agent": "'HanjuTV/6.8.2 (Redmi Note 12; Android 14; Scale/2.00)'"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"output": "($uid := 'DA000000000000000000'; $key := $exists(search.key) and search.key != '' ? search.key : $md5($uid & $string(search.ts)); $mix := $md5($key & '34F9Q53w/HJW8E6Q'); $aesKey := $base64Encode($substring($mix, 0, 16)); $aesIv := $base64Encode($substring($mix, 16, 16)); $raw := $aesCbcDecrypt(search.data, $aesKey, $aesIv, 'none'); $clean := $replace($trim($raw), /[\\u0000-\\u001f\\u007f-\\u009f]/, ''); $decoded := $jsonParse($clean); [$decoded.seriesData.seriesList.{ 'providerIds': { 'sid': $string(sid) }, 'indexedId': $string(sid), 'title': name, 'type': 'tvseries', 'typeDescription': '韩剧', 'imageUrl': $exists(image.thumb) ? image.thumb : '', 'episodeCount': 0, 'year': null }])"
|
|
25
|
+
},
|
|
26
|
+
"episodes": {
|
|
27
|
+
"inputs": ["sid"],
|
|
28
|
+
"steps": [
|
|
29
|
+
{
|
|
30
|
+
"type": "http",
|
|
31
|
+
"id": "detail",
|
|
32
|
+
"request": {
|
|
33
|
+
"method": "GET",
|
|
34
|
+
"url": "'https://hxqapi.hiyun.tv/api/series/detail'",
|
|
35
|
+
"query": "{ 'sid': $string(sid) }",
|
|
36
|
+
"headers": {
|
|
37
|
+
"vc": "'a_8280'",
|
|
38
|
+
"vn": "'6.8.2'",
|
|
39
|
+
"ch": "'xiaomi'",
|
|
40
|
+
"app": "'hj'",
|
|
41
|
+
"Accept-Encoding": "'gzip'"
|
|
42
|
+
},
|
|
43
|
+
"rewriteHeaders": {
|
|
44
|
+
"User-Agent": "'HanjuTV/6.8.2 (Redmi Note 12; Android 14; Scale/2.00)'"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"output": "($sidStr := $string(sid); [detail.playItems[$exists(pid)].{ 'providerIds': { 'sid': $sidStr, 'pid': $string(pid) }, 'indexedId': $string(pid), 'title': $exists(title) and title != '' ? title : ('第' & $string(serialNo) & '集'), 'episodeNumber': $string(serialNo) }])"
|
|
50
|
+
},
|
|
51
|
+
"danmaku": {
|
|
52
|
+
"inputs": ["pid"],
|
|
53
|
+
"steps": [
|
|
54
|
+
{
|
|
55
|
+
"type": "forEach",
|
|
56
|
+
"id": "segments",
|
|
57
|
+
"in": "$range(0, 240)",
|
|
58
|
+
"as": "i",
|
|
59
|
+
"throttleMs": 100,
|
|
60
|
+
"request": {
|
|
61
|
+
"method": "GET",
|
|
62
|
+
"url": "'https://hxqapi.hiyun.tv/api/danmu/playItem/list'",
|
|
63
|
+
"query": "{ 'pid': $string(pid), 'prevId': '0', 'fromAxis': $string(i * 60000), 'toAxis': $string((i + 1) * 60000), 'offset': '0' }",
|
|
64
|
+
"headers": {
|
|
65
|
+
"Content-Type": "'application/json'"
|
|
66
|
+
},
|
|
67
|
+
"rewriteHeaders": {
|
|
68
|
+
"User-Agent": "'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'"
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"collect": "danmus",
|
|
72
|
+
"breakOn": "$count($) = 0"
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"output": "[segments.{ 'cid': $number(did), 'p': $string(t / 1000) & ',' & $string(tp = 2 ? 5 : tp) & ',' & $string($number(sc)), 'm': con }]"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
{
|
|
2
|
+
"apiVersion": 1,
|
|
3
|
+
"id": "builtin:migu",
|
|
4
|
+
"name": "Migu Video",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"hosts": ["jadeite.migu.cn", "v3-sc.miguvideo.com", "webapi.miguvideo.com"],
|
|
7
|
+
"search": {
|
|
8
|
+
"inputs": ["q"],
|
|
9
|
+
"steps": [
|
|
10
|
+
{
|
|
11
|
+
"type": "http",
|
|
12
|
+
"id": "search",
|
|
13
|
+
"request": {
|
|
14
|
+
"method": "POST",
|
|
15
|
+
"url": "'https://jadeite.migu.cn/search/v3/open-search'",
|
|
16
|
+
"headers": {
|
|
17
|
+
"Content-Type": "'application/json'",
|
|
18
|
+
"appId": "'miguvideo'",
|
|
19
|
+
"terminalId": "'www'"
|
|
20
|
+
},
|
|
21
|
+
"body": "{ 'k': q, 'pageIdx': 1, 'pageSize': 20, 'ct': 101, 'mediaSource': 9000000, 'appVersion': '6.1.1.00', 'isCorrectWord': 1, 'copyrightTerminal': 3, 'searchScene': 2, 'uiVersion': 'A3.26.0', 'sid': '1X4A395AL3XS3UV5MT8PQ1L5JUYK6KFM8VKDSDD48S5Y7YX5WY1MOUHRT6512988' }",
|
|
22
|
+
"rewriteHeaders": {
|
|
23
|
+
"Origin": "'https://www.miguvideo.com'",
|
|
24
|
+
"Referer": "'https://www.miguvideo.com/'",
|
|
25
|
+
"User-Agent": "'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36'"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"output": "[search.body.contentInfoList[$exists(shortMediaAsset) and shortMediaAsset.isLong = true].($sma := shortMediaAsset; $eps := $exists($sma.extraData.episodes[0]) ? $sma.extraData.episodes[0] : $sma.pID; { 'providerIds': { 'epsId': $string($eps) }, 'indexedId': $string($eps), 'title': $sma.name, 'type': $sma.contDisplayName, 'typeDescription': $sma.contDisplayName, 'imageUrl': $sma.h5pics.highResolutionV, 'year': $exists($sma.year) and $trim($sma.year) != '' ? $number($trim($sma.year)) : null })]"
|
|
31
|
+
},
|
|
32
|
+
"episodes": {
|
|
33
|
+
"inputs": ["epsId"],
|
|
34
|
+
"steps": [
|
|
35
|
+
{
|
|
36
|
+
"type": "http",
|
|
37
|
+
"id": "info",
|
|
38
|
+
"request": {
|
|
39
|
+
"method": "GET",
|
|
40
|
+
"url": "'https://v3-sc.miguvideo.com/program/v4/cont/content-info/' & $string(epsId) & '/1'",
|
|
41
|
+
"rewriteHeaders": {
|
|
42
|
+
"User-Agent": "'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36'"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"type": "assign",
|
|
48
|
+
"id": "picked",
|
|
49
|
+
"values": {
|
|
50
|
+
"items": "$exists(info.body.data.datas) ? info.body.data.datas : ($exists(info.body.data.playing.pID) ? [{ 'pID': info.body.data.playing.pID, 'name': info.body.data.name, 'episodeNumber': '1' }] : [])"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
"output": "($epsIdStr := $string(epsId); [picked.items.{ 'providerIds': { 'epsId': $epsIdStr, 'pId': $string(pID) }, 'indexedId': $string(pID), 'title': name, 'episodeNumber': $exists(episodeNumber) ? episodeNumber : name }])"
|
|
55
|
+
},
|
|
56
|
+
"danmaku": {
|
|
57
|
+
"inputs": ["epsId", "pId"],
|
|
58
|
+
"steps": [
|
|
59
|
+
{
|
|
60
|
+
"type": "http",
|
|
61
|
+
"id": "info",
|
|
62
|
+
"request": {
|
|
63
|
+
"method": "GET",
|
|
64
|
+
"url": "'https://v3-sc.miguvideo.com/program/v4/cont/content-info/' & $string(pId) & '/1'",
|
|
65
|
+
"rewriteHeaders": {
|
|
66
|
+
"User-Agent": "'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36'"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"type": "assign",
|
|
72
|
+
"id": "plan",
|
|
73
|
+
"values": {
|
|
74
|
+
"duration": "$timeToSeconds($string(info.body.data.playing.duration))",
|
|
75
|
+
"segCount": "$ceil($timeToSeconds($string(info.body.data.playing.duration)) / 30)"
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"type": "forEach",
|
|
80
|
+
"id": "segments",
|
|
81
|
+
"in": "$range(0, plan.segCount)",
|
|
82
|
+
"as": "i",
|
|
83
|
+
"concurrency": 4,
|
|
84
|
+
"throttleMs": 50,
|
|
85
|
+
"request": {
|
|
86
|
+
"method": "GET",
|
|
87
|
+
"url": "'https://webapi.miguvideo.com/gateway/live_barrage/videox/barrage/v2/list/' & $string(epsId) & '/' & $string(pId) & '/' & $string(i * 30) & '/' & $string((i + 1) * 30 < plan.duration ? (i + 1) * 30 : plan.duration) & '/020'",
|
|
88
|
+
"format": "text",
|
|
89
|
+
"headers": {
|
|
90
|
+
"appCode": "'miguvideo_default_h5'"
|
|
91
|
+
},
|
|
92
|
+
"rewriteHeaders": {
|
|
93
|
+
"User-Agent": "'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36'"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"extract": {
|
|
97
|
+
"comments": "($decoded := $jsonParse($gatewayDecrypt($, 'vwwLu7e6ug4HAQMAug8CsA8HD7oHDwuxAg4HAQG6DLA=')); $exists($decoded.body.result) ? $decoded.body.result : [])"
|
|
98
|
+
},
|
|
99
|
+
"collect": "comments"
|
|
100
|
+
}
|
|
101
|
+
],
|
|
102
|
+
"output": "[segments.{ 'cid': $number(cid), 'p': $string(playtime) & ',1,' & $string($hexToInt(textcolor)), 'm': msg }]"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
{
|
|
2
|
+
"apiVersion": 1,
|
|
3
|
+
"id": "builtin:tencent",
|
|
4
|
+
"name": "Tencent Video",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"hosts": ["pbaccess.video.qq.com", "dm.video.qq.com"],
|
|
7
|
+
"urlMatch": [
|
|
8
|
+
{
|
|
9
|
+
"host": "v.qq.com",
|
|
10
|
+
"path": "^/x/cover/(?<cid>[^/]+)/(?<vid>[^/]+)\\.html"
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"search": {
|
|
14
|
+
"inputs": ["q"],
|
|
15
|
+
"steps": [
|
|
16
|
+
{
|
|
17
|
+
"type": "http",
|
|
18
|
+
"id": "search",
|
|
19
|
+
"request": {
|
|
20
|
+
"method": "POST",
|
|
21
|
+
"url": "'https://pbaccess.video.qq.com/trpc.videosearch.mobile_search.MultiTerminalSearch/MbSearch?vplatform=2'",
|
|
22
|
+
"headers": { "Content-Type": "'application/json'" },
|
|
23
|
+
"body": "{ 'version': '25071701', 'clientType': 1, 'filterValue': '', 'uuid': '0379274D-05A0-4EB6-A89C-878C9A460426', 'query': q, 'retry': 0, 'pagenum': 0, 'isPrefetch': true, 'pagesize': 30, 'queryFrom': 0, 'searchDatakey': '', 'transInfo': '', 'isneedQc': true, 'preQid': '', 'adClientInfo': '', 'extraInfo': { 'multi_terminal_pc': '1', 'themeType': '1', 'sugRelatedIds': '{}', 'appVersion': '' } }",
|
|
24
|
+
"rewriteHeaders": {
|
|
25
|
+
"Origin": "'https://v.qq.com'",
|
|
26
|
+
"Referer": "'https://v.qq.com/'"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"type": "assign",
|
|
32
|
+
"id": "picked",
|
|
33
|
+
"values": {
|
|
34
|
+
"items": "$exists(search.data.areaBoxList[boxId = 'MainNeed']) ? search.data.areaBoxList[boxId = 'MainNeed'].itemList : search.data.normalList.itemList"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"output": "[picked.items[videoInfo.episodeSites and $count(videoInfo.episodeSites) > 0].{ 'providerIds': { 'cid': doc.id }, 'indexedId': doc.id, 'title': videoInfo.title, 'type': videoInfo.typeName, 'typeDescription': videoInfo.typeName, 'imageUrl': videoInfo.imgUrl, 'episodeCount': videoInfo.episodeSites[0].totalEpisode, 'year': videoInfo.year > 0 ? videoInfo.year : undefined }]"
|
|
39
|
+
},
|
|
40
|
+
"episodes": {
|
|
41
|
+
"inputs": ["cid"],
|
|
42
|
+
"steps": [
|
|
43
|
+
{
|
|
44
|
+
"type": "forEach",
|
|
45
|
+
"id": "pages",
|
|
46
|
+
"in": "$range(0, 5)",
|
|
47
|
+
"as": "page",
|
|
48
|
+
"request": {
|
|
49
|
+
"method": "POST",
|
|
50
|
+
"url": "'https://pbaccess.video.qq.com/trpc.universal_backend_service.page_server_rpc.PageServer/GetPageData?video_appid=3000010&vversion_name=8.2.96&vversion_platform=2'",
|
|
51
|
+
"headers": { "Content-Type": "'application/json'" },
|
|
52
|
+
"body": "{ 'has_cache': 1, 'pageParams': { 'cid': $string(cid), 'lid': '0', 'vid': '', 'req_from': 'web_mobile', 'page_type': 'detail_operation', 'page_id': 'vsite_episode_list', 'id_type': '1', 'page_size': '100', 'page_context': 'episode_begin=' & $string(page * 100 + 1) & '&episode_end=' & $string((page + 1) * 100) & '&episode_step=100' } }",
|
|
53
|
+
"rewriteHeaders": {
|
|
54
|
+
"Origin": "'https://v.qq.com'",
|
|
55
|
+
"Referer": "'https://v.qq.com/'"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"collect": "[data.module_list_datas[0].module_datas[0].item_data_lists.item_datas[item_params.is_trailer != '1'].item_params]",
|
|
59
|
+
"breakOn": "$count($) < 100",
|
|
60
|
+
"throttleMs": 100
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
"output": "[pages.{ 'providerIds': { 'vid': vid, 'cid': cid }, 'indexedId': vid, 'title': play_title, 'episodeNumber': title, 'alternativeTitle': union_title and union_title != '' ? [union_title] : undefined, 'imageUrl': image_url }]"
|
|
64
|
+
},
|
|
65
|
+
"danmaku": {
|
|
66
|
+
"inputs": ["vid"],
|
|
67
|
+
"steps": [
|
|
68
|
+
{
|
|
69
|
+
"type": "http",
|
|
70
|
+
"id": "base",
|
|
71
|
+
"request": {
|
|
72
|
+
"method": "GET",
|
|
73
|
+
"url": "'https://dm.video.qq.com/barrage/base/' & vid",
|
|
74
|
+
"rewriteHeaders": {
|
|
75
|
+
"Origin": "'https://v.qq.com'",
|
|
76
|
+
"Referer": "'https://v.qq.com/'"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"type": "assign",
|
|
82
|
+
"id": "segmentNames",
|
|
83
|
+
"values": {
|
|
84
|
+
"names": "[base.segment_index.*.segment_name]"
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"type": "forEach",
|
|
89
|
+
"id": "segments",
|
|
90
|
+
"in": "segmentNames.names",
|
|
91
|
+
"as": "segName",
|
|
92
|
+
"concurrency": 4,
|
|
93
|
+
"throttleMs": 100,
|
|
94
|
+
"request": {
|
|
95
|
+
"method": "GET",
|
|
96
|
+
"url": "'https://dm.video.qq.com/barrage/segment/' & vid & '/' & segName",
|
|
97
|
+
"rewriteHeaders": {
|
|
98
|
+
"Origin": "'https://v.qq.com'",
|
|
99
|
+
"Referer": "'https://v.qq.com/'"
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"collect": "barrage_list"
|
|
103
|
+
}
|
|
104
|
+
],
|
|
105
|
+
"output": "[segments.($s := content_style and content_style != '' ? $jsonParse(content_style) : null; $g := $s.gradient_colors; $hex := $exists($g[0]) and $g[0] != '' ? $g[0] : $s.color; $color := $exists($hex) and $hex != '' ? $hexToInt($replace($hex, /^#/, '')) : 16777215; { 'p': $string($number(time_offset) / 1000) & ',1,' & $string($color) & ',' & (vuid ? $string(vuid) : ''), 'm': content })]"
|
|
106
|
+
},
|
|
107
|
+
"cookieSet": {
|
|
108
|
+
"url": "https://v.qq.com",
|
|
109
|
+
"title": "Visit Tencent Video to set cookies"
|
|
110
|
+
},
|
|
111
|
+
"loginProbe": {
|
|
112
|
+
"inputs": [],
|
|
113
|
+
"steps": [
|
|
114
|
+
{
|
|
115
|
+
"type": "http",
|
|
116
|
+
"id": "details",
|
|
117
|
+
"request": {
|
|
118
|
+
"method": "POST",
|
|
119
|
+
"url": "'https://pbaccess.video.qq.com/trpc.universal_backend_service.page_server_rpc.PageServer/GetPageData?video_appid=3000010&vversion_name=8.2.96&vversion_platform=2'",
|
|
120
|
+
"headers": { "Content-Type": "'application/json'" },
|
|
121
|
+
"body": "{ 'has_cache': 1, 'pageParams': { 'cid': 'mzc00200xf3rir6', 'lid': '0', 'vid': 'i0046sewh4r', 'req_from': 'web_mobile', 'page_type': 'detail_operation', 'page_id': 'detail_page_introduction' } }",
|
|
122
|
+
"rewriteHeaders": {
|
|
123
|
+
"Origin": "'https://v.qq.com'",
|
|
124
|
+
"Referer": "'https://v.qq.com/'"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
],
|
|
129
|
+
"output": "details.ret = 0"
|
|
130
|
+
},
|
|
131
|
+
"parseUrl": {
|
|
132
|
+
"inputs": [],
|
|
133
|
+
"steps": [
|
|
134
|
+
{
|
|
135
|
+
"type": "http",
|
|
136
|
+
"id": "details",
|
|
137
|
+
"request": {
|
|
138
|
+
"method": "POST",
|
|
139
|
+
"url": "'https://pbaccess.video.qq.com/trpc.universal_backend_service.page_server_rpc.PageServer/GetPageData?video_appid=3000010&vversion_name=8.2.96&vversion_platform=2'",
|
|
140
|
+
"headers": { "Content-Type": "'application/json'" },
|
|
141
|
+
"body": "{ 'has_cache': 1, 'pageParams': { 'cid': cid, 'lid': '0', 'vid': vid, 'req_from': 'web_mobile', 'page_type': 'detail_operation', 'page_id': 'detail_page_introduction' } }",
|
|
142
|
+
"rewriteHeaders": {
|
|
143
|
+
"Origin": "'https://v.qq.com'",
|
|
144
|
+
"Referer": "'https://v.qq.com/'"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"type": "forEach",
|
|
150
|
+
"id": "pages",
|
|
151
|
+
"in": "$range(0, 5)",
|
|
152
|
+
"as": "page",
|
|
153
|
+
"request": {
|
|
154
|
+
"method": "POST",
|
|
155
|
+
"url": "'https://pbaccess.video.qq.com/trpc.universal_backend_service.page_server_rpc.PageServer/GetPageData?video_appid=3000010&vversion_name=8.2.96&vversion_platform=2'",
|
|
156
|
+
"headers": { "Content-Type": "'application/json'" },
|
|
157
|
+
"body": "{ 'has_cache': 1, 'pageParams': { 'cid': cid, 'lid': '0', 'vid': '', 'req_from': 'web_mobile', 'page_type': 'detail_operation', 'page_id': 'vsite_episode_list', 'id_type': '1', 'page_size': '100', 'page_context': 'episode_begin=' & $string(page * 100 + 1) & '&episode_end=' & $string((page + 1) * 100) & '&episode_step=100' } }",
|
|
158
|
+
"rewriteHeaders": {
|
|
159
|
+
"Origin": "'https://v.qq.com'",
|
|
160
|
+
"Referer": "'https://v.qq.com/'"
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
"collect": "[data.module_list_datas[0].module_datas[0].item_data_lists.item_datas[item_params.is_trailer != '1'].item_params]",
|
|
164
|
+
"breakOn": "$count($) < 100",
|
|
165
|
+
"throttleMs": 100
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"type": "assign",
|
|
169
|
+
"id": "picked",
|
|
170
|
+
"values": {
|
|
171
|
+
"season": "details.data.module_list_datas[0].module_datas[0].item_data_lists.item_datas[0]",
|
|
172
|
+
"ep": "($v := vid; pages[vid = $v])"
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
],
|
|
176
|
+
"output": "{ 'seasonInsert': { 'providerIds': { 'cid': picked.season.item_params.`report.cid` }, 'indexedId': picked.season.item_params.`report.cid`, 'title': picked.season.item_params.title, 'type': $string(picked.season.item_type), 'imageUrl': picked.season.item_params.new_pic_vt, 'episodeCount': picked.season.item_params.episode_all }, 'episodeMeta': $exists(picked.ep) ? { 'providerIds': { 'vid': picked.ep.vid }, 'indexedId': picked.ep.vid, 'title': picked.ep.play_title, 'episodeNumber': picked.ep.title, 'alternativeTitle': picked.ep.union_title and picked.ep.union_title != '' ? [picked.ep.union_title] : undefined, 'imageUrl': picked.ep.image_url } : undefined }"
|
|
177
|
+
}
|
|
178
|
+
}
|