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
This commit is contained in:
tamakyi
2026-10-27 11:50:44 +08:00
parent f9bedde19e
commit 97825f020d
19 changed files with 3723 additions and 189 deletions
+80 -25
View File
@@ -1,9 +1,10 @@
# dsh-plugin-acfun
DeepSeek Harness plugin for **AcFun (A站, acfun.cn)**: search videos, read video
metadata, comment threads, danmaku (弹幕), the day/week ranking and playable
stream URLs, and — only when you opt in — interact with your own account (like,
tip bananas, favorite, follow, post comments).
metadata, comment threads, danmaku (弹幕), the day/week ranking, playable stream
URLs and 动态 (moments); interact with your own account (like, tip bananas,
favorite, follow, comment — including moments); and sign in by **scanning a QR
code in the Harness Web UI** (Settings → AcFun).
This is the AcFun counterpart of the community Bilibili tool plugins. Reads are
anonymous and work out of the box; every mutating call is gated behind
@@ -20,11 +21,15 @@ never silently gives the model write access to an account.
| `acfun_danmaku` | read | One batch of a video's 弹幕, ordered by in-video position, with cursor paging. |
| `acfun_rank` | read | Day or week ranking, all channels or one `channelId`. |
| `acfun_playurl` | read | m3u8 quality ladder (1080P+ / 1080P / 720P / 540P / 360P). |
| `acfun_interact` | write | `like`, `unlike`, `banana`, `favorite`, `unfavorite`, `follow`, `unfollow`. |
| `acfun_post_comment` | write | Post a top-level comment or a reply. |
| `acfun_moment` | read | One 动态 by id: text, topics, images, author, counts, canonical share URL. |
| `acfun_moment_comments` | read | One page of a 动态's comment thread (moments have their own comment enum). |
| `acfun_topic` | read | 社区圈 topic page (best effort, 15 newest items) — the only public way to discover current moment ids. |
| `acfun_interact` | write | Video: `like`, `unlike`, `banana`, `favorite`, `unfavorite`, `follow`, `unfollow`. Moment: `moment_like`, `moment_unlike`, `moment_banana`. |
| `acfun_post_comment` | write | Post a top-level comment or reply on a video (`acId`) or a 动态 (`momentId`). |
| `acfun_account` | read | Login state, credential source, whether interactions are enabled, and the next sign-in step. |
A model-facing skill (`skills/plugin-tool-acfun/SKILL.md`) teaches the tool
sequence and the write-safety rules.
sequence, the write-safety rules, and the 动态 limitations.
## Install
@@ -34,12 +39,42 @@ Install the workspace directory as a profile bundle:
plugin_manager action=install_bundle target=C:\path\to\dsh-acfun
```
The bundle inserts one Loader row (`id: acfun`) with the config below. A local
directory is linked, so edits to the plugin need a Harness restart to load a
fresh module generation.
The bundle inserts two Loader rows: `acfun` (the tools, panel API and QR login)
and `acfun-skill-filesystem` (which exposes this package's `skills/` directory
to the agent). A local directory is linked, so edits to the plugin need a
Harness restart to load a fresh module generation.
To remove it, use `plugin_manager action=remove_bundle target=dsh-plugin-acfun`.
## QR login and the Web UI panel
The bundle ships a Client half (`client.js`) that adds a **Settings → AcFun**
page. It shows the current login state, the credential source and whether
interaction tools are enabled, renders the QR code, and polls until the phone
confirms.
Flow (all Host-side, `lib/qr-flow.js`):
1. `GET https://scan.acfun.cn/rest/pc-direct/qr/start?type=WEB_LOGIN` returns a
base64 PNG plus `qrLoginToken`, `qrLoginSignature` and `expireTime` (120 s).
2. `scanResult` is a single long-poll held by AcFun until the phone scans or the
code expires. On scan it returns a **rotated signature**.
3. `acceptResult` with the rotated signature issues the session cookies; the
plugin captures `Set-Cookie`, persists the cookie through the Host credential
service (`acfun-session` grant record), and points the provider at it.
The panel talks to the Host over `/acfun-panel/*` (same-origin JSON, cross-origin
requests rejected). Scanning a QR code is the only step a plugin cannot verify
by itself, so the confirm branch is exercised only by a real phone scan.
**Restart requirement:** a Client half added to a package that the Host already
scanned needs one Harness restart, then a page reload, before
**Settings → AcFun** appears. The graph is composed from the live Loader entry
set at boot, so a bundle installed into a running page is not in it yet. Detect
it with `GET /acfun-panel/state` (`client.path` must be non-null) or
`GET /acfun-panel/diagnose`; details and evidence are in
[`docs/client-panel.md`](docs/client-panel.md).
## Configuration
The row config lives in `cordis.patch.yml`; every key is optional.
@@ -52,13 +87,16 @@ The row config lives in `cordis.patch.yml`; every key is optional.
| `requestTimeoutMs` | `30000` | Per-HTTP-request timeout. |
| `timeoutMs` | `45000` | Cooperative per-tool-call budget (write calls use at least 60 s). |
A cookie from the panel's QR login is stored as a credential record and wins
over `cookie`/`ACFUN_COOKIE`; signing out in the panel falls back to the config.
Example with the environment variable:
```powershell
$env:ACFUN_COOKIE = 'auth_key=...; acPasstoken=...; _did=...'
```
### Getting the cookie
### Getting the cookie without the panel
1. Sign in to <https://www.acfun.cn/> in a desktop browser.
2. Open DevTools → Network, reload any acfun.cn page, and copy the complete
@@ -85,15 +123,21 @@ plugin never logs it.
Verified live on 2026-10-27 against acfun.cn (anonymous):
- All six read tools — `acfun_search` (including `sort: "views"`), `acfun_video`,
`acfun_comments`, `acfun_danmaku`, `acfun_rank`, `acfun_playurl` — return real
API data and pass output-schema validation plus rendering
`acfun_comments`, `acfun_danmaku`, `acfun_rank`, `acfun_playurl`,
`acfun_moment`, `acfun_moment_comments`, `acfun_topic` — return real API data
and pass output-schema validation plus rendering
(`node scripts/live-check.mjs`).
- All eight tools register through a mock Cordis context and the write guard
fails closed (`node scripts/self-check.mjs`).
- QR login issuance and the waiting/abort path were verified live against
`scan.acfun.cn` (a real ticket and PNG are returned; an unscanned ticket holds
the long-poll until the 120 s expiry, then reports "expired").
- All twelve tools register through a mock Cordis context, the panel routes are
served on a local HTTP server, and the write guard fails closed
(`node scripts/self-check.mjs`).
- Write routes exist and reject anonymous requests with `{"result":-401}`. The
authenticated happy path was **not** exercised here because no AcFun
account/cookie was available. Treat first use as a live test: check the result
in the AcFun UI instead of retrying blindly.
authenticated happy path — including the final QR accept step and moment
writes — was **not** exercised here because no AcFun account was available.
Treat first use as a live test: check the result in the AcFun UI instead of
retrying blindly.
## Known limitations
@@ -102,6 +146,15 @@ Verified live on 2026-10-27 against acfun.cn (anonymous):
only ordering control AcFun honours.
- Bangumi (番剧) and article pages are not supported — `acfun_video` reports
`UNSUPPORTED_RESOURCE` instead of guessing.
- **No 广场 feed API exists.** After ~560 path probes no public square/recommend
moment list was found on any host; moment ids can only be discovered through
`acfun_topic` (server-rendered, 15 newest items, no pagination) or a share
link. The only moment *lists* are login-scoped following feeds.
- Publishing, deleting, reposting and voting on a 动态 are app-only behind the
signed midground token; the web plugin cannot do them.
- Moment like/banana/comment contracts are derived from the site's own store and
verified only up to their auth error (`401`), so treat moment writes as
experimental.
- Danmaku paging beyond the first batch depends on AcFun's opaque `pcursor`;
`acfun_danmaku` passes it through as `cursor`/`nextCursor`.
- Play URLs are time-limited signed m3u8 links; re-run `acfun_playurl` when one
@@ -112,17 +165,19 @@ Verified live on 2026-10-27 against acfun.cn (anonymous):
## Development
```
node scripts/self-check.mjs # offline: exports, tool schemas, write guard
node scripts/live-check.mjs # online: real reads + schema validation + render
node scripts/self-check.mjs # offline: exports, tool schemas, QR state machine, panel HTTP
node scripts/live-check.mjs # online: real reads, QR issuance, schema validation + render
```
No build step, no runtime dependencies. Everything under `lib/` is plain ESM
that imports only Node built-ins/globals. The bundle intentionally exports no
`Config` schema: a workspace-linked bundle cannot resolve `@deepseek-ai/*`
packages, so `lib/config.js` validates the row config and falls back to defaults
with warnings instead of using schemastery.
No build step, no runtime dependencies. Everything under `lib/` and `client.js`
is plain ESM/JavaScript that imports only Node built-ins/globals. The bundle
intentionally exports no `Config` schema: a workspace-linked bundle cannot
resolve `@deepseek-ai/*` packages, so `lib/config.js` validates the row config
and falls back to defaults with warnings instead of using schemastery.
See `docs/acfun-api.md` for the endpoint notes this plugin is based on.
Endpoint notes: `docs/acfun-api.md` (videos/comments/danmaku/rank),
`docs/acfun-login.md` (QR login), `docs/acfun-moment.md` (广场/动态 research and
verdict).
## License