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:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user