- 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
185 lines
9.3 KiB
Markdown
185 lines
9.3 KiB
Markdown
# dsh-plugin-acfun
|
||
|
||
DeepSeek Harness plugin for **AcFun (A站, acfun.cn)**: search videos, read video
|
||
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
|
||
`allowWriteActions: true` **and** an AcFun login cookie, so installing the bundle
|
||
never silently gives the model write access to an account.
|
||
|
||
## Tools
|
||
|
||
| Tool | Kind | What it does |
|
||
| --- | --- | --- |
|
||
| `acfun_search` | read | Keyword video search; `sort: "views"` orders by play count (AcFun returns one fixed first page of up to 30). |
|
||
| `acfun_video` | read | Full metadata for one video: author, channel, tags, stats, publish time, cover. |
|
||
| `acfun_comments` | read | One page of a video's comment thread, in floor order. |
|
||
| `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_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, the write-safety rules, and the 动态 limitations.
|
||
|
||
## Install
|
||
|
||
Install the workspace directory as a profile bundle:
|
||
|
||
```
|
||
plugin_manager action=install_bundle target=C:\path\to\dsh-acfun
|
||
```
|
||
|
||
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.
|
||
|
||
| Key | Default | Meaning |
|
||
| --- | --- | --- |
|
||
| `cookie` | `''` | Full AcFun `Cookie` header value. Prefer the environment variable so the secret does not enter the patch. |
|
||
| `cookieEnv` | `ACFUN_COOKIE` | Environment variable read when `cookie` is empty. |
|
||
| `allowWriteActions` | `false` | Master switch for `acfun_interact` and `acfun_post_comment`. |
|
||
| `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 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
|
||
`Cookie` request header of the document request (it contains `auth_key`,
|
||
`acPasstoken`, `_did`, and other session values).
|
||
3. Paste it as one line into `cookie` or `ACFUN_COOKIE`.
|
||
|
||
Treat that string like a password: it grants full access to the account. The
|
||
plugin never logs it.
|
||
|
||
## Safety model
|
||
|
||
- Reads cannot mutate anything and need no cookie.
|
||
- Writes require both `allowWriteActions: true` and a cookie. With only one of
|
||
them the tools return `WRITE_DISABLED` / `LOGIN_REQUIRED` and perform no HTTP
|
||
write request.
|
||
- Write tools are declared non-concurrency-safe so the harness will not run two
|
||
account mutations in parallel.
|
||
- A CAPTCHA/identity-verification challenge from AcFun (`result` 400001–410999)
|
||
is surfaced as `CAPTCHA_REQUIRED`; the plugin never tries to solve it.
|
||
|
||
## Verification status
|
||
|
||
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`,
|
||
`acfun_moment`, `acfun_moment_comments`, `acfun_topic` — return real API data
|
||
and pass output-schema validation plus rendering
|
||
(`node scripts/live-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 — 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
|
||
|
||
- AcFun's pc-direct search endpoint ignores `page`/`pageSize` and always returns
|
||
its first page (30 entries), so `acfun_search` cannot paginate. `sort` is the
|
||
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
|
||
expires. Downloading/merging streams is out of scope.
|
||
- This plugin only talks to acfun.cn's public web endpoints; it is unofficial and
|
||
can break when AcFun changes them.
|
||
|
||
## Development
|
||
|
||
```
|
||
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/` 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.
|
||
|
||
Endpoint notes: `docs/acfun-api.md` (videos/comments/danmaku/rank),
|
||
`docs/acfun-login.md` (QR login), `docs/acfun-moment.md` (广场/动态 research and
|
||
verdict).
|
||
|
||
## License
|
||
|
||
MIT — see `LICENSE`.
|