detain / phlix-plugin-anidb
AniDB metadata provider — anime titles, descriptions, episodes, ratings via UDP API and title dump.
Package info
github.com/detain/phlix-plugin-anidb
Type:phlix-plugin
pkg:composer/detain/phlix-plugin-anidb
Requires
- php: >=8.3
- ext-mbstring: *
- ext-sockets: *
- ext-zlib: *
- detain/phlix-shared: ^0.52.0
- psr/container: ^1.1 || ^2.0
Requires (Dev)
- phpstan/phpstan: ^2.2
- phpunit/phpunit: ^10.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-14 01:16:35 UTC
README
AniDB metadata provider plugin for Phlix — anime titles, descriptions, episodes, ratings via UDP API and daily title dump.
Overview
This plugin fetches structured anime metadata from AniDB using:
- UDP API (
api.anidb.net:9000) — fetch anime details, descriptions, episodes - Daily title dump (
anime-titles.dat.gz) — fast offline title→AID lookups without hitting the API
Features
- Title dump search — offline exact/prefix/contains match search across all AniDB anime titles (exact hits resolve in O(1) from a lowercase-title index built once per load; near-misses are ranked by length similarity — not fuzzy matching)
- Full metadata — romaji/english/kanji titles, synonyms, year, type, rating (the
genreskey is always empty — AniDB category tags are not in the fetched response, see Protocol Notes) - Description — fetch long descriptions via separate ANIMEDESC command
- Flood protection — 4-second rate limiting between API calls (per AniDB rules)
- Session management — keep-alive pings every 30 min, auto-reconnect on 506 SESSION EXPIRED
- Episode info — episode number parsed from the filename by
src/Parser/EpisodeExtractor.phpand returned asepisode_number
Install
The plugin is unsigned by design. Install via the Phlix admin UI:
-
Log in to your Phlix server as an admin user (
users.is_admin = 1). -
Browse to
/admin/plugins. -
Paste this URL into the Install from URL form:
https://raw.githubusercontent.com/detain/phlix-plugin-anidb/master/plugin.json -
The server downloads and validates the manifest, runs
composer install --no-dev, and stores a row in thepluginstable. -
Configure your AniDB credentials in the plugin settings form:
- AniDB Username: your AniDB username
- AniDB UDP API Key: the API password you set in your AniDB profile (Settings → Account → API), NOT your login password
If either is missing or empty, AUTH is refused before any datagram is sent with
AniDB: the "AniDB Username" and "AniDB UDP API Key" settings are required (missing or empty: …), naming each unusable field by its form label. A server-side500 LOGIN FAILEDsurfaces asAniDB AUTH failed: invalid "AniDB Username" or "AniDB UDP API Key" …. -
Enable the plugin.
Requirements
- PHP >= 8.3 with
ext-sockets(AniDB UDP transport),ext-zlib(title-dump gzip handling) andext-mbstring(title normalization) — all three are declared incomposer.json, so the host'scomposer installrefuses to install onto a runtime that lacks them. - Workerman is provided by the Phlix server runtime and is deliberately
not a composer dependency of this plugin. The deferred title-dump
download uses
Workerman\Http\Client/Workerman\Timerwhen present (class_exists-guarded); outside a Workerman runtime (plain CLI, unit tests) the download is skipped rather than blocking, and the UDP path serves. See "AniDB Protocol Notes".
Non-blocking behaviour (resident worker)
The UDP path is async-native by default. CooperativeWaiter (the default
WaiterInterface) suspends the current Fiber and resumes it from the host event
loop (Revolt\EventLoop, else a one-shot Workerman\Timer), and
SocketUdpClient waits for AniDB replies with non-blocking socket_select()
polls that yield between polls. A silent AniDB therefore never parks the worker
for the 10 s budget or the 2 s flood-protection delay when the lookup runs inside
a Fiber (Workerman 5 request handlers do). Outside a Fiber (plain CLI, unit
tests) there is no loop to yield to, so the wait is a bounded blocking
select/usleep. Hosts can inject their own WaiterInterface /
CooperativeWaiterInterface; ProductionWaiter is the explicit blocking
choice for dedicated UDP workers.
Configuration
Configure these in the Phlix admin Plugins → Configure dialog.
| Setting | Type | Required | Default | Description |
|---|---|---|---|---|
username |
string | Yes | — | Your AniDB account username. Register free. |
api_key |
string (secret) | Yes | — | The UDP API Key (API password) set in your AniDB profile under Settings → Account → API — separate from your login password. See the AniDB API docs. |
use_title_dump |
boolean | No | true |
Download the daily title dump for fast, offline search (reduces rate-limited API calls). |
title_dump_url |
string | No | https://anidb.net/api/anime-titles.dat.gz |
URL to anime-titles.dat.gz; change only for a mirror. Must be HTTPS — AniDB's Cloudflare front answers plain HTTP with 403, and any other plain-HTTP URL is refused (no download, a REFUSING log line) unless the server environment sets PHLIX_ANIDB_ALLOW_HTTP_DUMP_MIRROR=1 — see "Protocol & credential posture". A stored http://…anidb.net/… value from before 0.4.1 is migrated to https automatically. |
cache_dir |
string | No | — | Absolute path of a writable directory for the title-dump index. It must live outside the read-only plugin tree (systemd sandbox) and must not be world-writable (the index is served from it). A relative path, a non-string value, or an existing world-writable directory is refused at construction with a loud error. Unset falls back to the PHLIX_ANIDB_CACHE_DIR environment variable (same rule), then the system temp dir. |
The title-dump index is written to a writable cache directory outside the plugin tree, resolved in this order: constructor argument → cache_dir setting → the PHLIX_ANIDB_CACHE_DIR environment variable → sys_get_temp_dir()/phlix-plugin-anidb.
The AniDB UDP API authenticates with your username + a separate API Key you set in your profile (not your website login password). Set the API Key under your AniDB profile → Settings → Account → API.
How It Works
The plugin registers an AnidbMetadataProviderAdapter with the host MetadataManager.
When the server needs anime metadata it calls the adapter's search() or
getDetails() methods (the adapter's surface is search/getDetails/getImages/
getProviders/getSourceName; the file-path-based lookup() lives on the wrapped
provider in src/AnidbMetadataProvider.php, not on the adapter):
- search(title) — resolve an anime title to an AID using the title dump or API fallback
- getDetails(aid) — fetch full anime metadata by AID
- lookup(filePath) — extract anime name from a file path and return metadata
Internal flow (when lookup() or getDetails() is called):
- Fetch details — send
ANIME aid=...for full anime data - Fetch description — send
ANIMEDESC aid=...for the full synopsis - Map response — translate AniDB field layout to MetadataManager's expected return shape
Registration (onEnable()) does wiring only — the socket, title-dump download and
index load are deferred to the first real lookup so plugin boot never blocks the
resident Workerman worker.
AniDB Protocol Notes
- Protocol: UDP (not HTTP) to
api.anidb.net:9000 - Flood protection: ≤ 0.5 packets/sec after first 5, minimum 4 seconds between packets
- Session: valid 35 minutes; keep-alive with PING every ~30 minutes (the server answers a bare
PINGwith300 PONG) - Flood ban: reusing the same local UDP port is critical to avoid IP-level bans
- Field coverage: the ANIME amask in use (
00f0f0f0000000) returns titles, episode/date and rating fields only — AniDB's category/genre tags are not part of that response, sogenresis structurally always empty (deriving real genres would additionally require vote/weight filtering over the tag cloud)
Protocol & credential posture
The AniDB UDP API has no TLS and no TCP alternative, but it does offer packet
encryption: the client opens every session with ENCRYPT user=<name>&type=1
(the server answers 209 <salt> ENCRYPTION ENABLED), derives an AES-128 key as
md5(api_key + salt), and sends the AUTH datagram and every later command
encrypted. This handshake was verified against the live server (see
docs/captures/anidb-udp-wire-2026-11-13.md). If the server ever refuses
ENCRYPT, the client logs that and falls back to the cleartext pass= AUTH, in
which case any party on the network path between the Phlix server and
api.anidb.net:9000 could read those bytes. Mitigations, in order of
importance:
- Use a dedicated AniDB account for Phlix, never your personal one.
- Set an API password that is unique to AniDB and not reused anywhere (it is already separate from the site login password — see Settings above).
- Run Phlix on trusted network infrastructure; if in doubt, confirm with your
host that the egress path to
api.anidb.netis not shared with untrusted tenants.
Reply-origin check is an address allowlist, not authentication. A UDP reply
is accepted only if it comes from port 9000 on one of the resolved IPs of
api.anidb.net (DNS is resolved again every hour). That drops stray and
misrouted datagrams, but UDP does not authenticate source addresses, and AniDB
does not sign its replies. Anyone on the network path, or anyone who can spoof
AniDB's source IP and port and get the timing right, can still get a forged
reply accepted. That reply could be fake metadata or a fake AUTH/session
answer. The protocol cannot close this gap. The mitigation is the same trusted
network path described above.
title_dump_url must be HTTPS (default-deny cleartext). The title dump
feeds every worker's offline title→AID index, and a plain-HTTP download has no
certificate to verify, so anyone on the network path could serve a forged dump.
A non-HTTPS title_dump_url is therefore refused: each download attempt
logs TitleDumpIndexer: REFUSING download — title_dump_url uses cleartext HTTP (http://<host>) and contacts nothing, and the previously cached index (if any)
keeps serving. If your mirror genuinely has no HTTPS, set the environment
variable PHLIX_ANIDB_ALLOW_HTTP_DUMP_MIRROR=1 (exactly 1) for the Phlix
server process; the fetch then proceeds and logs a cleartext warning on
every attempt. Redirects are never followed, so an https URL cannot be walked
down to http by a 301 either way.
title_dump_url fetch reachability (deferred guard). The server fetches
whatever URL the title_dump_url setting names. The setting lives in the
plugin settings form (advanced tier), so only an administrator can change it.
Members cannot reach it. The fetched body is never shown to anyone. It is
size-capped and thrown away unless it decompresses and parses as a valid title
dump. Logs carry only refusal reasons, byte counts, the scrubbed URL and the
transport error class/message — never body content. There is no host
allowlist and no private/loopback-range deny on this URL today, so an
administrator can point the fetch at an internal address and the server will
send that request; only the transport outcome is logged. This is accepted
because only administrators can set the URL.
If title_dump_url ever becomes settable by members, a host
allowlist or a private-range deny must ship in the same change.
See the AniDB UDP API docs for full details.
Data Returned
[
'title' => 'Seikai no Monshou', // Primary romanized title
'original_name' => 'Crest of the Stars', // English official title (or kanji)
'overview' => 'A space opera...', // Description (fetched separately)
'year' => 1999, // First release year
'genres' => [], // Always empty — categories not in the fetched amask
'rating' => 8.53, // AniDB rating (0-10)
'vote_count' => 3225, // Number of votes
'poster_url' => 'https://cdn-eu.anidb.net/images/main/1.jpg', // null if no picname
'fanart_url' => null, // Not provided by AniDB
'episodes' => 13, // Episode count (null if unknown)
'type' => 'tv', // Normalized type (tv, movie, ova, etc.)
'anidb_id' => 1, // AniDB AID
'titles' => ['Seikai no Monshou', 'Crest of the Stars', '星界の紋章'],
'status' => 'Finished', // Finished / Currently Airing / Upcoming
'runtime_ticks' => null, // Not provided by AniDB
'studio' => null, // No studio field; AniDB's studio data lives in categories (not fetched)
'studios' => [], // Same, as a list
'source' => null, // Source material, when known
'is_movie' => false, // Derived from type / filename pattern
'synonyms' => ['Crest of the Stars'], // True synonyms from the ANIME response (field 11); short_names (field 10) also feed 'titles'
'description_fetch_failed' => false, // true when the synopsis (ANIMEDESC) fetch failed — distinguishes "no synopsis exists" from "synopsis fetch failed"
'episode_number' => 1, // Parsed from the filename; key is ABSENT (not null) when the filename yields none
]
Fork as a Starter
This plugin is based on phlix-plugin-example. To create your own metadata provider:
- Fork or copy this repository.
- Edit
plugin.json— pick a newname(must start withphlix-plugin-), setversionto0.1.0for your fork, changeentryto your FQCN. - Edit
composer.json— rename the package, update PSR-4 autoload prefix. - Replace
src/AnidbMetadataProvider.phpwith your own implementation. - Run tests:
composer install && vendor/bin/phpunit.
Testing
composer install vendor/bin/phpunit vendor/bin/phpunit --testdox # verbose output vendor/bin/phpstan analyse --no-progress # static analysis, level 9 over ALL of src/ — zero ignoreErrors, zero excludes (phpstan.neon) php scripts/security-audit-check.php # security audit over the whole lock (require + require-dev)
License
MIT — see LICENSE.