nano’s stream resolution is powered by a plugin system. Each plugin is aDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/mohameodo/nano/llms.txt
Use this file to discover all available pages before exploring further.
.rink file — an AES-256-CBC encrypted JavaScript module that exports an object implementing the ScraperPlugin interface. Plugins are loaded from src/shade/catalog/ and src/shade/private/ at build time by Vite’s glob import, decrypted in memory, and executed at runtime. No plugin source code is ever exposed to the client.
How plugins work
Glob import at build time
Vite loads all
.rink files from both src/shade/catalog/ and src/shade/private/ as Uint8Array binary data using import.meta.glob with the ?uint8array query. Both directories are active: catalog holds the pre-compiled bundled sources that ship with the package; private holds any additionally activated plugins copied there by shiopa add. Plugins in both directories are loaded and registered at runtime.Decrypt and execute at runtime
getPlugins() iterates every loaded module, decrypts the binary content with a derived AES-256-CBC key, then evaluates the resulting CommonJS string with a sandboxed new Function(...) call. Each executed module’s exports are collected into an array of ScraperPlugin objects. Duplicate key values are deduplicated — first occurrence wins.Route the scrape request
When
/api/scrape receives a request with a provider query parameter, resolveStream() finds the plugin whose key matches providerId. If the plugin is enabled, its fetchStream() method is called with the TMDB id, content type, and optional season / episode.The ScraperPlugin interface
Every .rink module must export an object (or array of objects) that satisfies this TypeScript interface, defined in src/lib/nano/plugins-loader.ts:
A plugin may also export an array of
ScraperPlugin objects from a single .rink file. All entries in the array are registered individually and each must have a unique key.Installing bundled sources with the shiopa CLI
The shiopa CLI (installed globally from the shiopa-nano npm package) manages encrypted catalog sources. The catalog ships inside the npm package itself — shiopa add copies the appropriate .rink file from src/shade/catalog/ into your active plugins directory (src/shade/private/) and registers the server in config.shiopa.ts.
shiopa add, the source ID is automatically appended to the servers array in src/components/shiopa/config.shiopa.ts so it appears in the player’s server picker. You can set any installed source as the default via the DEFAULT_SERVER environment variable.
Bundled catalog sources
The following sources are included in theshiopa-nano npm package and available via shiopa add:
| Source ID | Display Name |
|---|---|
rei | Rei |
nemu | Nemu |
haru | Haru |
sora | Sora |
kisskh | KissKH |
yuki | Yuki |
aoi | Aoi |
ren | Ren |
nagi | Nagi |
kaede | Kaede |
hina | Hina |
riku | Riku |
kaze | Kaze |
noa | Noa |
akari | Akari |
yume | Yume |
momo | Momo |
xpass | XPass |
hana | Hana |
The catalog
.rink files are encrypted binaries. Their source is not publicly readable. They are bundled inside the shiopa-nano npm package and fetched automatically when you run shiopa add.getPlugins()
getPlugins() is exported from src/lib/nano/plugins-loader.ts and returns all successfully loaded, deduplicated plugins at the time of the call. Plugins that fail decryption or execution are silently skipped. You can call this function directly for admin UI, debugging, or to enumerate the active server list at runtime:
resolveStream()
resolveStream() is exported from src/lib/nano/index.ts and is the main entry point called by the /api/scrape route handler. It:
- Calls
getPlugins()and finds the plugin matchingproviderId - Skips disabled plugins and returns an empty result without throwing
- Calls
plugin.fetchStream()and passes the result throughfinalizeStream() - Proxies the stream URL (and any subtitle/quality URLs) through
/api/proxy
StreamResult:
StreamResult (with url: "") is returned when no matching plugin is found, the plugin is disabled, or fetchStream() returns null or throws.
Stream URL safety
Before any URL is proxied or returned, nano validates it withisAllowedStreamUrl() from src/lib/nano/stream-safety.ts. The rules are:
- The URL must be non-empty and non-whitespace
blob:URLs and/api/streampaths are always allowed- URLs matching a blocklist of explicit-content hostnames are rejected
- All remaining URLs must begin with
httpor/api/
resolveStream() to return the empty StreamResult rather than exposing the rejected URL to the client.
Stream headers and origin spoofing
For streams that require specificReferer or Origin headers (common with CDN-hosted HLS), nano automatically infers the correct headers based on either the providerId or the stream hostname. This logic lives in src/lib/nano/stream-headers.ts and is applied transparently before the proxy URL is constructed — plugin authors do not need to handle it manually.
A plugin can optionally return a headers object alongside url and isM3U8; those headers are merged with the inferred ones (plugin-provided values take precedence):