- scan.acfun.cn QR flow (start -> long-poll scanResult -> acceptResult) with rotated-signature handling, background poller, durable session in the Host credential service, and cookie handoff to the provider - Client half: Settings -> AcFun page (status, QR code, poll, logout) over a same-origin /acfun-panel JSON API with cross-origin rejection - acfun_moment / acfun_moment_comments / acfun_topic read tools, moment_like / moment_unlike / moment_banana actions, and comment posting on moments - acfun_account status tool; docs for the QR contract, the 广场/动态 research verdict, and the client-composition restart requirement
37 KiB
AcFun 广场 / 动态 (moment feed) — reverse-engineered for a Node.js fetch plugin
All probes were executed live on 2026-10-27 (Asia/Shanghai) against acfun.cn hosts
(www.acfun.cn, m.acfun.cn, api-ipv6.acfunchina.com, kuaishouzt.com) using a desktop Chrome
User-Agent and Referer: https://www.acfun.cn/ (or https://m.acfun.cn/ for mobile hosts).
No AcFun account was used. Every claim below is marked [VERIFIED] (called live, real
status/JSON/payload pasted) or [UNVERIFIED] (contract read out of the site's own JS, or a write
that could only be probed up to its auth error). No write was ever executed.
Probe scripts live in %TEMP%\acfun-moment-probe\ (p1…p33*.mjs) and are not part of the repo.
0. Verdict first (read this if nothing else)
| Question | Answer |
|---|---|
| Is there an official web 广场/动态 feed? | No. https://www.acfun.cn/moment/ → 302 → /404. The www nav contains no 动态/广场 entry at all. /member/feeds exists but 302s anonymous visitors to the login page. There is no moment bundle in the www app (40 bundles / 50 /rest/ paths scanned; only one internal bundle mentions moment). |
| Does a usable moment read API exist? | Yes — one. GET /rest/pc-direct/moment/detail?momentId={id} returns a full moment object anonymously. Moment comments work anonymously too (comment/listByFloor with sourceType=4). |
| Is there a public "square"/recommend list API? | Not found. ~560 guessed paths across /rest/pc-direct/*, /rest/mobile-direct/*, /rest/app/*, /rest/zt/* and five hosts. The only moment lists are (a) login-scoped following feeds (feed/followFeedV2, feed/followDougaFeed, feed/webPush) and (b) the mobile-web topic page HTML, which server-renders a fixed 15 items with no pagination. |
| Can a plugin post a 动态? | No. Publish is app-only: POST https://api-ipv6.acfunchina.com/rest/app/moment/add → 401 {"result":-401,"error_msg":"token value error"}. Requires the signed acfun.midground.api_st token (see §5), i.e. an AcFun app session, not a web cookie. |
| What is the real 2026 surface? | 社区圈 / 话题 (communityCircle) on mobile web: https://m.acfun.cn/communityCircle/{tagId} is alive with freshly-dated content (observed 2026-10-13), a topic index (e.g. 教育 = `38关注 |
| Does the community build its own web 广场? | Yes — "【油猴脚本】网页端动态广场!" (2026-09-01) plus Greasy Fork scripts 579890 / 584032. That is direct evidence there is an API but no official web UI. (Greasy Fork was unreachable from this network, so their exact endpoint list could not be read; the endpoints below were derived from AcFun's own bundles and verified live.) |
Bottom line: a plugin can be a moment reader + comment reader today, with no cookie at all, plus a cookie-gated "following feed" reader. Every write except maybe like/banana/comment is effectively impossible.
1. Hosts and URL prefixes (the one thing that trips everyone up)
| Purpose | Host / prefix |
|---|---|
| PC web JSON BFF | https://www.acfun.cn/rest/pc-direct/... |
| PC web SSR-internal BFF | https://www.acfun.cn/rest/pc-node/... — [VERIFIED] every call → 502; not publicly routed |
| Mobile web JSON BFF | https://m.acfun.cn/rest/mobile-direct/... — [VERIFIED] 502 on www.acfun.cn, JSON on m.acfun.cn (host-bound) |
| Mobile web pages | https://m.acfun.cn/communityCircle/... |
| App API | https://api-ipv6.acfunchina.com/rest/app/... |
| App API (IPv6-less siblings) | https://api.acfun.cn → 502 [VERIFIED]; app.acfun.cn / ias.acfun.cn / zt.acfun.cn → DNS/connect failure |
| Like / interact service | https://kuaishouzt.com/rest/zt/..., https://api.kuaishouzt.com/rest/zt/... |
| Auth token service | https://id.app.acfun.cn/rest/web/token/get |
| Captcha / QR | https://www.acfun.cn/rest/c/infra/captcha/{get,verify}, /rest/c/infra/ks/qr/* (from bundles) |
[VERIFIED] The PC bundle states the prefix rule verbatim — this is why pc-node exists:
function p(){return "server"===e.env.VUE_ENV||a.a.prototype.$isServer ? "/rest/pc-node" : "/rest/pc-direct"}
A Node plugin is not the AcFun SSR server, so it must use /rest/pc-direct (or /rest/mobile-direct
on m.acfun.cn, or /rest/app on the app host). /rest/pc-node/* is unusable from outside.
Recommended headers (what the mobile pages answer best to — see §3.4 for the Accept quirk):
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/ (or https://m.acfun.cn/ for mobile hosts)
Accept: */*
Content-Type: application/x-www-form-urlencoded; charset=UTF-8 # for POSTs
No sign/__/md5 parameter is needed on any read verified here. Writes need the midground token (§5).
2. Feature map — what actually exists in 2026
2.1 Page routes (all fetched live)
| URL | Result | Verdict |
|---|---|---|
https://www.acfun.cn/moment/ |
302 → /404 (52-byte body) |
dead |
https://www.acfun.cn/moment |
302 → /404 |
dead |
https://www.acfun.cn/moment/am:1 |
302 → /404 |
dead (the /moment/am:momentId route string still sits in an old bundle) |
https://www.acfun.cn/moment/square |
302 → /404 |
dead |
https://www.acfun.cn/square, /community, /discovery, /dynamic, /feed, /am, /index/moment, /square/ |
502 + 18 366-byte "出错啦!" page |
nonexistent (502 is AcFun's unknown-route response on www) |
https://www.acfun.cn/member/feeds, /member/ |
302 //www.acfun.cn?returnUrl=https%3A%2F%2Fwww.acfun.cn%2Fmember |
exists, login-gated (个人动态中心) |
https://www.acfun.cn/u/1 |
200, 73 523 B |
user space — tabs are 投稿/收藏/关注/粉丝; no 动态 tab, zero occurrences of moment/动态 |
https://www.acfun.cn/ |
200, 155 119 B, 8 bigPipe pagelets (pagelet_player_info, pagelet_navigation, pagelet_live, pagelet_spring_festival, pagelet_monkey_recommend, pagelet_top_area, pagelet_banner, pagelet_list_banana) |
nav = 首页 / 直播 / 番剧 / video channels / 排行榜 — no 广场, no 动态 |
https://m.acfun.cn/ |
302 → http://www.acfun.cn |
mobile home redirects to www |
https://m.acfun.cn/communityCircle |
200 (31–40 KB shell) |
社区圈 index, client-rendered (no tags in HTML) |
https://m.acfun.cn/communityCircle/square |
200 shell |
话题广场, client-rendered, no tags in HTML |
https://m.acfun.cn/communityCircle/tops, /tag |
200 shell |
same |
https://m.acfun.cn/communityCircle/{tagId} |
200, 144 KB, 15 moment items SSR'd |
the working 广场 read surface (§3.4) |
https://m.acfun.cn/communityCircle/moment/{momentId} |
200, <title>Acfun 动态详情</title>, contains .moment-text, .moment-img-box, #comment |
canonical human-readable moment page |
https://m.acfun.cn/moment/200 |
404 page (HTTP 200) | wrong path |
2.2 JS bundle evidence (www, 40 bundles / 2.7 MB scanned)
Only one www bundle mentions moment: https://ali-imgs.acfun.cn/kos/nlav10360/static/js/app.11fc9e51.js
(the personal-center/header SPA). It contains the complete moment store, the feed list actions, and
the interaction actions — but no publish action and no moment page component. Extracted facts:
// moment store normalisation — THE key mapping
n.resourceType = 10; // moments are resourceType 10
n.resourceId = n.momentId;
// moment detail
GetMomentDetail: function(e,t){
return a.a.get(Object(a.b)(e.rootState, Object(a.e)() + "/moment/detail?momentId=" + t), …)
.then(function(t){ … if(0===n.result) e.commit(f.CHANGE_MOMENT_DETAIL,{moment:n.moment, repostSource:n.repostSource}) })
}
// following feed list
GetFeedsFlow: function(e,t){ var n=t.pcursor||0, o=t.pageSize||20, c=t.resourceTypes||0;
… a.a.get(prefix + "/feed/followFeedV2?pcursor="+n+"&count="+o+"&resourceTypes="+c) …
// resourceTypes === 0 -> ChangeFeedsFlow (all), else -> ChangeFeedsArticleFlow (articles only)
}
// like / unlike
var h = "like"===u ? "/rest/zt/interact/add" : "/rest/zt/interact/delete";
a.a.post("//"+ztDomain+h, qs.stringify({
objectId:n, objectType:o, userId:d,
"acfun.midground.api_st":s, kpn:"ACFUN_APP", kpf:"PC_WEB", subBiz:"mainApp", interactType:1
}), {headers:{"Content-Type":"application/x-www-form-urlencoded"}})
// success is checked as: 1 === t.data.result
// banana on a moment
FeedThrowBanana: function(e,t){ var n=t.resourceId,r=t.resourceType,o=t.count;
a.a.post(prefix + "/banana/throwBanana", qs.stringify({resourceId:n, resourceType:r, count:o}), …)
// success is checked as: 0 === n.data.result
}
// other endpoints present in the same bundle
"/feed/followFeedV2?pcursor=0&count=5&resourceTypes=0"
"/feed/followDougaFeed?pcursor="
"/feed/webPush?count=50&pcursor=0"
"/feed/favorite/bangumi?count=10"
Routes in that bundle: /member/feeds, /member/feeds/fans, /member/feeds/following,
/moment/am:momentId (legacy string), /moments/... none. No publish/edit/delete anywhere.
3. Read contracts (each one verified live)
3.1 Moment detail — the one reliable read [VERIFIED]
GET https://www.acfun.cn/rest/pc-direct/moment/detail?momentId={momentId}
- Method/params:
GET, single query parammomentId(numeric). Anonymous works. No cookie needed. - Also served identically by
https://api-ipv6.acfunchina.com/rest/app/moment/detail?momentId=…andhttps://m.acfun.cn/rest/mobile-direct/moment/detail?momentId=…[VERIFIED] (all three →result:0).
Live, trimmed (momentId=5093831, current-era content):
{"result":0,"host-name":"hb2-acfun-kce-node24.aliyun","requestId":"eac1ac4e8c4e4fbd8d82a662927e7cc2",
"moment":{
"momentId":"5093831","momentType":2,"createTime":"2026-09-23",
"text":"#教育##父亲##贷款#","replaceUbbText":"#教育##父亲##贷款#",
"user":{"id":"23240229","name":"红格男(RSM)",
"headUrl":"https://tx-free-imgs.acfun.cn/newUpload/23240229_cfb0100bc9a64c0aa96d8…"},
"imgs":[{"w":1080,"h":2554,"type":4,"originUrl":"https://tx-free-imgs.acfun.cn/newUpload/23240229_44bf454add3…"}],
"likeCount":13,"commentCount":6,"bananaCount":5,"shareCount":0,
"isLike":false,"isThrowBanana":false,"visibleForFans":false,"originResourceType":0,
"tagList":[{"tagId":40887,"tagName":"贷款"},{"tagId":488,"tagName":"教育"},{"tagId":1968,"tagName":"父亲"}],
"shareUrl":"https://m.acfun.cn/communityCircle/moment/5093831"}}
Top-level keys: result, host-name, requestId, moment — plus repostSource only when the moment is a
repost (then counts/author live under repostSource, per the store mutation).
Moment id space — two eras [VERIFIED]
moment/detail distinguishes "not found" reasons, which lets you map the id space:
momentId |
result |
error_msg |
|---|---|---|
1, 100 |
140005 |
动态不存在或者已被删除 (in-range, deleted) |
10000, 1000000, 10000000, 100000000, 48910450, 48896959 |
140002 |
内容不存在 (out of range) |
200, 202, 300, 5093831, 5093817 |
0 |
real moments |
Binary search pinned the boundary: ids ≤ 7525 are "in range" (140005), ids ≥ 7526 are
140002. That range is the legacy 2019 动态 system: every live id found in 1..1200 is dated
2019-05-23 … 2019-06-14, user set is the 2019 launch crowd (七里香004, soso15, ACer第一次耳机 …),
momentType 1/2, text like "樱花" / "搞笑图片". The current system uses 7-digit ids (5093831
→ dated 2026-09-23, tag list with 话题 ids) and is reachable only by knowing the id (from a topic
page) — there is no enumerable list here.
So: getMoment(id) works for both eras, but ids cannot be discovered by the API alone.
3.2 Moment comments — [VERIFIED]
GET https://www.acfun.cn/rest/pc-direct/comment/listByFloor
?sourceId={momentId}&sourceType=4&page=1&supportZtEmot=true
sourceType = 4 identifies a moment. Proof from a live matrix on sourceId=202 (a moment whose
commentCount is 2):
sourceType |
HTTP | payload |
|---|---|---|
| 4 | 200 | {"result":0,"curPage":1,"isUp":false,"sourceType":4,"totalPage":1,"totalCount":2, "commentsMap":{…},"commentIds":[…],"pageSize":50,…} — 2 real comments |
| 3 | 200 | {"result":0,"sourceType":3,"totalPage":0,"totalCount":0,"commentsMap":{}} (video type — empty) |
| 2 | 200 | {"result":0,"sourceType":2,"totalPage":0,"totalCount":0} (valid but unrelated) |
| 10 | 200 | {"result":21,"error_msg":"参数格式错误,请仔细阅读API文档。"} (invalid) |
Comment object keys [VERIFIED]:
imageInfo, userHeadImgInfo, avatarFrameImgInfo, imageUrl, isDelete, quoteId, nameRed, nameColor, headUrl, verifiedTypes, deviceModel, isSignedUpCollege, avatarFrame, avatarImage, sourceId, cid, isUpDelete, floor, postDate, sourceType, userName, userId, isUp, verified, content, timestamp
(e.g. {"userName":"soso15","userId":"13360169","content":"666666"}).
Note the numbering split, this is the classic trap: comments use sourceType 4, while
like/banana use resourceType 10 (§4). They are different enums for the same object.
/rest/mobile-direct/comment/list also exists on m.acfun.cn (result:21 without params → route alive)
but its param names (resourceId/resourceType) were rejected in probing — [UNVERIFIED]; use the
pc-direct listByFloor form above, which is fully verified.
3.3 Following feeds (the "动态流") — route [VERIFIED], data login-scoped
GET https://www.acfun.cn/rest/pc-direct/feed/followFeedV2?pcursor=0&count=20&resourceTypes=0
GET https://www.acfun.cn/rest/pc-direct/feed/followDougaFeed?pcursor=0&count=20
GET https://www.acfun.cn/rest/pc-direct/feed/webPush?count=50&pcursor=0
GET https://www.acfun.cn/rest/pc-direct/feed/favorite/bangumi?count=10
Anonymous live results — note these are 200, not auth errors, because the routes are
guest-tolerant and simply have nothing to show a guest with followingCount: 0:
// followFeedV2
{"result":0,"feedList":[],"pullCount":0,"userInfo":{"followingCount":0},
"requestId":"ea7f66cd511f4926bf9e25c55435415a","followTags":[],"pcursor":"no_more","ups":[],"upsPos":0}
// followDougaFeed
{"result":0,"feedList":[],"userInfo":{"followingCount":0},"followUpers":[],"pcursor":"no_more","pageSize":10,"favBangumis":[]}
// webPush
{"result":0,"feedList":[],"followUpers":[],"pcursor":"no_more"}
// favorite/bangumi -> AUTH ERROR
{"result":-401,"error_msg":"user not login"}
So followFeedV2 is the right endpoint for "关注动态" and it does carry moments in feedList
(the store action names them feeds, and the item schema is the same resourceType 10 object), but
its content is entirely a function of the logged-in user's follows. It is useless anonymously and
it is not a 广场.
Params: pcursor (opaque cursor; "no_more" = end), count (default 20 in JS), resourceTypes
(0 = all → ChangeFeedsFlow; non-zero → article flow, per the bundle's ternary).
The app API mirrors these [VERIFIED]: /rest/app/feed/followFeed → 200
{"result":0,"feedList":[],"pageNo":1,"totalPage":0,"pageSize":10}, /rest/app/feed/followFeedV2
→ 200 same shape, /rest/app/feed/followDougaFeed → result:11.
3.4 Topic page (社区圈) — the only public "广场" read, HTML only [VERIFIED]
GET https://m.acfun.cn/communityCircle/{tagId}
Live (tagId=488, 教育): 200, 144 110 bytes, title Acfun【教育】话题,快来参与,
topic header 教育 + 38关注 | 10458内容, and 15 server-rendered moment items, newest dated
2026-10-13 — i.e. this subsystem is genuinely in use today.
Item markup (stable, scrape-able):
<li class="resources-item ks-1px-b type-1"
moment-id="" resource-type="1" resource-id="48910450" data-repost="false">
<div class="resources-user">…<p class="user-name">c137牛奶娘</p><p class="release-time">2026-10-13</p>…</div>
<div class="resources-article"><p class="article-title">…</p><p class="article-body">…</p></div>
<div class="resources-tag pics-0"><a class="tag-item" href="/communityCircle/488">#教育</a>…</div>
<div class="resources-base">…<span class="base-text">228</span>…</div>
</li>
Observed resource-type values and their moment-id:
resource-type |
moment-id |
meaning |
|---|---|---|
1 |
"" (empty) |
图文动态/article-style post; only resource-id (e.g. 48910450) — not a moment id |
2 |
"" |
video-style post; only resource-id |
3 |
5093831, 5093817 |
real moment — this is how you harvest current moment ids |
⚠️ The Accept header flips the renderer — this cost me a lot of time, so it is worth stating
precisely. Same URL, two outcomes:
| Request headers | Length | Items |
|---|---|---|
default Accept (or none, or Referer www vs m — irrelevant) |
144 110 | 15 ✅ |
Accept: text/html,application/xhtml+xml… |
31 628 | 0 (JS shell) |
User-Agent: …Googlebot… |
41 722 | 15 (different shell) |
So a Node fetch must send default/*/* Accept to get the SSR'd list, and must not pretend to be a
browser navigation.
⚠️ No pagination. ?page=2, ?page=3, ?sort=reply, ?sort=post all return the identical 15
items (byte-identical length, same first resource-id=48910450). The 热门排序 / 回复时间 / 发帖时间
filter and the infinite scroll are client-side-only and their data source is not reachable:
/communityCircle/square, /tops, /tag, /communityCircle are all empty shells with zero tag
entries or /rest/ URLs in the HTML.
3.5 feed/hot — [VERIFIED] but it is videos, not moments
GET https://www.acfun.cn/rest/pc-direct/feed/hot (also /rest/mobile-direct/feed/hot, /rest/app/feed/hot)
200 {"result":0,"feeds":[…], "host-name":…, "requestId":…} — 9–14 items, every one type: 2
(douga video), with dougaId/videoId/caption/commentCount/bananaCount etc. It is the hot
video module. count, resourceTypes, type, pcursor are all ignored (same 9–14 items each
time, no pcursor in the response). Do not mistake it for a 广场 moment feed — but it is a fine
anonymous "hot feed" and the item schema is the same feed-item envelope that moments use.
4. Interaction endpoints (contracts from the site's own JS; writes only probed to their auth error)
I have no account, so each write below was attempted unauthenticated only to prove the route and observe the auth tier. No like/banana/comment/follow/post was ever actually performed.
4.1 Like / unlike a moment [UNVERIFIED — route + auth tier verified]
POST https://kuaishouzt.com/rest/zt/interact/add # like
POST https://kuaishouzt.com/rest/zt/interact/delete # unlike
Content-Type: application/x-www-form-urlencoded
objectId={momentId}&objectType=10&userId={authorUserId}
&acfun.midground.api_st={token}&kpn=ACFUN_APP&kpf=PC_WEB&subBiz=mainApp&interactType=1
objectType= the moment'sresourceType= 10 (from the store'sn.resourceType=10).- Success is
{"result":1}(note: 1, not 0 — this endpoint's envelope differs frompc-direct). - Auth tier: signed midground token. [VERIFIED] unauthenticated call →
401 {"result":-401,"error_msg":"token value error","error_id":""}. (Note/rest/zt/interact/deleteis the unlike route — it is not "delete a moment".)
4.2 投蕉 (banana) on a moment [UNVERIFIED — route + auth tier verified]
POST https://www.acfun.cn/rest/pc-direct/banana/throwBanana
Content-Type: application/x-www-form-urlencoded
resourceId={momentId}&resourceType=10&count={1..5}
[VERIFIED] unauthenticated → 401 {"result":-401,"error_msg":"user not login"} (same for
resourceType=1), i.e. route exists, plain session cookie tier. Success would be result:0.
4.3 Comment / reply on a moment [UNVERIFIED]
POST https://www.acfun.cn/rest/pc-direct/comment/add
objectId={momentId}&content={ubb text}&replyToCommentId={0|commentId}
Inferred from the verified comment list pairing (sourceId+sourceType=4) and the existing
video-comment contract in docs/acfun-api.md (§3), which documents comment/add taking
objectId. Whether a moment post also needs sourceType=4 is [UNVERIFIED] — the mobile comment
widget constructs itself with initCommentWidget({resourceId: momentId, resourceType: 4, …})
(from pages/momentDetail/index.*.js), so send sourceType=4 as well to be safe.
Auth tier: plain session cookie (user not login). Mobile equivalent that exists but whose params I
could not pin down: POST https://m.acfun.cn/rest/mobile-direct/comment/add → result:11 [VERIFIED route].
4.4 Follow the moment author [UNVERIFIED]
POST https://www.acfun.cn/rest/pc-direct/relation/follow # {targetUid} / {herdId}
(/rest/pc-direct/relation/follow and /rest/pc-direct/relation/getGroups are both present in the www
bundles.) Cookie tier.
4.5 Vote (投票动态) [UNVERIFIED — route + auth tier verified]
POST https://api-ipv6.acfunchina.com/rest/app/moment/vote
[VERIFIED] 401 {"result":-401,"error_msg":"token value error"} → app token tier. The moment store
has an UpdateFeedVote mutation and the mobile page reads vote__radio DOM, so 投票 moments exist as
momentType variants; the web cannot drive them.
4.6 Repost / 转发 [UNVERIFIED — no route found]
The store has updateFeedRepostFollow / updateFeedRepostFavorite mutations and renders
repostSource, so reposts exist and can be read (a repost's payload has a repostSource sibling).
But no repost/forward endpoint was found: /rest/pc-direct/moment/{repost,forward,share} → 404,
and /rest/app/moment/repost → 404. Most likely reposting = moment/add with an origin field
(app-only). Treat as impossible for a plugin.
4.7 Publish / delete a 动态 [UNVERIFIED — app-only, token-gated]
POST https://api-ipv6.acfunchina.com/rest/app/moment/add # publish
POST https://api-ipv6.acfunchina.com/rest/app/moment/delete # delete
[VERIFIED] POST moment/add (any body: content=test, momentId=1, or empty) →
401 {"result":-401,"error_msg":"token value error"} — the handler ran, so the route is real; the
GET form returns result:11 (网络错误,请稍后再试). moment/delete returns result:11 on GET
(route alive). Every /rest/pc-direct/moment/{add,create,publish,post,delete,remove,repost,forward, share,like,unlike,vote,update,edit} returns HTTP 404 on www — there is no web publish path at all,
which is exactly why the community wrote a userscript instead of using the site.
Image upload for a moment would go through /rest/pc-direct/image/upload/getToken +
getUrlAfterUpload (present in bundles) — [UNVERIFIED], and pointless without a publish route.
5. Auth tiers, signing, captcha
| Tier | What it needs | Evidence seen |
|---|---|---|
| Anonymous | nothing | moment/detail, comment/listByFloor, feed/followFeedV2, feed/hot, m.acfun.cn/communityCircle/* all 200 |
| Session cookie | auth_key / _did / csrfToken cookies from a real login |
401 {"result":-401,"error_msg":"user not login"} for feed/favorite/bangumi, banana/throwBanana |
| Signed midground token | acfun.midground.api_st, obtained from https://id.app.acfun.cn/rest/web/token/get with a logged-in app/web session; sent as a form field |
401 {"result":-401,"error_msg":"token value error"} for kuaishouzt/rest/zt/interact/add, /rest/app/moment/add, /rest/app/moment/delete, /rest/app/moment/vote |
- No HMAC/md5 request signing was observed on any read, nor on
interact/add(it sends plain form fields plus the token). The "signature" is effectively just the midground token. - Anonymous visit cookies are only
csrfTokenand_did— no session. - Captcha / identity:
/rest/c/infra/captcha/{get,verify}and/rest/c/infra/ks/qr/{start,scanResult, acceptResult}exist for login/QR. Both the desktop and mobile pages load@ks/identity-verification(static/@ks/identity-verification.81c79f9a249d01283c29.js), so publishing may also require 实名/手机号 identity verification on top of the token — [UNVERIFIED], but the SDK's presence on the very pages that would post is a strong hint. - Observed
resultcodes relevant here:0ok ·1ok forinteract/add·11handler ran, context/ params rejected ·21param format error ·-401auth (cookie or token) ·140002内容不存在 ·140005动态不存在或者已被删除 ·129000kuaishouzt "Server Error." (missing token entirely).
6. Field maps
6.1 Moment object (moment/detail → moment, and feedList[] items when resourceType/type is a moment)
| Field | Type | Meaning / note |
|---|---|---|
momentId |
string | the id — string, not number. Used by detail + comments (sourceId) |
momentType |
number | 1 text-only, 2 image/text, 3 vote/其他 (observed 1/2 live; 3 inferred from /communityCircle/{tagId} items whose moments-type-3 markup carries a vote) |
text |
string | raw content, includes #话题# markers and newlines |
replaceUbbText |
string | same text after UBB processing |
createTime |
YYYY-MM-DD string |
day precision only — no time-of-day |
user |
object | see 6.2 |
imgs[] |
array | {width,height,size,type,url,expandedUrl,originUrl}; type 1/3/4 observed (gif vs jpg vs png); originUrl is the full-size original, url is a 224×224 thumb (imageView2/5/w/224/h/224) |
imgInfos[] |
array | the newer <ImageInfo> envelope (thumbnailImage/smallSharedImage/expandedImage/originImage, each {cdnUrls:[{url,freeTrafficCdn}]}) — prefer imgs[].originUrl for a plugin |
likeCount / commentCount / bananaCount / shareCount |
number | counters; bananaCount = 投蕉 |
isLike / isThrowBanana |
boolean | always false anonymously; true/false when a cookie is present |
visibleForFans |
boolean | 粉丝可见 flag |
originResourceType |
number | 0 for a native moment; non-zero when it wraps another resource |
tagList[] |
array | {tagId, tagName} — the 话题 links; tagId feeds /communityCircle/{tagId} |
shareUrl |
string | https://m.acfun.cn/communityCircle/moment/{momentId} — use this as the canonical URL |
groupId |
string | per-request rendering group, not stable — ignore |
voteInfo |
object | present on 投票 moments only (referenced by the UpdateFeedVote mutation) |
repostSource |
object (sibling of moment) |
on reposts: a second moment-shaped object with its own user/counts; the store reads moment.repostSource.* |
6.2 Author object (moment.user)
id (numeric string), name, headUrl, headCdnUrls[], userHeadImgInfo, avatarFrame/avatarFramePcImg/
avatarFrameMobileImg, signature, gender, fanCount/fanCountValue, followingCount/
followingCountValue, contributeCount/contributeCountValue, isFollowing, followingStatus,
isFollowed, href, action, nameColor, socialMedal, sexTrend.
The store normalises this to {userHead, userId, userName, liveId} — if you cache, keep id+name.
6.3 Feed-item envelope (feedList[], feed/hot[])
groupId, contentId, videoId, dougaId, sourceId, type, caption, title, description, coverUrl, coverUrls[], coverCdnUrls[], displayPlayCount, viewCount, displayDanmakuCount, danmakuCount, likeCount, likeCountShow, commentCount, commentCountRealValue, bananaCount, bananaCountShow, shareCount, stowCount, isLike, isThrowBanana, isFavorite, isFollowing, createTime, createTimeMillis, contributeTime, playDuration, durationMillis, videoList[] (part list with id/title/danmakuCount), tagList[], user, channel, superUbb, status, expTag, videoSizeType, shareUrl, picShareUrl
For a moment, type/resourceType is 10 and the moment body sits alongside as moment /
repostSource (per the store). feed/hot items are type:2 videos.
7. Dead ends, 404s, and app-only
[VERIFIED] 404 on www.acfun.cn (route simply not served publicly):
/rest/pc-direct/moment/{add,create,publish,post,delete,remove,repost,forward,share,like,unlike,vote, update,edit} · /rest/pc-direct/feed/{publish,addMoment,square,recommend,circle,topic,list,getFeed, feedList,queryFeed,recommendList,recommendMoment,discover,index,home,timeline,circleFeed,topicFeed, hotMoment,userFeed,userMoment,user,contribute} · /rest/pc-direct/moment/{list,getList,userList,hot, recommend,square} · /rest/pc-direct/{topic/*,communityCircle/*,tag/resources,resources/list,resource/list}.
[VERIFIED] 404 on api-ipv6.acfunchina.com: /rest/app/moment/{square,list,recommend,hot,feed, timeline,publish,create,repost} · /rest/app/{square/*,circle/list,community/*,feed/{square,list, recommend}}.
[VERIFIED] 404 on m.acfun.cn: /rest/mobile-direct/{topic/*,communityCircle/*,tag/*,moment/{list, square,recommend,hot,add,publish},feed/{square,recommend,list},square/list,resources/list}.
502 (wrong host): /rest/pc-node/* on www; /rest/mobile-direct/* on www.
HTML 200 (not JSON) on kuaishouzt.com: /rest/zt/{feed,moment,square}/* — only
/rest/zt/interact/{add,delete} is a real JSON endpoint there.
App-only / effectively impossible for a web plugin: publishing a 动态, deleting one, reposting,
voting, and the app's 广场/推荐 timeline. All of these sit behind the midground token on
api-ipv6.acfunchina.com and have no web counterpart. There is no public square/recommend moment
list API on any host — after ~560 path probes this is a solid negative, not a gap in the search.
8. Feasibility and the recommended plugin surface
8.1 Verdict per capability
| Capability | Feasible? | Basis |
|---|---|---|
| Read one moment by id (text, images, tags, counts, author) | ✅ reliable, anonymous | moment/detail result:0 on legacy + 2026 ids |
| Read a moment's comments | ✅ reliable, anonymous | comment/listByFloor?sourceType=4 totalCount:2 with real comments |
| Read a topic/话题's moments | ⚠️ possible but HTML-only, first 15, no paging | m.acfun.cn/communityCircle/{tagId} 144 KB SSR |
| Read the 关注动态 stream | ⚠️ cookie only | feed/followFeedV2 200 but empty for guests |
| Discover moment ids / browse a 广场 | ❌ no API | ~560 probes; topic SSR is the only discovery vector |
| Like/unlike | 🔶 plausible, unverifiable | route + token tier verified via 401 |
| 投蕉 | 🔶 plausible, unverifiable | route + cookie tier verified via 401 |
| Comment/reply | 🔶 plausible, unverifiable | list side verified; comment/add from existing doc |
| Follow author | 🔶 plausible, unverifiable | /relation/follow in bundles |
| Repost | ❌ | no route found |
| Publish / delete 动态 | ❌ impossible for a web plugin | app-only, midground token |
| Vote | ❌ | app-only, midground token |
8.2 Smallest useful implementation
Two read tools, both anonymous, plus one optional cookie-gated tool. Do not ship a "square feed" tool.
acfun_moment(momentId: string)
GET https://www.acfun.cn/rest/pc-direct/moment/detail?momentId={id}
-> { momentId, url, author{id,name,avatar}, text, createTime, images[], tags[{tagId,name}],
counts{like,comment,banana,share}, isRepost, repostSource? , raw }
Accepts a bare id, "am:5093831", or a https://m.acfun.cn/communityCircle/moment/5093831 URL.
Errors: 140002 -> NOT_FOUND (out of range); 140005 -> DELETED.
acfun_moment_comments(momentId, page=1)
GET https://www.acfun.cn/rest/pc-direct/comment/listByFloor
?sourceId={id}&sourceType=4&page={page}&supportZtEmot=true
-> { totalCount, totalPage, page, comments[{cid, floor, userName, userId, content, postDate, isUp}] }
acfun_topic_moments(tagId, limit<=15) # optional, mark as best-effort
GET https://m.acfun.cn/communityCircle/{tagId} with DEFAULT Accept (no text/html!)
Parse li.resources-item[resource-type=3] -> moment-id ; other types -> resource-id only.
-> { tag{id,name,followers,contents}, items[{momentId?, resourceType, resourceId, user, time, text, stats}] }
First page only; params page/sort do nothing.
acfun_following_feed(cursor) # only when a session cookie is configured
GET .../feed/followFeedV2?pcursor={cursor||0}&count=20&resourceTypes=0
-> { items[], nextCursor } ; nextCursor="no_more" terminates; empty list for anonymous.
Mapping to the existing plugin shape in this repo: these are pure fetch GETs, so they slot in beside
the existing acfun_video / acfun_comments tools with no auth plumbing (lib/provider.js style).
acfun_moment is the one to ship first — it is the only capability that is both anonymous and
complete. acfun_topic_moments is the only way to discover a moment id, so it earns its place
despite being HTML scraping.
8.3 Risk notes
- HTML scraping is load-bearing for discovery.
communityCircle/{tagId}has no API, no pagination, and its SSR only appears whenAcceptis nottext/html. A markup change breaks discovery, not reads. Isolate the parser and fail soft. - Two id spaces. Legacy
1…7525(2019, mostly deleted) and current 7-digit ids. Never assume contiguity, never enumerate to "browse" — validate the id and surface140002/140005distinctly (they mean different things). sourceType 4vsresourceType 10. Comments use 4, like/banana use 10. Mixing them silently yields empty comment lists (sourceType 3→totalCount:0) rather than an error.- Host binding.
/rest/mobile-direct/*only answers onm.acfun.cn;/rest/pc-node/*never answers publicly. Hard-coding the wrong pair gives502s that look like outages. - Guest-tolerant ≠ available.
feed/followFeedV2returns200with an empty list for a guest; a plugin must not report "no dynamics" when it means "not logged in". - Writes cannot be validated at all here.
interact/addsuccess isresult:1while every other route usesresult:0— easy to get backwards. If write tools are ever enabled, require a cookie and treat any non-0/1as failure, and never retry blindly. - The feature is app-first and actively mutating. www dropped
/moment/entirely; the live subsystem moved tom.acfun.cn/communityCircleand the app. Treat every endpoint here as verified on 2026-10-27 and re-probe before relying on it.
9. How each claim was produced (short reproduction log)
| # | Probe | What it established |
|---|---|---|
| p1 | fetch 7 candidate URLs, redirect:'manual' |
/moment/→302 /404; /square,/community,/discovery→502 error page |
| p2 | home page bigPipe frames + keyword scan | 8 pagelets, nav routes, 0 occurrences of moment/square/dynamic |
| p3 | pagelet_navigation HTML + /member/* |
nav = 首页/直播/番剧/channels/排行榜; /member/feeds→login redirect |
| p4 | /u/1, /u/2 space pages |
no 动态 tab, no moment strings |
| p5 | download 70 www bundles (2.7 MB), regex /rest/ |
50 rest paths; only app.11fc9e51.js mentions moment |
| p6–p9 | dissect app.11fc9e51.js |
prefix fn, moment/detail, followFeedV2, banana/throwBanana, interact/add, resourceType=10 |
| p10 | live probe of 7 read + 2 write endpoints | detail 200, feeds 200-empty, favorite/bangumi 401, throwBanana 401, interact/add 401 token |
| p11 | existence oracle over 533 /rest/pc-direct/{moment,feed,...} paths (502 vs JSON) |
only moment/detail + feed/hot are JSON |
| p12 | feed/hot shape + param matrix |
9–14 video items, type:2 only, params ignored |
| p13–p14 | moment-id range scan + binary search | boundary 7525/7526; momentId=200/300 → result:0 |
| p15 | fetch + scan ids 1..1200 | all live moments dated 2019-05/06; full field list |
| p16 | m.acfun.cn/communityCircle* + app host |
/communityCircle/square 200; /moment/detail on app host 200 |
| p17–p20 | mobile pagelets + 37 mobile bundles | pages client-rendered; comment widget uses resourceType 4, mobile-direct paths |
| p21 | moment variety + comment sourceType matrix |
sourceType=4 → 2 real comments; 3/2 empty; 10 → result 21 |
| p22–p23 | publish/repost oracle on www + m + app | www 404 all; POST /rest/app/moment/add → 401 token; vote → 401 |
| p24–p26 | topic pages | /communityCircle/488 SSR 15 items dated 2026-10-13; tag 教育 38关注/10458内容 |
| p27–p29 | paging/sort/headers | ?page/?sort no-ops; Accept: text/html kills the SSR list; */* restores it |
| p30 | moment page SSR | m.acfun.cn/communityCircle/moment/{id} → Acfun 动态详情 with .moment-text |
| p31–p32 | kuaishouzt + final list hunt | zt only has interact/*; no public/user moment list anywhere |
| p33 | clean evidence capture | the JSON/comment/tag snippets pasted above |