Example plugins¶
Two published plugins, both public, both installable in Kino, and both usable as a template. Start from Internet Archive for the simplest possible template; start from Tu servidor, the complete API demo, when your source is a server the person owns, or when you want to see every feature up to apiVersion 7 working end to end.
-
Tu servidor · kinotvapp/kino-plugin-own-server
The complete API demo. A media server at home (Jellyfin, Emby, a NAS…): the person types its address, user and password. Version 1.5.0, apiVersion 7:
"hosts": [], every setting type and the full settings form, a session kept withkino.storage,kino.rank, seasons,download, copies, request signing,channelsin every shape, a section and Categorías tiles,migrate,meta,subtitles,trackingandsegments, plus a reference server (server.mjs) to run it against with nothing of your own. -
Internet Archive · kinotvapp/kino-plugin-archive
Public-domain films and classic TV from archive.org. The simplest template to start from: one manifest, one JavaScript file, no build step, all five capabilities plus
download, and onelistsetting for the person's own archive.org addresses (apiVersion 4), with thesdk/kit,GUIDE.md,contract.jsonandkino.d.ts.
To try either one in Kino, open Ajustes > Plugins and type kinotvapp/kino-plugin-archive or
kinotvapp/kino-plugin-own-server.
Use the template, don't fork. "Use as template" creates a fresh repository of your own with the
same files. A fork would work as a plugin too, but Kino's community search leaves forks out
(Get found). Then change id, name, homepage, hosts and
capabilities in kino-plugin.json, rewrite plugin.js, and keep sdk/.
A real-world example: a plugin that uses the hidden browser
Maratón (signed, apiVersion 6, "browser": "pages") is a real-world plugin, by someone else, that
finds its video with kino.browser.capture and offers each episode's other servers
and languages as labelled lazy copies. It is an example of those two
features only (Kino just lists community plugins: each author is responsible for theirs); "Tu servidor" stays the complete reference plugin.
The reference plugin¶
For the basics -- search, home, browse, episodes and resolve over a public site, with no
settings or session -- the reference is kino-plugin.json and plugin.js in
kinotvapp/kino-plugin-archive, the Internet
Archive plugin, with all five capabilities. It reads about like this:
- It declares
archive.organd*.archive.org: a download URL onarchive.orgredirects to a storage node such asdn720705.ca.archive.org, and the wildcard does not cover the bare domain. getJsondoes theawaitfirst and throws afterwards (the rule of the rejection trap).searchcleans what the person typed: archive.org answers 200 with an error body when the query has a stray/,-,&or'or a danglingAND/OR/NOT, so it keeps letters, digits and apostrophes inside words, drops the operator words, and asks both collections (films and classic TV) whatevertypesays, using it only to decide which group comes first; an item that is in both is listed once.homebuilds three rows (films, classic TV, classic animation) and wraps each row in its owntry/catch, so one failing row does not lose the others; it reports it withkino.log. Each row carries its own id asref, andbrowse(ref, cursor)pages through the same query 50 at a time with the page number as the cursor ("2","3", …), throwingkino.error("not_found")for a row it does not know.episodesreads the item's file list, keeps the video originals in natural order (a smallnatural()comparator, becauselocaleComparecannot be trusted), numbers them fromS01E02in the file name or 1, 2, 3, and uses"<item>|<file name>"as each episode'sref.resolvepicks the best playable file (an mp4 derived from the original, or the mp4/webm itself), turns sibling.vtt/.srtfiles intosubtitles, and setsdurationMs.- Every URL it builds is
httpson a declared host; posters usehttps://archive.org/services/img/<id>and are not host-checked.
README.md in that repository says what it does not do (a collection is exposed as a single movie,
episodes numbered 0 are dropped), so do not copy those as intended behavior.
For everything else, up to apiVersion 7 (Kino 0.9.51) -- settings, a session, downloads, live
items, channels, the apiVersion 6 set, tracking and segments -- the reference is
kinotvapp/kino-plugin-own-server ("Tu servidor"
1.5.0): its
kino-plugin.json
and its plugin.js use
nearly everything that exists, and its
README.md maps every
feature to the title of its test server that exercises it:
| What it shows | Where in the code | Guide |
|---|---|---|
"hosts": [], the server the person types and its other addresses (a list of url fields) |
kino-plugin.json; base(), addresses(), reach() |
The person's own servers |
Every setting type: url, text, password, toggle, select, list, section (a 300-character hint), status, action |
kino-plugin.json: settings |
The settings form |
Status lines, buttons (confirm, clearSettings) and a check before saving |
settingsStatus(), action(), validateSettings() |
The settings form |
A login and a token kept in kino.storage, retried once on a 401; kino.sleep on a short Retry-After |
token(), api() |
kino.storage |
Typed errors (kino.error) and your own sentence (userMessage) |
api(), resolveCopy() |
Errors people understand |
A cache with ttlMs the person picks, and the last copy when the server is down; telemetry + kino.log.report |
home(), reach() |
kino.storage, Telemetry |
Title search over a backend that matches any word; a Page with next; scopedSearch |
search(), kino.rank.* |
kino.rank, Searching inside "Ver más" |
Cursor paging, rows with genre |
browse(), refFilter(), ROWS |
Paging |
| Seasons as separate titles | episodes() |
Seasons |
ids.tmdb + ids.imdb, item fields, adult: true entries |
item(), categories() |
ids.tmdb, 18+ content |
A section with tabs and a hero, Categorías tiles, theme |
section(), categories(); kino-plugin.json |
Section, categories and colors |
Downloads (download) |
kino-plugin.json; resolve() returns a progressive mp4 |
Downloads |
audioTracks, subtitles, durationMs and skip on the Stream |
resolve() |
The Stream rules |
| Labelled and lazy copies, the same file at the other addresses | withCopies(), resolveCopy(), withAddresses() |
Labelled and lazy copies |
Signing every request (signing, signContext, sign, alternateHosts, resolve(ref, { retry })) |
signedStream(), sign(), resolve() |
Signing every request |
live items (apiVersion 2) |
item(), resolve() |
Live channels (apiVersion 2) |
channels: a ref, an inline stream, an M3U list with an XMLTV guide, a resolve: true list, liveSearch, paging |
liveCategories(), channel(), liveChannels(), liveSearch(), resolveListEntry() |
Channels in the En vivo tab, Three recipes |
A User-Agent the channels insist on: headers on a Stream, streamHeaders on a playlist |
agentHeaders() |
Channels in the En vivo tab |
| A guide for its own channels | guide() |
The channel functions |
| Moving saved titles from the server's older ids | migrate(), movedTable() |
Moving saved titles |
meta with logo, ratings and cast (Kino 0.9.51) |
meta() |
Describing other titles |
subtitles with the file hint (Kino 0.9.51) |
subtitles() |
Subtitles for any title |
tracking (apiVersion 7): idempotency by event.id, { skipped: true } |
track() |
Telling a tracker |
segments (apiVersion 7) |
segments() |
Where the intro and credits are |
Its core, line by line, is in the cookbook: The person's own server.
Install it and see it work¶
kinotvapp/kino-plugin-own-server bundles a
reference server with no dependencies
(node server.mjs [--port 8096] [--user ana] [--password s3cr3t] [--live-agent VLC]) whose catalog exercises one
feature per title, so you can install the plugin in Kino and watch every row of the table above work,
with no real server of your own.
A few powers are deliberately not in it, because a server at home never needs them: Widevine DRM
(recipe), a declared host over plain http
(recipe), streams on any server
(recipe), sealed secrets (kino.secret), the
author's signature (Signed plugins), the hidden browser and kino.html.select
(Hidden browser), and key pairs (Key pairs).