# danmu_api: a JavaScript comment server that turns streaming URLs into subtitle tracks

> LogVar danmu_api fans in bullet-screen comment data from the major Chinese video platforms, normalises it into a dozen output formats, and ships as one Express server you can run locally, in Docker, or on a free serverless platform.

**huangxd-/danmu_api** — 一个人人都能部署的基于 js 的弹幕 API 服务器，支持爱优腾芒哔咪人韩巴狐乐西埋帆红弹幕直接获取，兼容弹弹play的搜索、详情查询和弹幕获取接口规范，并提供日志记录，支持vercel/netlify/edgeone/cloudflare/docker/hf等部署方式，不用提前下载弹幕，没有nas或小鸡也能一键部署。

- Repository: https://github.com/huangxd-/danmu_api
- Website: https://bks.indevs.in
- Stars: 3,186 · Forks: 3,035
- Language: JavaScript
- License: AGPL-3.0
- Published: 2026-10-06 · Updated: 2026-10-06 · Language: en
- Canonical page: https://hysenlabs.com/projects/huangxd-danmu-api

## What the project actually does

The repository `huangxd-/danmu_api` is a JavaScript Express server that retrieves bullet-screen comments, the scrolling text overlay popular on Chinese video platforms, for a given episode and hands them back in a format a media player understands. GitHub reports it as JavaScript under the AGPL-3.0 licence, with about 3,186 stars and 3,035 forks. The last push landed on 2026-09-20, so the tree is current, and 14 open issues is a modest backlog for a service that fronts a dozen upstream sites.

The README calls the service LogVar, and the Docker badge in the header points at the `logvar/danmu-api` image. The repository slug stays lowercase and underscored, which is worth keeping in mind when you are reading Docker commands.

The design goal stated in the README is that nobody should have to own a home server or a small VPS. Instead of downloading comment files ahead of time, you deploy the API and let it fetch on demand. That single decision explains most of the rest: the deployment targets, the cache layers, and the multiple adapter formats all exist because the server has to run somewhere as cheap as a serverless function.

What it is not is a scraper library you import. There is no published package here, only an application you clone and run. The topics list `anime`, `api`, `dandanplay`, `danmu`, `danmuku` and `server`, which places it in the same family as media metadata tools rather than in general-purpose scraping.

It also says plainly that the project was built for personal study and hobby, is open source, and will be removed on request in the event of an infringement complaint. That framing matters if you are considering running it as part of a commercial service, because the licence is copyleft and the upstream platforms set their own terms.

## The endpoint surface and what each call answers

The API is organised around four questions a player needs answered: which show is this, which episode is it, how long is it, and what did viewers type. Each has a route, and the naming follows the dandanplay and FengMi conventions so existing clients can point at a different host without code changes.

Search comes in two shapes. `GET /api/v2/search/anime?keyword=` takes a title and returns matching titles with images and episode counts, while `GET /api/v2/search/episodes` returns every matching episode for a keyword. The automatic path is `POST /api/v2/match`, which is the one you wire into a player so the user types a filename instead of picking from a list. That route accepts an inline platform override, so a query like a title plus `@qiyi` biases matching toward one provider, and it parses season and episode numbers out of ordinary release filenames.

Detail and payload follow. `GET /api/v2/bangumi/:animeId` returns the record for one show. `GET /api/v2/comment/:commentId` returns the comments themselves, and adding `duration=true` with a JSON response attaches a `videoDuration` field that prefers the upstream duration and falls back to `0` rather than failing. `POST /api/v2/segmentcomment` exists for players that want one time segment at a time, taking the segment structure from a prior comment response. For players that already hold a video URL, `GET /api/v2/comment?url=` fetches comments straight from the URL, and the server documents this as compatible with third-party comment server formats.

Two compatibility short paths are exposed for FengMi clients: `GET /api/v2/fongmi/danmaku?name=&episode=` and a shorter alias under `/danmaku/`. Both take a name and an episode number.

Two operational endpoints sit alongside. `GET /api/logs` returns up to the most recent 500 lines in a `[timestamp] level: message` shape, and `GET /api/cache/animes` exposes the recent search cache. The log route is genuinely useful when a deployment silently returns nothing, because the server captures `console.log` at info level and `console.error` at error level and formats JSON payloads readably.

## Format negotiation across a dozen output shapes

Comments are only useful if the player can read them, and no two players agree on the format. This server sidesteps the argument by supporting JSON, XML, and every format offered by the `ani-uni/dan-any` library, which is also a runtime dependency pinned at `^2.3.9`.

The formats named in the README are `json`, `xml`, `artplayer.json`, `baha.json`, `bili.xml`, `danuni.json`, `danuni.binpb`, `ddplay.json`, `dplayer.json` and `vod.json`, with `json` as the default. Two mechanisms select one. The `DANMU_OUTPUT_FORMAT` environment variable sets a deployment-wide default, and a `format` query parameter overrides it per request. The stated precedence is query parameter, then environment variable, then default, so one deployment can serve several player types if you let the clients ask.

The XML output is the interesting case because it is not an ad hoc serialisation. The README states that it follows the Bilibili standard format with the eight standard comment attributes, which means existing tooling that reads Bilibili XML will accept it unchanged. For a media server that wants one on-disk library of comment files, that compatibility is the whole point.

The dependency list also explains parts of the pipeline that the endpoints alone would not reveal. `pako` and `brotli` handle decompression of upstream responses, since providers compress their payloads. `node-fetch` provides the HTTP client and `https-proxy-agent` lets the server route requests through a proxy, which matters in regions where some upstream hosts are unreachable directly. `opencc-js` is a Chinese conversion library, which pairs with the `TITLE_TO_CHINESE` variable used when matching foreign-language titles such as a dotted romanised series name.

## Three cache layers with deliberately different lifetimes

Caching is where this project is most opinionated, and the opinions are worth reading before you deploy. There are three separate layers with three different rules, plus an optional Redis backend.

The short-lived layer is in-process memory. Search results are cached for `SEARCH_CACHE_MINUTES`, defaulting to 1 minute, and comment payloads for `COMMENT_CACHE_MINUTES`, defaulting to 5 minutes. Those two stay in instance memory even when Redis is configured, which is a deliberate choice: they are cheap to recompute and write volume should not go to a network store on every request. A separate preference map, capped by `MAX_LAST_SELECT_MAP` at 100 entries by default, remembers which title a user manually picked and which episode offset applied, so automatic matching prefers the earlier choice. The README marks the episode offset behaviour as experimental.

The permanent layer is the favourites cache, and it exists for long-running shows where a search costs a lot and the result barely changes. It stores the full set of search results for a keyword and, importantly, never stores comment payloads. Favourites have no TTL, are not subject to `SEARCH_CACHE_MINUTES`, and are exempt from the 500-entry ceiling on the ordinary search cache, so they cannot be evicted by ordinary traffic. When a later search or an automatic match hits a favourite, the server returns it without contacting any upstream source at all.

Around that sit write operations: `POST /api/v2/favorite/add`, a read-only `list` endpoint, `refresh` to bypass the cache, `schedule` to set a recurring refresh, and `remove` to delete both the favourite and its search cache. Scheduling accepts a frequency and a time, with an optional weekday for weekly jobs, and it runs on a fixed `Asia/Shanghai` clock. A failed run keeps the old cache, retries once after ten minutes, then waits for the next period. A missed run while the process was down is caught up once on restart.

Persistent storage is where deployment shape starts to matter. On Node and Docker, favourites and schedules are written to `.cache/favoritesCache`, so mounting the `.cache` directory preserves them. On serverless platforms nothing is persisted unless you configure `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`, and when you have not, the interface greys out the favourite controls and says why.

## Token handling and the local upload path

Anything that writes is gated, and the README is specific enough to save you from guessing. The default `TOKEN` value is `87654321`, which is documented openly and therefore should be treated as public. When you set a custom token, the write endpoints stop answering at their plain paths and expect the token in the URL, in the form `/{TOKEN}/api/v2/favorite/...` or the same shape with `ADMIN_TOKEN`. With the default token still in place and admin restriction off, the token can be omitted entirely.

Setting `FAVORITE_REQUIRE_ADMIN=true` narrows writes and management operations to `ADMIN_TOKEN` alone. A separate switch, `LOCAL_DANMU_NOT_REQUIRE_ADMIN=true`, decides whether an ordinary `TOKEN` may upload and delete local comment files; by default that is admin-only.

The local upload path is the other half of the system and deserves its own attention, because it is how you get comments for content that has no upstream provider. `POST /api/v2/local-danmu/upload` accepts `multipart/form-data` with fields for `file`, `title`, `year` and `type`, plus `season` and `episode` for series, one file per request and 10 MB maximum. The management page batches imports by calling it repeatedly. `GET /api/v2/local-danmu/list` returns uploaded resources grouped by title, year, type and season, and `GET` plus `DELETE` on a single `:resourceKey` covers metadata and removal.

Editing is a `PATCH` with a scope. `scope=resource` changes the episode number and the display filename, while `scope=group` changes the season's title, year, type and season count. If the target resource already exists the server returns a conflict error and leaves the original file alone rather than overwriting it, which is the right default when the same episode has been imported twice from different sources.

Storage for these files follows the same split as favourites. Node and Docker write to `.cache/local-danmu`, while cloud deployments use Redis for persistence.

## Running it locally or in Docker

The repository is small and the entry point is obvious. `package.json` names the package `danmu-api-server`, declares ES modules with `"type": "module"`, points `main` at `danmu_api/server.js`, and exposes one start script alongside two widget build scripts:

```json
  "scripts": {
    "start": "node danmu_api/server.js",
    "build-forward-widget": "node build-forward-widget.js",
    "build-forward-widget:debug": "node build-forward-widget.js --debug"
  },
```

The dependency block explains a good deal about what the server does at runtime:

```json
  "dependencies": {
    "@dan-uni/dan-any": "^2.3.9",
    "brotli": "^1.3.3",
    "chokidar": "^4.0.3",
    "opencc-js": "^1.4.1",
    "pako": "^2.1.0",
    "redis": "^5.11.0"
  },
```

`chokidar` at version 4 is the file watcher, which fits a local deployment that wants to notice new comment files, and `redis` at `^5.11.0` backs the optional distributed cache. `esbuild` and `dotenv` are there as well, which is how the widget bundle and environment loading work.

The Dockerfile is the shortest useful summary of the deployment contract:

```dockerfile
FROM node:22-alpine
WORKDIR /app
EXPOSE 9321
```

A Node 22 Alpine base, an exposed port of 9321, and no build stage beyond installing dependencies. Note that the container copies `config/` into a directory named `config_example`, so a mounted configuration file goes in where the README's environment section expects it rather than over the shipped defaults.

The repository tree also tells you which platforms are first class. Alongside `Dockerfile` and `package.json` sit `vercel.json`, `netlify.toml` and a `netlify/` directory, `edgeone.json`, `wrangler.toml` for Cloudflare, a `README.hf.md` for Hugging Face Spaces, and a `node-functions/` directory. A `forward/` directory and `build-forward-widget.js` handle the forwarding widget, and `.pr_agent.toml` suggests the project uses an automated pull-request agent for review comments.

There are no releases published, which is normal for a continuously deployed service: the Docker version badge tracks the published image rather than a GitHub release list.

## What to check before you point a player at it

A few practical decisions are worth making deliberately rather than discovering at midnight.

Persistence first. If you run on Node or Docker, mount `.cache` and both the favourites cache and the uploaded local comments survive a restart, and scheduled refreshes keep running because the scheduler starts with the process. If you deploy to Vercel, Netlify, EdgeOne, Cloudflare or Hugging Face Spaces, the scheduler is not started at all: the server returns `501` for the schedule endpoint and the interface disables the control with a note that it works only on Node or Docker. Configure Upstash Redis before you expect favourites to work there, because without it a cold start loses everything and the buttons stay greyed out.

Tokens second. Change `TOKEN` from its documented default before the deployment is reachable by anyone else, and decide whether you need `FAVORITE_REQUIRE_ADMIN` and `LOCAL_DANMU_NOT_REQUIRE_ADMIN` at all. Those two switches are independent, so it is easy to open local uploads more widely than you intended while still protecting favourites.

Format third. Decide whether clients should ask for their format through the `format` parameter or whether you set `DANMU_OUTPUT_FORMAT` once and accept that every player gets the same shape. The per-request parameter is the more flexible choice and costs nothing.

Match quality fourth. Automatic matching parses season and episode numbers out of release filenames, accepts a platform override with the `@` syntax, honours an `AUTO_MATCH_MAPPING_TABLE` for cross-title and cross-season ranges, and can consult AI matching when the AI environment variables are configured. For libraries with messy naming, the mapping table is the setting that actually reduces manual corrections, since the manual choice memory only applies after a human has already picked once.

Licensing deserves a final note. The repository is AGPL-3.0, though the `package.json` license field still reads ISC, an inconsistency inherited from the template it was scaffolded from. If the distinction matters to you, treat the `LICENSE` file as authoritative.

## Conclusion

danmu_api earns its place in a media stack for one reason: it turns a URL your player already knows how to open into a comment track it did not have. The rest of the repository is careful about the boring parts that usually break fan-in proxies, namely output format negotiation, cache layers with different lifetimes, and a clear statement of which features need a persistent disk or Redis. If you are running a library on Node or Docker, mount `.cache` and everything including scheduled refreshes works. If you are deploying to Vercel, Netlify, EdgeOne, Cloudflare or Hugging Face Spaces, read the persistence notes first, because a cold start discards in-memory state and no scheduler runs at all. Either way, set a token other than the default before you expose it, since the documented default value is public.

## FAQ

### What does "Danmu" mean?

Danmu is the Chinese term for bullet-screen comments: viewer reactions that scroll across the top of a video at the moment they are sent, so a single episode can carry a few thousand of them at once. The English-language ecosystem usually says danmaku or bullet comments. The project name `danmu_api` and its `danmu`, `danmuku` and `dandanplay` topic tags all use that vocabulary.

### Does danmu_api work without a server of my own?

Yes. The README lists one-click deployment targets for Vercel, Netlify, Tencent EdgeOne Pages, Cloudflare, Hugging Face Spaces and Docker, and its stated goal is that people with no home server or VPS can still run it. What serverless platforms do not give you is persistence or scheduling: without Upstash Redis the favourite controls stay disabled, and the refresh scheduler does not start at all on those platforms.

### Which comment formats can the server return?

JSON and XML, plus every format offered by the `ani-uni/dan-any` library: artplayer.json, baha.json, bili.xml, danuni.json, danuni.binpb, ddplay.json, dplayer.json and vod.json. Set a default with the `DANMU_OUTPUT_FORMAT` environment variable, or override it per request with a `format` query parameter, which takes priority. The XML output follows the Bilibili standard format with its eight standard comment attributes.

### How do I protect the write endpoints on a public deployment?

Set a custom `TOKEN` rather than relying on the documented default of 87654321. With a custom token in place, write routes must be called as `/{TOKEN}/api/v2/favorite/...` or the same shape with `ADMIN_TOKEN`. Setting `FAVORITE_REQUIRE_ADMIN=true` limits favourite writes and management to the admin token, while `LOCAL_DANMU_NOT_REQUIRE_ADMIN=true` is the separate switch that decides whether an ordinary token may upload and delete local comment files.

## Sources

- [huangxd-/danmu_api on GitHub](https://github.com/huangxd-/danmu_api)
- [Issues](https://github.com/huangxd-/danmu_api/issues)
- [License: AGPL-3.0](https://github.com/huangxd-/danmu_api/blob/main/LICENSE)
- [Project website](https://bks.indevs.in)
- [README](https://github.com/huangxd-/danmu_api/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/huangxd-danmu-api
