API Reference
Hooks
How StreamNook's native UI delegates to plugins through named actions, status slots, and provides flags.
The host never contains code that names a specific plugin. Instead, the app's own features expose named hooks, and a plugin declares which hooks it fills. The host routes by name. Swap in a different plugin that fills the same hooks and the feature works identically.
This is what lets a native control (say the Drops center's Mine button) be powered by an add-on without core knowing the add-on exists.
Info
Hook ids are namespaced as feature.name. They are defined by the host feature that exposes them, not by the plugin. A plugin only fills hooks. It cannot reach anything it was not handed.
Three kinds of hooks
Actions
Named operations the native UI can invoke that a plugin handles.
Status slots
Named regions a plugin pushes values into for the native UI to display.
Provides
Feature flags a plugin declares, so the UI can light up or gray out its controls.
- Actions. Named operations the native UI can invoke that a plugin handles. The UI fires
drops.mine, the host routes it to whichever plugin declared that action and returns the result. If no plugin declared it, the call fails and the UI can show "needs an add-on." - Status slots. Named regions a plugin pushes values into for the native UI to display. The plugin pushes to
drops.status, the host forwards it to the frontend, where the owning component renders it in native style. - Provides. Feature flags a plugin declares. The host answers "does anything provide
drops.mining?" so the UI lights up (or grays out) its controls accordingly.
Manifest
A plugin declares the hooks it fills in a [contributes] block.
[contributes]
actions = ["drops.mine", "drops.mine-auto", "drops.mine-all", "drops.stop"]
status = ["drops.status"]
provides = ["drops.mining"]Settings are a host-rendered panel built from generic field types (see the Manifest and the register_panel schema in the Protocol), so a plugin gets a rich native settings screen without core importing or naming anything plugin-specific. The field vocabulary includes list and picker types (channel_list, string_list as add-remove chips, slider), so a settings screen is never a bare text box. Any plugin composes the same generic fields, and the host renders them blind to which plugin it is.
Tip
See Plugin UI for how panels render in the native settings screen.
Wire protocol
These messages are additive to protocol version 1. See the Protocol for the transport and lifecycle they sit on.
Action call
Host to plugin, request.
{ "jsonrpc": "2.0", "id": 7, "method": "invoke_action",
"params": { "action": "drops.mine", "args": { "campaign_id": "..." } } }The plugin handles the action and replies with a result (or a JSON-RPC error). The host fans the call only to a plugin whose manifest declared that action.
Status push
Plugin to host, notification.
{ "jsonrpc": "2.0", "method": "set_status",
"params": { "slot": "drops.status", "value": { "active": true, "is_mining": true,
"game_name": "...", "channel_login": "...", "current_minutes": 12, "required_minutes": 15 } } }The host ignores a slot the plugin did not declare. Accepted pushes are forwarded to the frontend as a plugin://status event carrying { plugin_id, slot, value }.
Host surface
These are the calls core's own UI uses to talk to the hook system.
| Call | Shape | Purpose |
|---|---|---|
plugins_invoke_action | (action, args) -> result | Core UI invokes a hooked action. The host routes to the providing plugin. |
plugins_provides | (feature) -> plugin_id | null | Core UI checks whether a feature is backed before enabling its controls. |
plugin://status | event | Core UI subscribes to receive a plugin's status-slot pushes. |
Hook catalog
Contracts for the hooks core currently exposes. Hook names are owned by the host feature. Any plugin may fill them.
Drops center (drops.*)
drops.mine { campaign_id? }, drops.mine-auto, drops.mine-all, drops.stop. Each returns:
{ "ok": true }Playback resolution (playback.*)
The playback.resolve action is invoked when a live stream starts and the viewer is not already entitled to watch it ad-free (Twitch Turbo or a channel subscription). Entitled streams never reach the hook. The action supplies a video source for the session.
Args:
{
"stream_id": "solo",
"channel": "somechannel",
"quality": "best",
"auth_master": "<the master playlist core resolved itself, or null>"
}stream_id is the relay session id this resolution will serve (solo, or a multi-stream tile id). The plugin keeps it to address later set_upstream calls. auth_master is core's own direct resolution when it succeeded. A plugin that resolves through an anonymous source can merge the above-1080p tiers from it, since anonymous masters are capped at 1080p.
The response takes one of two shapes.
{ "master": "<HLS master playlist body>", "base": "https://...", "region": "EU" }{ "declined": true }On master, core parses it and selects the variant exactly as it would its own resolution. base and region are optional provenance for the player's source badge. On declined (or any error or timeout), core falls back to its own direct resolution.
The provides flag playback.resolve makes core invoke the action at stream start.
Note
The mid-stream loop is entirely the plugin's own. It detects refresh windows in its own process (polling the playlist it resolved) and answers by re-resolving and calling set_upstream with a fresh media-playlist URL for that session. Core never scans on the plugin's behalf. It only exposes playback.resolve and set_upstream, neither of which is specific to that loop.
Why this shape
It keeps the host generic. The Drops center is a core feature, so it owns the drops.* hook names, but it has no knowledge of any particular plugin: it invokes actions, reads a status slot, and checks a provides flag. One plugin happens to fill those hooks, and a different one could fill the same ones. The same mechanism serves every future plugin, so features become extension points rather than per-plugin wiring.
A plugin reaches the native UI only through hooks it declared in [contributes]. Nothing else is exposed to it.