Eight tools (search, video, comments, danmaku, rank, playurl, interact, post_comment) over acfun.cn's public web API, a model-facing skill, a bundle patch with a skill-filesystem row, and offline/live checks. Reads are anonymous; writes require allowWriteActions plus a login cookie.
842 lines
38 KiB
Markdown
842 lines
38 KiB
Markdown
# AcFun (acfun.cn / "A站") Web API — reverse-engineered for a Node.js `fetch` plugin
|
||
|
||
All probes were executed live on **2026-10-27 (Asia/Shanghai)** against `https://www.acfun.cn`
|
||
using desktop Chrome UA and `Referer: https://www.acfun.cn/`. Every endpoint below is marked
|
||
**[VERIFIED]** (called live, real status/JSON pasted) or **[UNVERIFIED]** (contract only — usually
|
||
because it needs a logged-in session I do not have; writes were *never* executed).
|
||
|
||
---
|
||
|
||
## 0. Conventions, host map, and shared plumbing
|
||
|
||
### 0.1 Base hosts
|
||
|
||
| Purpose | Host |
|
||
|---|---|
|
||
| PC web JSON API | `https://www.acfun.cn` (paths under `/rest/pc-direct/...`) |
|
||
| Login / token service | `https://id.app.acfun.cn` (`/rest/web/token/get`) |
|
||
| Like ("顶") service | `https://kuaishouzt.com` (`/rest/zt/interact/add`), also `api.kuaishouzt.com` |
|
||
| App API (legacy, mostly dead) | `https://api-ipv6.acfunchina.com` (`/rest/app/...`) |
|
||
| Video CDN (m3u8/ts) | `https://tx-video.acfun.cn`, `https://tx-safety-video.acfun.cn`, `https://ali-safety-video.acfun.cn` |
|
||
|
||
### 0.2 Two different "video ids" — this is the #1 gotcha
|
||
|
||
The PC web uses **two numeric ids per video**:
|
||
|
||
| Name in payload | Meaning | Example | Used by |
|
||
|---|---|---|---|
|
||
| `dougaId` / `contentId` / `id` | The "ac number" — what appears in `ac12345678` and in `/v/acNNNN` URLs | `42588725` | page URL, comments (`sourceId`), favorites (`resourceId`), banana (`resourceId`), interact (`objectId`) |
|
||
| `videoId` / `currentVideoId` / `currentVideoInfo.id` | Per-part id (a video can have several parts) | `32224197` | playInfo (`videoId`), danmaku (`resourceId`) |
|
||
|
||
**[VERIFIED]** `/v/ac42588725` → HTTP 200, full SSR payload.
|
||
**[VERIFIED]** `/v/ac32224197` (using the *part* id in the URL) → **HTTP 404** (the app shell).
|
||
|
||
So a plugin's `getVideo(acId)` must treat the number the user typed as `dougaId`; the `videoId`
|
||
needed by playInfo/danmaku comes out of the detail payload (`currentVideoId`, `videoList[].id`).
|
||
|
||
### 0.3 Required request headers
|
||
|
||
**[VERIFIED]** JSON reads work **without** `Referer` and even with curl's default UA:
|
||
|
||
```
|
||
no-referer search HTTP 200
|
||
no-UA search HTTP 200 -> {"result":0,"totalNum":547,...}
|
||
```
|
||
|
||
Recommended (matches the browser, avoids surprises):
|
||
|
||
```http
|
||
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36
|
||
Referer: https://www.acfun.cn/
|
||
Accept: application/json, text/plain, */*
|
||
```
|
||
|
||
- `Content-Type: application/x-www-form-urlencoded; charset=UTF-8` for every POST listed here
|
||
(the site's own client sends form-encoded, **not** JSON, for `/rest/pc-direct/*`).
|
||
- **No `sign` / `__` / `md5` parameter is needed** on any read endpoint verified here. The site's
|
||
own comment widget (`@ac/pc-comment.*.js`) posts plain form data with
|
||
`subBiz=jimu&kpf=PC_WEB&kpn=ACFUN_APP` and no signature. (Auth token is a separate thing, §7.)
|
||
|
||
### 0.4 Common response envelope
|
||
|
||
```json
|
||
{"result": 0, "host-name": "hb2-acfun-kce-node20.aliyun"}
|
||
```
|
||
|
||
Observed `result` codes:
|
||
|
||
| `result` | HTTP | Meaning |
|
||
|---|---|---|
|
||
| `0` | 200 | success |
|
||
| `1` | 200 | generic "ok" for some interact endpoints (`/rest/zt/interact/add`) |
|
||
| `11` | 200 | `"网络错误,请稍后再试"` / `"请检查下网络连接是否正常"` — handler ran but request context/params rejected (e.g. POST to a route that wants different params) |
|
||
| `21` | 200 | `"参数格式错误,请仔细阅读API文档"` — missing/malformed parameter |
|
||
| `-401` | **401** | `"user not login"` — route exists, cookie session missing/invalid |
|
||
| `-401` | **401** | `"token value error"` — route exists, requires the `acfun.midground.api_st` token (§7) |
|
||
| `105001` | 200 | app API (`api-ipv6.acfunchina.com`) — auth token invalid/missing |
|
||
|
||
### 0.5 Auth cookies (names only — never logged in here)
|
||
|
||
**[VERIFIED]** an anonymous visit to `https://www.acfun.cn/` sets only:
|
||
|
||
```
|
||
.acfun.cn _did web_83374403393B0029
|
||
www.acfun.cn csrfToken Pkyh8iuCGfRggSGwYox04LCz
|
||
```
|
||
|
||
**[UNVERIFIED]** logged-in session (from community docs for `id.app.acfun.cn/rest/app/login/signin`
|
||
plus the PC web's own `getCookie("auth_key")` usage):
|
||
|
||
```
|
||
acPasstoken <- login response field acPassToken (the main session cookie)
|
||
auth_key <- login response field auth_key (numeric userId)
|
||
_userid <- numeric userId
|
||
acPlatform = 2 (PC web)
|
||
_did, csrfToken (set for anonymous too)
|
||
```
|
||
|
||
Probes with forged values (`acPasstoken=deadbeef; auth_key=123456; _userid=123456; acPlatform=2`)
|
||
still return `{"result":-401,"error_msg":"user not login"}` — proving the server validates the
|
||
session rather than merely checking cookie presence. **Minimum cookie set remains UNVERIFIED.**
|
||
|
||
---
|
||
|
||
## 1. Video detail — [VERIFIED] via SSR page (there is no working `pc-direct` JSON endpoint)
|
||
|
||
### 1.1 Endpoint
|
||
|
||
```
|
||
GET https://www.acfun.cn/v/ac{dougaId} # dougaId == contentId == the "ac" number
|
||
```
|
||
|
||
- HTTP **200**, `text/html`, ~139 KB.
|
||
- The page embeds the entire structured video object as a JS assignment:
|
||
|
||
```js
|
||
window.pageInfo = window.videoInfo = { ... }; // then: window.videoResource = {}; ...
|
||
```
|
||
|
||
Parse with a regex from `window.pageInfo = window.videoInfo = ` up to `window.videoResource`, trim
|
||
the trailing `;`, then `JSON.parse`. In PowerShell (what I actually ran):
|
||
|
||
```powershell
|
||
$c = Get-Content page.html -Raw
|
||
$s = $c.IndexOf('window.pageInfo = window.videoInfo = ') + 38
|
||
$e = $c.IndexOf('window.videoResource', $s)
|
||
$o = ($c.Substring($s, $e-$s).Trim().TrimEnd(';') | ConvertFrom-Json)
|
||
```
|
||
|
||
Node equivalent for the plugin:
|
||
|
||
```js
|
||
const html = await (await fetch(`https://www.acfun.cn/v/ac${acId}`, {headers})).text();
|
||
const start = html.indexOf('window.pageInfo = window.videoInfo = ');
|
||
const end = html.indexOf('window.videoResource', start);
|
||
const info = JSON.parse(html.slice(start + 38, end).trim().replace(/;\s*$/, ''));
|
||
```
|
||
|
||
**Evidence:** HTTP 200, payload length 15,567 chars, 63 top-level keys.
|
||
|
||
```
|
||
HTTP 200 https://www.acfun.cn/v/ac42588725
|
||
LEN 138828 (html) pageInfo JSON LEN 15567
|
||
TOP KEYS: currentVideoId, pctr, commentCountRealValue, groupId, bananaCountShow,
|
||
stowCountShow, giftPeachCountShow, stowCount, channel, likeCount, hasHotComment,
|
||
shareCountShow, isDislike, shareCount, picShareUrl, danmakuCount, currentVideoInfo,
|
||
viewCountShow, dougaId, commentCountTenThousandShow, coverImgInfo, commentCount,
|
||
coverUrl, disableEdit, createTimeMillis, superUbb, shareUrl, status, isFavorite,
|
||
isLike, description, title, belongToSpecifyArubamu, result, videoList, isThrowBanana,
|
||
viewCount, bananaCount, likeCountShow, coverCdnUrls, giftPeachCount, isRewardSupportted,
|
||
commentCountShow, durationMillis, host-name, tagList, danmakuCountShow, createTime,
|
||
user, mkey, priority
|
||
```
|
||
|
||
### 1.2 Field mapping (exactly what a plugin needs)
|
||
|
||
| Plugin field | Path | Sample (ac42588725) |
|
||
|---|---|---|
|
||
| ac number | `dougaId` | `42588725` |
|
||
| part/video id (for playInfo/danmaku) | `currentVideoId` | `32224197` |
|
||
| title | `title` | `【原神】三周年超治愈的歌曲《你呀你呀》` |
|
||
| description | `description` | `""` (many videos have empty) |
|
||
| author name | `user.name` | `天阶夜色凉如水de陈秀凤` |
|
||
| author id | `user.id` | `74428749` |
|
||
| author avatar | `user.headUrl` (also `user.headCdnUrls[].url`) | `…/defaultAvatar.jpg?imageslim` |
|
||
| author signature | `user.signature` | — |
|
||
| author fans | `user.fanCountValue` (number) / `user.fanCount` (display) | — |
|
||
| is following author | `user.isFollowing` | `false` |
|
||
| views | `viewCount` (`viewCountShow` display) | `5682` |
|
||
| danmaku count | `danmakuCount` (`danmakuCountShow`) | `8` |
|
||
| comments | `commentCount` (also `commentCountRealValue`) | `21` |
|
||
| likes/"顶" | `likeCount` (`likeCountShow`) | `217` |
|
||
| favorites/collect | `stowCount` (`stowCountShow`) | `324` |
|
||
| bananas/投蕉 | `bananaCount` (`bananaCountShow` string `"119"`) | `119` |
|
||
| shares | `shareCount` (`shareCountShow`) | `34` |
|
||
| publish time | `createTimeMillis` (ms epoch) / `createTime` (`"2023-10-8"`) | `1696772749620` |
|
||
| duration | `durationMillis` (ms) | `196976` (= 3:16) |
|
||
| cover | `coverUrl` (`coverImgInfo.thumbnailImage.cdnUrls[].url` variants) | `https://tx-free-imgs.acfun.cn/newUpload/…png?imageslim` |
|
||
| channel | `channel.id`, `channel.name`, `channel.parentId`, `channel.parentName` | `106 / 动画综合 / 1 / 动画` |
|
||
| tags | `tagList[]` → `{id, name}` | `[{id:"609267",name:"原神空"},{id:"371390",name:"#原神"},{id:"9546",name:"原神"}]` |
|
||
| parts | `videoList[]` → `{id, title, durationMillis}` | `[{id:"32224197",title:"noTitle",durationMillis:196976}]` |
|
||
| playback (embedded!) | `currentVideoInfo.ksPlayJson` / `ksPlayJsonHevc` | see §4 |
|
||
| viewer state | `isLike`, `isFavorite`, `isThrowBanana`, `isFollowing` | all `false` anonymously |
|
||
| video status | `status` | `2` |
|
||
|
||
### 1.3 Endpoint status (dead paths)
|
||
|
||
**[VERIFIED 404]** — all return the SPA 404 HTML (~102.5 KB `<!DOCTYPE html>` shell):
|
||
|
||
```
|
||
/rest/pc-direct/video/index?videoId=32224197
|
||
/rest/pc-direct/video/detail?videoId=32224197
|
||
/rest/pc-direct/video/getVideoInfo?videoId=32224197
|
||
/rest/pc-direct/video/info?videoId=32224197
|
||
```
|
||
|
||
Caveat: the SSR payload is the only *complete* detail source. The search API (§2) can supply a
|
||
partial substitute (title/author/avatar/counts/cover/duration/channelId/ctime) but has **no
|
||
description and no tag names**, and it only works if the video is searchable.
|
||
|
||
---
|
||
|
||
## 2. Video search — [VERIFIED]
|
||
|
||
### 2.1 Endpoint
|
||
|
||
```
|
||
GET https://www.acfun.cn/rest/pc-direct/search/video?keyword={urlencoded}&page=1&pageSize=20[&sortType=N]
|
||
```
|
||
|
||
Also accepts **POST** with the same form fields (identical JSON result). No referer/auth needed.
|
||
|
||
**Evidence (baseline, the example that already worked):**
|
||
|
||
```
|
||
HTTP 200 len 23026
|
||
{"result":0,"totalNum":600,"videoList":[{...}]}
|
||
```
|
||
|
||
### 2.2 Parameters — and the pagination trap
|
||
|
||
| Param | Status | Notes |
|
||
|---|---|---|
|
||
| `keyword` | **[VERIFIED]** required, URL-encoded UTF-8 | `keyword=原神` → 600 hits |
|
||
| `page` | **[VERIFIED] ignored** | `page=1/2/3` returns the *identical* 30 rows (first id always `32224197`) |
|
||
| `pageSize` | **[VERIFIED] ignored** | `pageSize=5/10/20/50` all return exactly **30** rows |
|
||
| `pageNum`, `pageNo`, `pn`, `offset`, `cursor`, `pcursor`, `currentPage` | **[VERIFIED] ignored** | all identical results (tried each) |
|
||
| `sortType` | **[VERIFIED] honoured** | `0`/`1`/omitted = relevance (default); **`2` = most-viewed** (ordering by `viewCount` desc: `29725, 29452, 8253, 7443, 5682`); `3`/`4` behaved like `2` |
|
||
|
||
Evidence for ignored pagination:
|
||
|
||
```
|
||
pageSize=5 -> HTTP 200 count=30 total=600
|
||
pageSize=20 -> HTTP 200 count=30 total=600
|
||
page=2 -> HTTP 200 count=30 first=32224197 (same as page 1)
|
||
GET 龙 page1 sort2 -> 200 total=600 count=30 first=17777112
|
||
GET 龙 page2 sort2 -> 200 total=600 count=30 first=17777112
|
||
POST (page=2) -> 200 len=22040 (same shape; page still not applied)
|
||
```
|
||
|
||
The response echoes bogus pagination metadata (`pageNum=20`, `pageSize=30` regardless of input),
|
||
which is a server-side bug. **Conclusion: only the first 30 results are obtainable.** Deep search
|
||
paging must go through the SSR search page (`/search?keyword=...&page=N`, server-rendered) instead.
|
||
|
||
`totalNum` sanity check: obscure keyword `qzxwvn` → `totalNum=0`, `videoList=[]`; `test` → 547;
|
||
common terms (`原神`, `编程`, `龙`) → `600` (appears to be a display cap at 600).
|
||
|
||
### 2.3 Response field map (`videoList[]`)
|
||
|
||
```
|
||
top-level keys: result, totalNum, videoList, requestId, pageSize, host-name, pageNum
|
||
videoList[0] keys: groupId, emTitle, viewCountInfo, danmakuCountInfo, displayInfo,
|
||
belongToSpecifyArubamu, channelId, viewCount, commentCount, coverUrl, userImg, videoId,
|
||
contentId, danmuCount, playDuration, userName, userId, id, itemType, ctime, title
|
||
```
|
||
|
||
| Plugin field | Path | Sample |
|
||
|---|---|---|
|
||
| ac number | `contentId` (== `id`) | `42588725` |
|
||
| part id (for playInfo/danmaku) | `videoId` | `32224197` |
|
||
| title | `title` | `【原神】三周年超治愈的歌曲《你呀你呀》` |
|
||
| title with `<em>` highlights | `emTitle` | `【<em>原神</em>】…` |
|
||
| author name | `userName` | `天阶夜色凉如水de陈秀凤` |
|
||
| author id | `userId` | `74428749` |
|
||
| author avatar | `userImg` | `https://tx-free-imgs.acfun.cn/…?imageslim` |
|
||
| views | `viewCount` / `viewCountInfo` | `5682` / `"5682次播放"` |
|
||
| comments | `commentCount` | `21` |
|
||
| danmaku | `danmuCount` / `danmakuCountInfo` | `8` |
|
||
| duration | `playDuration` (`"mm:ss"` string!) | `"03:16"` |
|
||
| published | `ctime` (ms epoch) | `1696772749620` |
|
||
| channel | `channelId` (id only — no name!) | `106` |
|
||
| cover | `coverUrl` | `https://tx-free-imgs.acfun.cn/newUpload/…png?imageslim` |
|
||
| type | `itemType` | `2` (video) |
|
||
| display string | `displayInfo` | `"5682次播放 2023-10-08"` |
|
||
| short description | `decr` (some items only) | — |
|
||
|
||
### 2.4 Sibling search endpoints
|
||
|
||
**[VERIFIED 200]** `GET /rest/pc-direct/search/suggest?keyword=...` — used by the header search box
|
||
(found in `common/widget/searchBox/index.*.js`).
|
||
**[VERIFIED 200, documented]** `GET /rest/pc-direct/search/user?keyword=...` (returns `userList`).
|
||
|
||
---
|
||
|
||
## 3. Comments (评论 / 回复) — [VERIFIED]
|
||
|
||
### 3.1 Main comment list (new "JIMU" comment system — works anonymously)
|
||
|
||
```
|
||
GET https://www.acfun.cn/rest/pc-direct/comment/listByFloor
|
||
?sourceId={dougaId}&sourceType=3&page=1&supportZtEmot=true
|
||
```
|
||
|
||
- `sourceId` = the **ac number** (`dougaId`), *not* `videoId`.
|
||
- `sourceType` = **3** for videos (the embedded comment objects themselves carry
|
||
`"sourceType": 3`, which is the authoritative value). Lenient server quirk: `sourceType=1`
|
||
also returned the same 21 comments; `sourceType=2` returned 0. Use **3**.
|
||
- `supportZtEmot=true` enables Kuaishou emoticon rendering in `content`.
|
||
- Optional `pivotCommentId` / `newPivotCommentId` / `t` (cache-buster ms) accepted.
|
||
|
||
**Evidence (HTTP 200):**
|
||
|
||
```json
|
||
{"result":0,"curPage":1,"isUp":false,"sourceType":3,"totalPage":1,
|
||
"pivotCommentId":0,"commentsMap":{...},"contentUbbVersion":"1.0",
|
||
"pageSize":50,"totalCount":21,"commentIds":["c806796787", ...]}
|
||
```
|
||
|
||
`totalCount=21` matches `commentCount`/`commentCountRealValue` in pageInfo. `pageSize` is 50,
|
||
`totalPage` 1. `commentsMap` is an **object keyed by `"c" + cid`** (`"c806796787"`), and
|
||
`commentIds[]` lists those keys in display order (newest first).
|
||
|
||
**Comment object field map** (from `commentsMap["c…"]`):
|
||
|
||
```
|
||
userHeadImgInfo, avatarFrameImgInfo, imageInfo, cid, userId, isUpDelete, floor, postDate,
|
||
isUp, userName, sourceType, verified, sourceId, isDelete, headUrl, nameRed, avatarFrame,
|
||
quoteId, nameColor, verifiedTypes, deviceModel, isSignedUpCollege, avatarImage, imageUrl,
|
||
content, timestamp
|
||
```
|
||
|
||
| Plugin field | Path | Sample |
|
||
|---|---|---|
|
||
| comment id | `cid` | `806796787` |
|
||
| author id / name | `userId` / `userName` | `77984979` / `不知何处吊湘君de段倩玉` |
|
||
| avatar | `headUrl[]` → `{cdn,url}`; also `userHeadImgInfo.thumbnailImage.cdnUrls[].url` | `https://tx-free-imgs.acfun.cn/…` |
|
||
| content (UBB, may contain `[emot=acfun,ID/]`) | `content` | `[emot=acfun,2789/][emot=acfun,2789/]…` |
|
||
| floor | `floor` | `10` |
|
||
| posted | `timestamp` (ms) / `postDate` (`"07-28 21:32"` local, no year) | `1785245573037` |
|
||
| is uploader | `isUp` | `false` |
|
||
| verified badge | `verified` / `verifiedTypes[]` | `0` |
|
||
| name color | `nameColor`, `nameRed` | `0` |
|
||
| deleted markers | `isDelete`, `isUpDelete` | `false` |
|
||
| device | `deviceModel` | `HUAWEI ART-AL00x` |
|
||
| quote | `quoteId` | `0` |
|
||
| source | `sourceId`, `sourceType` | `42588725`, `3` |
|
||
|
||
Note: `listByFloor` comment objects have **no** `likeCount` / `subCommentCount` fields; reply
|
||
counts come from the sublist call (§3.2). Total count comes from `totalCount` on the list response.
|
||
|
||
### 3.2 Reply / sub-comments — [VERIFIED]
|
||
|
||
```
|
||
GET https://www.acfun.cn/rest/pc-direct/comment/sublist
|
||
?sourceId={dougaId}&sourceType=3&rootCommentId={cid}&page=1&supportZtEmot=true
|
||
```
|
||
|
||
**Evidence (HTTP 200):**
|
||
|
||
```json
|
||
{"result":0,"subCommentCount":0,"curPage":1,"subComments":[],"sourceType":3,
|
||
"totalPage":0,"contentUbbVersion":"1.0","pcursor":"no_more","pageSize":10,
|
||
"contentTitle":"【原神】三周年超治愈的歌曲《你呀你呀》",
|
||
"rootComment":{...}}
|
||
```
|
||
|
||
`subComments[]` items follow the same shape as §3.1 (they additionally carry `subCommentCount` /
|
||
reply-level fields). `pageSize=10`, `pcursor` ∈ `{number, "no_more"}`.
|
||
|
||
### 3.3 Dead / auth-gated comment routes
|
||
|
||
**[VERIFIED]** `POST /rest/pc-direct/comment/list` (`objectId`+`pcursor`+`pageSize`,
|
||
the "SECONDARY"/legacy widget path) — **does not work anonymously**:
|
||
|
||
```
|
||
POST /rest/pc-direct/comment/list (objectId=42588725&pcursor=&pageSize=20&subBiz=jimu&kpf=PC_WEB&kpn=ACFUN_APP)
|
||
-> HTTP 200 {"result":11,"error_msg":"网络错误,请稍后再试"}
|
||
GET /rest/pc-direct/comment/list?objectId=…&objectType=…&page=…&pageSize=…
|
||
-> HTTP 200 {"result":21,"error_msg":"参数格式错误,请仔细阅读API文档。"}
|
||
```
|
||
|
||
`/comment/subCommentList`, `/comment/hotList`, `/comment/replyList` → **[VERIFIED 404]** (SPA shell).
|
||
The legacy `objectType` parameter does not exist in the new widget at all (0 occurrences in
|
||
`@ac/pc-comment.*.js`); classification is by `sourceId`+`sourceType` only.
|
||
|
||
---
|
||
|
||
## 4. Playback (playInfo / m3u8 / quality list) — [VERIFIED]
|
||
|
||
### 4.1 playInfo endpoint
|
||
|
||
```
|
||
GET https://www.acfun.cn/rest/pc-direct/play/playInfo/ksPlayJson?videoId={videoId}
|
||
```
|
||
|
||
(POST with the same form body also works. `videoId` is the **part id** from pageInfo
|
||
`currentVideoId`, not the ac number. Extra `resourceId`/`resourceType` params are ignored.)
|
||
|
||
**Evidence (HTTP 200, 7,494 bytes):**
|
||
|
||
```json
|
||
{"result":0,"playInfo":{
|
||
"streams":[],
|
||
"ksPlayJsonHevc":"{\"version\":\"1.0.0\",\"businessType\":1,...}",
|
||
"domainInfos":…, "duration":…, "sizeType":…, "firstFrameUrls":…,
|
||
"userPlayedSeconds":0, "danmakuGuidePosition":…, "durationMillis":196976,
|
||
"ksPlayJson":"{\"version\":\"1.0.0\",\"businessType\":1,\"mediaType\":1,
|
||
\"videoId\":\"a7856caac8f5d4cc\",\"hideAuto\":false,\"adaptationSet\":[{
|
||
\"id\":0,\"duration\":196976,\"representation\":[
|
||
{\"id\":1,\"url\":\"https://tx-video.acfun.cn/mediacloud/acfun/acfun_video/a7856caac8f5d4cc-…-hls_1080p_h264_6m_1.m3u8?sign=6971e530669cf53e31dbf8a424d4acc6&t=1793091872&us=2128358904\",
|
||
\"backupUrl\":[\"https://ali-safety-video.acfun.cn/…\"],\"maxBitrate\":6000,\"avgBitrate\":1483,
|
||
\"width\":1920,\"heigh\":1080,\"qualityType\":\"1080p+\",\"qualityLabel\":\"1080P+\",…}]}]}",
|
||
"isKsManifest":…, "transcodeInfos":[…]},
|
||
"host-name":"hb2-acfun-kce-node20.aliyun"}
|
||
```
|
||
|
||
Parse: `JSON.parse(playInfo.ksPlayJson)` → `adaptationSet[0].representation[]`.
|
||
|
||
**Quality list actually returned for this video (HTTP 200):**
|
||
|
||
| id | qualityType | qualityLabel | width×height | maxBitrate | avgBitrate |
|
||
|---|---|---|---|---|---|
|
||
| 1 | `1080p+` | 1080P+ | 1920×1080* | 6000 | 1483 |
|
||
| 2 | `1080p` | 1080P | 1920×1080* | 3000 | 1143 |
|
||
| 3 | `720p` | 720P | 1280×… | 2000 | 779 |
|
||
| 4 | `540p` | 540P | 960×… | 900 | 431 |
|
||
| 5 | `360p` | 360P | 640×… | 500 | 288 |
|
||
|
||
\* the JSON field is misspelled `heigh` (not `height`) — read `rep.heigh`.
|
||
|
||
Page-level **quality catalogue** is also embedded in the HTML as
|
||
`window.qualityConfig` (all 16 tiers the player can negotiate):
|
||
`2160p120HDR, 2160p120, 2160p60HDR, 2160p60, 2160pHDR, 2160p, 1080p60HDR, 1080p60, 1080p+,
|
||
1080pHDR, 1080p, 720p60, 720p (defaultSelect), 540p, 480p, 360p` with `{qualityType, qualityLabel,
|
||
definition, disableAutoSwitch?, limitType?, defaultSelect?}`. Individual videos expose only the
|
||
tiers they were transcoded to (the 5 above), so treat `qualityConfig` as the label dictionary and
|
||
`representation[]` as the actual streams.
|
||
|
||
### 4.2 m3u8 reachability — [VERIFIED]
|
||
|
||
```
|
||
m3u8 fetch HTTP 200 ; first line: #EXTM3U
|
||
```
|
||
|
||
- Signed query: `?sign=<md5>&t=<unix seconds>&us=<int>`. **Expiring** (the `t` value is a future
|
||
timestamp ≈ +? minutes; treat the URL as short-lived, re-call playInfo when it 403s).
|
||
- Streams are HLS (`#EXTM3U` with `.ts` slices; `m3u8Slice` also inlines the playlist in pageInfo).
|
||
- CDN hosts seen: `tx-video.acfun.cn`, `tx-safety-video.acfun.cn`, `ali-safety-video.acfun.cn`.
|
||
- Anonymous access is fine — no cookies needed for either playInfo or the CDN.
|
||
|
||
### 4.3 Danmaku (弹幕) read — [VERIFIED]
|
||
|
||
```
|
||
POST https://www.acfun.cn/rest/pc-direct/new-danmaku/list
|
||
Content-Type: application/x-www-form-urlencoded
|
||
|
||
resourceId={videoId}&resourceType=9&enableAdvanced=true&pcursor=1&count=200&sortType=1&asc=false
|
||
```
|
||
|
||
- `resourceId` = **`videoId`** (part id). `resourceType` = **9** (video).
|
||
**[VERIFIED]** `resourceId=42588725` (ac number) → `totalCount=0`; `resourceId=32224197`
|
||
(videoId) → `totalCount=5`, 5 items.
|
||
- Old-style XML danmaku endpoints are dead; this is the JSON API the current PC player uses.
|
||
|
||
**Evidence (HTTP 200):**
|
||
|
||
```json
|
||
{"result":0,"danmakus":[
|
||
{"likeCount":0,"color":16777215,"mode":1,"roleId":0,"danmakuId":287561494,
|
||
"danmakuType":0,"danmakuStyle":…,"danmakuAvatarUrl":…,"isLike":false,"rank":5,
|
||
"createTime":1778494836010,"userId":77697264,"body":"啊啊啊好萌的和声",
|
||
"danmakuImgUrl":…,"position":11880,"size":25}, …],
|
||
"styleDanmakuCount":0,"requestId":"…","pcursor":"no_more",
|
||
"host-name":"hb2-acfun-kce-node24.aliyun","totalCount":5}
|
||
```
|
||
|
||
| Plugin field | Path | Sample |
|
||
|---|---|---|
|
||
| danmaku id | `danmakuId` | `287561494` |
|
||
| text | `body` | `啊啊啊好萌的和声` |
|
||
| time offset in video | `position` (**milliseconds**) | `11880` |
|
||
| color | `color` (int RGB) | `16777215` (white) |
|
||
| mode (scroll/top/bottom) | `mode` | `1` (1 scroll, 5 top, 4 bottom per player conventions) |
|
||
| font size | `size` | `25` |
|
||
| created | `createTime` (ms epoch) | `1778494836010` |
|
||
| author | `userId` | `77697264` |
|
||
| advanced/style | `danmakuType`, `danmakuStyle`, `danmakuImgUrl`, `danmakuAvatarUrl` | — |
|
||
| likes | `likeCount`, `isLike` | `0`, `false` |
|
||
|
||
Notes:
|
||
- Page `danmakuCount` (8) ≠ `totalCount` here (5): some entries are filtered/special; also
|
||
`enableAdvanced=true`/`sortType=1` change the set. Use `totalCount` as authoritative for the API.
|
||
- Pagination: `pcursor` from the response (`"no_more"` when done), `count` page size, `asc` order.
|
||
- Posting danmaku (write, not executed): `POST /rest/pc-direct/new-danmaku/add` with
|
||
`body,color,id(=ac number),mode,position,size,subChannelId,subChannelName,type=douga,videoId`
|
||
— **[UNVERIFIED]**, requires login, `Referer` set to the video page.
|
||
|
||
---
|
||
|
||
## 5. Hot / ranking / feeds — mixed
|
||
|
||
### 5.1 Banana / channel ranking — [VERIFIED] (the only public "hot" list that works)
|
||
|
||
```
|
||
POST https://www.acfun.cn/rest/pc-direct/rank/channel
|
||
Content-Type: application/x-www-form-urlencoded
|
||
|
||
channelId=0&rankPeriod=day&rankLimit=10 # rankPeriod: day | week ; GET also works
|
||
```
|
||
|
||
**Evidence (HTTP 200):**
|
||
|
||
```json
|
||
{"result":0,"host-name":"hb2-acfun-kce-node24.aliyun","rankList":[
|
||
{"groupId":"Ml8wXzE3OTMwNzAzMDY4ODhfMzg1MA_1&-124&&rlg1","userId":17735808,
|
||
"dougaId":"48940670","contentId":48940670,"title":"XBOX联名,一个比一个抽象",
|
||
"userName":"Shi咪","channelName":"生活日常","channelId":86,
|
||
"viewCount":1615,"danmuCount":47,"commentCount":23,"bananaCount":1072,"likeCount":253,
|
||
"durationMillis":358099,"videoCover":"https://tx-free-imgs.acfun.cn/newUpload/…",
|
||
"createTimeMillis":1793014710758,"isFollowing":false,"isLike":false,"isFavorite":false,
|
||
"isThrowBanana":false,"tagList":[…],"videoList":[…],"user":{…},"channel":{…}}]}
|
||
```
|
||
|
||
`rankList[]` is a **superset of the search item shape** — it carries `title`, `description`,
|
||
`channelName`, `tagList`, `durationMillis` and full `user` object, so it is a decent
|
||
"browse without search" feed. Field names: `dougaId`/`contentId` (ac number), `contentTitle`
|
||
(alias of title on some nodes), `videoCover`/`coverUrl`, `createTimeMillis`, `viewCount`,
|
||
`danmuCount`, `commentCount`, `bananaCount`, `likeCount`, `durationMillis`.
|
||
|
||
`POST /rest/pc-direct/rank/banana` (`channelId=0&rankPeriod=DAY`) → **[VERIFIED]** HTTP 200,
|
||
same `rankList` shape (每日香蕉榜).
|
||
|
||
### 5.2 Following feed — [VERIFIED] route, empty without login
|
||
|
||
```
|
||
GET https://www.acfun.cn/rest/pc-direct/feed/webPush?count=20&pcursor=0
|
||
```
|
||
|
||
```json
|
||
{"result":0,"feedList":[],"followUpers":[],"pcursor":"no_more",
|
||
"host-name":"hb2-acfun-kce-node24.aliyun","requestId":"…"}
|
||
```
|
||
|
||
Route exists and returns a valid envelope, but `feedList` is empty anonymously → **login required
|
||
for real content** [UNVERIFIED for populated shape; item fields match the rank item shape per
|
||
community docs: `videoId`, `caption`, `coverUrl`, `playDuration`, `channel`, `viewCount`,
|
||
`commentCount`, `user`, `tag`, `likeCount`, `createTime`].
|
||
|
||
### 5.3 Dead endpoints — **[VERIFIED 404]** (all returned the 102.5 KB SPA shell)
|
||
|
||
```
|
||
GET/POST /rest/pc-direct/rank/video (tried rankId=1, channelId+rankPeriod variants)
|
||
GET/POST /rest/pc-direct/rank/video/day
|
||
/rest/pc-direct/rank/douga
|
||
/rest/pc-direct/rank/list
|
||
/rest/pc-direct/rank/hot
|
||
/rest/pc-direct/feed/recommend
|
||
/rest/pc-direct/feed/hot
|
||
/rest/pc-direct/feed/list
|
||
/rest/pc-direct/recommend/list
|
||
/rest/pc-direct/recommend
|
||
/rest/pc-direct/video/recommend
|
||
/rest/pc-direct/hotVideo
|
||
/rest/pc-direct/index/recommend
|
||
/rest/pc-direct/index/hot
|
||
```
|
||
|
||
There is **no working anonymous "recommended for you" JSON endpoint** as of 2026-10. The homepage
|
||
(`https://www.acfun.cn/`) is fully SSR (pagelets `pagelet_douga`, `pagelet_dance`, …,
|
||
`pagelet_monkey_recommend`); a plugin that wants a home feed must either scrape those pagelets from
|
||
the HTML or use `rank/channel`.
|
||
|
||
---
|
||
|
||
## 6. Interactions (cookie-authenticated) — routes **[VERIFIED]**, semantics **[UNVERIFIED]**
|
||
|
||
> **I never executed a successful write** (no AcFun account, and the task forbids logging in).
|
||
> Everything below is proven to exist by unauthenticated probing: each endpoint answers
|
||
> **HTTP 401 with JSON** instead of the 404 app shell. Parameters come from the site's own
|
||
> JS bundles (`@ac/pc-comment.*.js`, `newVideo/widget/bread/bread.*.js`) — that is the exact
|
||
> contract the browser uses.
|
||
|
||
### 6.1 Like / "顶" (点赞 + 取消) — [VERIFIED route, 401]
|
||
|
||
The PC web does **not** use a `pc-direct` thumb endpoint (all `/rest/pc-direct/thumb/*` → 404).
|
||
It calls the Kuaishou-side interact service:
|
||
|
||
```
|
||
POST https://kuaishouzt.com/rest/zt/interact/add # 点赞
|
||
POST https://kuaishouzt.com/rest/zt/interact/delete # 取消点赞
|
||
Content-Type: application/x-www-form-urlencoded; withCredentials: true
|
||
```
|
||
|
||
Form fields (from `bread.es6`, `likeParams` + token merge):
|
||
|
||
| Param | Value |
|
||
|---|---|
|
||
| `objectId` | **ac number** (`contentId` / `dougaId`) |
|
||
| `objectType` | `2` |
|
||
| `interactType` | `1` |
|
||
| `subBiz` | `mainApp` |
|
||
| `kpn` | `ACFUN_APP` |
|
||
| `kpf` | `PC_WEB` |
|
||
| `acfun.midground.api_st` | token from §7, param name is `` `${sid}_st` `` with `sid = "acfun.midground.api"` |
|
||
| `userId` | `userId` returned by the token call |
|
||
| `extParams` | object: `{isPlaying, showCount, otherBtnClickedCount, playBtnClickedCount}` (jQuery serializes as `extParams[...]`) |
|
||
|
||
Success response: `{"result":1,"hostName":"…"}` (`1`, not `0`). Button state then flips locally.
|
||
|
||
**Evidence (unauthenticated):**
|
||
|
||
```
|
||
POST https://kuaishouzt.com/rest/zt/interact/add
|
||
-> HTTP 401 {"result":-401,"error_msg":"token value error","error_id":""}
|
||
```
|
||
|
||
Note: pageInfo contains `window.likeDomain = "https://kuaishouzt.com"` — use that value rather than
|
||
hardcoding; it is how the page resolves the host.
|
||
|
||
### 6.2 Coin / 投蕉 — [VERIFIED route, 401]
|
||
|
||
```
|
||
POST https://www.acfun.cn/rest/pc-direct/banana/throwBanana
|
||
Content-Type: application/x-www-form-urlencoded
|
||
|
||
resourceId={ac number}&count={1..5}&resourceType=2
|
||
```
|
||
|
||
**Evidence (unauthenticated):**
|
||
|
||
```
|
||
POST /rest/pc-direct/banana/throwBanana (resourceId=42588725&count=2&resourceType=2)
|
||
-> HTTP 401 {"result":-401,"error_msg":"user not login"}
|
||
```
|
||
|
||
App/legacy duplicate (same error class): `POST https://api-ipv6.acfunchina.com/rest/app/banana/throwBanana`
|
||
with `resourceId,count,resourceType`. Success shape per community docs:
|
||
`{"result":0,"extData":{"bananaRealCount":5,"criticalHitInfo":null}}`. Rules: cannot banana your own
|
||
video (page checks `auth_key !== videoInfo.user.id`), 1–5 per call.
|
||
|
||
### 6.3 Favorite / 收藏 — [VERIFIED routes, 401 "token value error"]
|
||
|
||
Videos ("v" category, `resourceType = 9`) use a multi-folder system:
|
||
|
||
```
|
||
POST /rest/pc-direct/favorite/folder/list {resourceId}
|
||
POST /rest/pc-direct/favorite/folder/add {name} # 1-40 chars, CJK/alnum/_
|
||
POST /rest/pc-direct/favorite/resource/add {resourceId, resourceType, addFolderIds} # comma list
|
||
POST /rest/pc-direct/favorite/resource/remove {resourceId, resourceType, delFolderIds}
|
||
POST /rest/pc-direct/favorite/resource/updateFolder {resourceId, resourceType, addFolderIds, delFolderIds}
|
||
```
|
||
|
||
`resourceId` = **ac number**, `resourceType` = **9** for videos. Max 10 folders per operation; a
|
||
folder can be full (`resourceCount >= favoriteCountLimit`) or `type:1` (default folder, no limit).
|
||
|
||
Bangumi (番剧) uses a different pair: `POST /rest/pc-direct/favorite` `{resourceId,resourceType}`
|
||
and `POST /rest/pc-direct/unFavorite` `{resourceIds,resourceType}`.
|
||
|
||
**Evidence (unauthenticated):**
|
||
|
||
```
|
||
POST /rest/pc-direct/favorite/resource/add -> HTTP 401 {"result":-401,"error_msg":"token value error"}
|
||
POST /rest/pc-direct/favorite/folder/list -> HTTP 401 {"result":-401,"error_msg":"token value error"}
|
||
```
|
||
|
||
Important: favorites require the **midground token** (§7), not just the session cookie — the error
|
||
message differs from the plain `user not login` routes. Success is `result:0` plus a `dataList` for
|
||
`folder/list` (`[{folderId, name, type, resourceCount, favoriteCountLimit, inFolder}]`).
|
||
|
||
### 6.4 Follow / 关注 (and unfollow) — [VERIFIED route, 401]
|
||
|
||
```
|
||
POST https://www.acfun.cn/rest/pc-direct/relation/follow
|
||
Content-Type: application/x-www-form-urlencoded
|
||
|
||
toUserId={userId}&action=1&groupId=-1 # action: 1 = follow, 2 = unfollow ; groupId -1 = default
|
||
```
|
||
|
||
Group management (optional): `POST /rest/pc-direct/relation/getGroups`, `getFollows`
|
||
(`action=7` follows / `8` fans, `page`, `count`, `groupId`).
|
||
|
||
**Evidence (unauthenticated):**
|
||
|
||
```
|
||
POST /rest/pc-direct/relation/follow (toUserId=52326&action=1&groupId=-1)
|
||
-> HTTP 401 {"result":-401,"error_msg":"user not login"}
|
||
```
|
||
|
||
### 6.5 Post a comment (and delete/like) — [VERIFIED routes, 401]
|
||
|
||
```
|
||
POST https://www.acfun.cn/rest/pc-direct/comment/add
|
||
Content-Type: application/x-www-form-urlencoded
|
||
|
||
objectId={ac number}&content={UBB text}&replyToCommentId=0&subBiz=jimu&kpf=PC_WEB&kpn=ACFUN_APP
|
||
```
|
||
|
||
- `objectId` = ac number (`dougaId`), **not** `sourceId`/`sourceType` (despite the list endpoint
|
||
using those). This is what `@ac/pc-comment`'s `Ke()` sends.
|
||
- `replyToCommentId = 0` for a top-level comment, or the parent `cid` to reply.
|
||
- `content` is UBB text; emoticons are `[emot=acfun,{emotionId}/]`. `ztExtParams` is optional
|
||
(image payload for the image-comment mode).
|
||
- Success: `{"result":0,"commentId":…}` (the widget then reloads the list).
|
||
- A captcha/verification branch exists for `result` 400001–410999: the site redirects to
|
||
`error_url` and obtains a `{type, token}` before retrying — a plugin should surface that failure
|
||
rather than retry blindly.
|
||
|
||
Related:
|
||
|
||
```
|
||
POST /rest/pc-direct/comment/delete {sourceId, sourceType, commentId}
|
||
POST /rest/pc-direct/comment/like {commentId} # or {sourceId, sourceType, commentId}
|
||
POST /rest/pc-direct/comment/unlike {commentId}
|
||
```
|
||
|
||
**Evidence (unauthenticated):**
|
||
|
||
```
|
||
POST /rest/pc-direct/comment/add (objectId=42588725&content=test&replyToCommentId=0)
|
||
-> HTTP 401 {"result":-401,"error_msg":"user not login"}
|
||
POST /rest/pc-direct/comment/like -> HTTP 401 {"result":-401,"error_msg":"user not login"}
|
||
```
|
||
|
||
### 6.6 Miscellaneous authenticated read routes (useful for "am I logged in?")
|
||
|
||
| Route | Method | Anon result |
|
||
|---|---|---|
|
||
| `/rest/pc-direct/user/personalInfo` | POST | `401 {"result":-401,"error_msg":"user not login"}` [VERIFIED] |
|
||
| `/rest/pc-direct/user/userInfo?userId=` | POST/GET | public profile [UNVERIFIED live, route from bundle] |
|
||
| `/rest/pc-direct/relation/getGroups` | POST | 401 |
|
||
| `/rest/pc-direct/browse/history/list` | POST | 401 |
|
||
| `/rest/pc-direct/user/signIn` | POST | 401 |
|
||
|
||
`personalInfo` doubles as the cheapest session check: `result:0` + `info.userId` means the
|
||
`acPasstoken`/`auth_key` cookie pair is valid.
|
||
|
||
---
|
||
|
||
## 7. Token flow for like / favorite (midground token) — [VERIFIED endpoints, auth required]
|
||
|
||
The site's `common.es6` exposes `getTokenGen("acfun.midground.api")`:
|
||
|
||
```
|
||
POST https://id.app.acfun.cn/rest/web/token/get
|
||
Content-Type: application/x-www-form-urlencoded
|
||
|
||
sid=acfun.midground.api
|
||
```
|
||
|
||
Success (community docs, needs a logged-in cookie jar on `id.app.acfun.cn`):
|
||
|
||
```json
|
||
{"result":0,"ssecurity":"***","userId":625724,
|
||
"acfun.midground.api_st":"***","acfun.midground.api.at":"***"}
|
||
```
|
||
|
||
- `acfun.midground.api_st` is then sent as a **form field** (not header) to
|
||
`kuaishouzt.com/rest/zt/interact/*` and to `/rest/pc-direct/favorite/*`, with `userId`.
|
||
- The PC page itself calls this token endpoint through `common/static/js/common.*.js`
|
||
(`rest/web/token/get` appears there).
|
||
- **[VERIFIED]** unauthenticated: `POST https://id.app.acfun.cn/rest/web/token/get` →
|
||
`HTTP 401 {"result":-401,"error_msg":"token value error"}` — route exists, session required.
|
||
- Token is short-lived; re-fetch per action batch rather than caching for hours. Treat the whole
|
||
like/favorite flow as **[UNVERIFIED]** end-to-end because it cannot be tested without credentials.
|
||
|
||
---
|
||
|
||
## 8. CORS, redirects, rate-limit, encoding quirks
|
||
|
||
### 8.1 CORS — [VERIFIED]
|
||
|
||
Response headers on `/rest/pc-direct/search/video`:
|
||
|
||
```
|
||
HTTP/1.1 200 OK
|
||
Server: Tengine
|
||
Content-Type: application/json;charset=UTF-8
|
||
Access-Control-Allow-Origin: https://www.acfun.cn
|
||
Access-Control-Allow-Credentials: true
|
||
Strict-Transport-Security: max-age=172800
|
||
X-Cache: MISS TCP_MISS
|
||
```
|
||
|
||
- ACAO is pinned to `https://www.acfun.cn` — a browser page on another origin cannot read these
|
||
responses. **Node.js `fetch` is unaffected** (no CORS enforcement), which is why this plugin
|
||
approach works server-side.
|
||
- Cookie-authenticated calls need the cookie jar and `credentials: 'include'` only if you ever run
|
||
in a browser-like context; in Node just set the `Cookie` header.
|
||
- The kuaishouzt interact host answers with the same auth envelope; no ACAO needed from Node.
|
||
|
||
### 8.2 Required headers / redirects — [VERIFIED]
|
||
|
||
- No `Referer` and no custom UA needed for JSON reads (both tested → 200).
|
||
- `www.acfun.cn` serves everything over HTTPS directly; no redirect chains observed
|
||
(`curl -L` not needed). `m.acfun.cn` is a different (mobile) frontend — do not mix.
|
||
- 404s are **HTTP 404 with the full SPA HTML shell**, ~102.5 KB. A plugin MUST check
|
||
`content-type` / body prefix before `JSON.parse`; otherwise a dead path throws a confusing
|
||
JSON syntax error.
|
||
|
||
### 8.3 Rate limiting — [VERIFIED: none hit]
|
||
|
||
- 6 rapid consecutive search calls: `200 200 200 200 200 200`.
|
||
- During a 20-call batch at ~0 ms spacing I saw a few transient `curl` transport failures
|
||
(no response / connection reset), which recovered immediately on retry. That is the only
|
||
throttling-like behavior observed.
|
||
- Practical advice: 200–500 ms between calls, retry once on network error; no `429` seen.
|
||
|
||
### 8.4 Encoding
|
||
|
||
- All JSON is UTF-8 (`Content-Type: application/json;charset=UTF-8`). Decode as UTF-8 — Chinese
|
||
text is not escaped, so a wrong code page garbles it (this bit me in the Windows console only).
|
||
- `Content-Type` for POSTs: `application/x-www-form-urlencoded; charset=UTF-8`. json bodies are
|
||
**not** used by `/rest/pc-direct/*`.
|
||
- `playDuration` in search results is a `"mm:ss"` **string**, not seconds; `durationMillis`
|
||
elsewhere is milliseconds.
|
||
|
||
---
|
||
|
||
## 9. Recommended plugin mapping (what to call for each capability)
|
||
|
||
| Capability | Call |
|
||
|---|---|
|
||
| resolve `ac123` → detail | `GET /v/ac{acId}` → parse `window.pageInfo` (§1) |
|
||
| get part id for playback | `pageInfo.currentVideoId` (or search `videoId`) |
|
||
| search videos | `GET /rest/pc-direct/search/video?keyword=&sortType=0\|2` (first 30 only) |
|
||
| comments | `GET /rest/pc-direct/comment/listByFloor?sourceId={acId}&sourceType=3&page=N` |
|
||
| replies | `GET /rest/pc-direct/comment/sublist?sourceId={acId}&sourceType=3&rootCommentId={cid}&page=N` |
|
||
| play URL / qualities | `GET /rest/pc-direct/play/playInfo/ksPlayJson?videoId={videoId}` → `JSON.parse(playInfo.ksPlayJson)` |
|
||
| danmaku | `POST /rest/pc-direct/new-danmaku/list` (`resourceId={videoId}&resourceType=9&…`) |
|
||
| hot/ranking | `POST /rest/pc-direct/rank/channel` (`channelId=0&rankPeriod=day\|week&rankLimit=N`) |
|
||
| follow feed | `GET /rest/pc-direct/feed/webPush` (login) |
|
||
| like / favorite / follow / banana / comment | §6 + token §7 (login required) |
|
||
|
||
### Dead / do-not-use as of 2026-10-27
|
||
|
||
`/rest/pc-direct/video/{index,detail,getVideoInfo,info}`, `/rest/pc-direct/rank/video*`,
|
||
`/rest/pc-direct/feed/{recommend,hot,list}`, `/rest/pc-direct/recommend*`, `/rest/pc-direct/hot*`,
|
||
`/rest/pc-direct/thumb/*`, `/rest/pc-direct/coin/*`, `/rest/pc-direct/stow|collect/*`,
|
||
`/rest/pc-direct/comment/{list (POST anon), subCommentList, hotList, replyList}`,
|
||
`/rest/pc-direct/play/{playInfo,m3u8}`. All answered **404 + SPA shell** (or `result:11/21` for
|
||
routes that exist but reject anonymous/legacy usage). The current replacements are the SSR page
|
||
(§1), `play/playInfo/ksPlayJson` (§4) and `comment/listByFloor` (§3).
|
||
|
||
### Open items (explicitly UNVERIFIED — no login used)
|
||
|
||
1. End-to-end success shapes for like/favorite/follow/banana/comment-add (only 401 proofs here).
|
||
2. The exact minimum cookie set for authenticated calls (`acPasstoken`+`auth_key` per community
|
||
docs; PC additionally sends `_userid`, `acPlatform=2`, `csrfToken`, `_did`).
|
||
3. Whether `kuaishouzt.com` vs `api.kuaishouzt.com` matters for `/rest/zt/interact/*` in 2026
|
||
(page uses `window.likeDomain` = `https://kuaishouzt.com`; the older `api.` host returned 502 on
|
||
a probe of a different path).
|
||
4. Populated `feed/webPush` item shape (empty without login).
|