Files
dsh-acfun/docs/acfun-moment.md
T
tamakyi 97825f020d feat: QR login, AcFun settings panel, and 动态 (moment) tools
- 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
2026-10-27 11:50:44 +08:00

37 KiB
Raw Blame History

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 param momentId (numeric). Anonymous works. No cookie needed.
  • Also served identically by https://api-ipv6.acfunchina.com/rest/app/moment/detail?momentId=… and https://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's resourceType = 10 (from the store's n.resourceType=10).
  • Success is {"result":1} (note: 1, not 0 — this endpoint's envelope differs from pc-direct).
  • Auth tier: signed midground token. [VERIFIED] unauthenticated call → 401 {"result":-401,"error_msg":"token value error","error_id":""}. (Note /rest/zt/interact/delete is 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 csrfToken and _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 result codes relevant here: 0 ok · 1 ok for interact/add · 11 handler ran, context/ params rejected · 21 param format error · -401 auth (cookie or token) · 140002 内容不存在 · 140005 动态不存在或者已被删除 · 129000 kuaishouzt "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.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

  1. HTML scraping is load-bearing for discovery. communityCircle/{tagId} has no API, no pagination, and its SSR only appears when Accept is not text/html. A markup change breaks discovery, not reads. Isolate the parser and fail soft.
  2. 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 surface 140002/140005 distinctly (they mean different things).
  3. sourceType 4 vs resourceType 10. Comments use 4, like/banana use 10. Mixing them silently yields empty comment lists (sourceType 3 → totalCount:0) rather than an error.
  4. Host binding. /rest/mobile-direct/* only answers on m.acfun.cn; /rest/pc-node/* never answers publicly. Hard-coding the wrong pair gives 502s that look like outages.
  5. Guest-tolerant ≠ available. feed/followFeedV2 returns 200 with an empty list for a guest; a plugin must not report "no dynamics" when it means "not logged in".
  6. Writes cannot be validated at all here. interact/add success is result:1 while every other route uses result:0 — easy to get backwards. If write tools are ever enabled, require a cookie and treat any non-0/1 as failure, and never retry blindly.
  7. The feature is app-first and actively mutating. www dropped /moment/ entirely; the live subsystem moved to m.acfun.cn/communityCircle and 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