Nuvio scrapers¶
Kino can install the scrapers of a Nuvio provider repository without anyone writing a Kino plugin: it converts the chosen scraper into a Kino plugin on the device, at install time. This page says how people add them, what the conversion does and where it stops. You don't need any of it to write a plugin of your own; it matters if you maintain a Nuvio repository, or you want to know why a converted plugin behaves differently from a hand-written one.
How people add them¶
- In Kino, Ajustes ▸ Plugins (on the TV, the "Plugins" button on Home also gets there), "Agregar
plugin", and type the repository's address,
owner/repo, exactly as for a Kino plugin. The pasted address can also be agithub.com/owner/repoURL, a/tree/<ref>/<folder>one, or araw.githubusercontent.com/owner/repo/<ref>/.../manifest.jsonURL (alsogithub.com/.../blob/...or/raw/...to a.jsonfile): Kino takes the folder that file is in. Any other file is refused. - Kino reads
manifest.jsonat the repository root. When it is Nuvio's own format (an object with ascrapersarray) the address is a Nuvio repository; otherwise Kino treats it as a Kino plugin (kino-plugin.json). If the default branch has amanifest.jsonthat is not in that format (some repositories keep a template there), Kino also tries themainand then themasterbranch. - A full-screen picker lists every scraper of the manifest, with its logo, types, language, version and author, and filters by type (Todas, Películas, Series, Anime) and language. Each card says "Agregar", "Instalado", or "No disponible" (a scraper the manifest disables, or disables on Android) or "No compatible" (a scraper of peer-to-peer sources, with or without debrid: "Kino no admite scrapers de torrents, ni siquiera con debrid", from Kino 0.9.51). A repository with nothing installable says "Este repositorio de Nuvio no tiene scrapers instalables en Android".
- "Agregar" converts that scraper and opens the usual consent sheet; after "Instalar" the picker stays open so the person can add another one. Each scraper becomes its own plugin, listed in Ajustes ▸ Plugins like any other.
The picker notes that the scrapers are converted from Nuvio and that their original code is under the GPL-3.0 license; the plugin's description says the same ("Convertido desde el plugin de Nuvio …; código original GPL-3.0").
What the conversion builds¶
The scraper's own JavaScript is kept byte for byte, wrapped with a compatibility layer and a small adapter, and installed with a generated manifest:
apiVersion6 (4 before Kino 0.9.51),version1.<converter revision>.0(today1.4.0, see Updates), capabilitiessearch,episodes,resolveanddownload.- The plugin's
settings, when the scraper hasonSettings(Kino 0.9.51): see The scraper's settings. - The types the scraper serves come from its
supportedTypes, with the usual spellings folded together:movie,movies,film,filmsare movies;tv,series,show,showsare series;animeis anime (any case). A scraper declaring["movie", "series"]serves series too. hosts: detected automatically from the scraper's code (and from a remote domain list it names, when it has one), withapi.themoviedb.orgalways first. A manifest caps them at 20: over that, the addresses that look like the scraper's own site go first, and the description warns "se detectaron más de 20 dominios; algunos quedaron fuera". A scraper whose code names no domain at all cannot be converted ("No encontré ningún dominio en el código de …")."streamHosts": "any": its movies and episodes may play from any public server (the rule)."fetchHosts": "any": itskino.fetchmay reach any public server, without a question per host. Local names, private, loopback and link-local addresses, and names that resolve into the home network stay refused, on every redirect hop. This field is honoured only for these converted installs; in a hand-written plugin it has no effect (why).
So the consent sheet of a converted scraper shows, in red, "Puede reproducir video desde cualquier servidor que indique" and "Puede conectarse a cualquier servidor de internet", plus "Puede descargar videos para verlos sin conexión". Nothing of it runs before the person accepts.
How a converted scraper behaves¶
Nuvio scrapers have no catalogue and no text search: they only answer "streams for this TMDB id". So the adapter works from TMDB:
- No Home rows. The plugin shows up in search results, never as Home rows.
searchwith a TMDB id answers one item, for the TMDB id Kino is looking for, with TMDB's poster, backdrop, year and synopsis when TMDB answers in time. A scraper answers only for the types its manifest declares: one without movies (or without series) is not offered as a source for them.searchwithout a TMDB id (a typed search, e.g. on the TV) asks TMDB's own search for the text instead (/search/multi, or only movies or only series when that is all the scraper serves or Kino asked for a series) and answers up to 10 matches, ranked by how close their title is to the query, ties by TMDB popularity. The scraper itself is not called until the person picks one. If TMDB fails, the search answers nothing rather than an error.episodeslists the seasons and episodes from TMDB, without season 0 (specials) and without episodes that have not aired yet.resolvecalls the scraper'sgetStreamsexactly as Nuvio does and keeps only thehttp/httpscopies (Kino has no BitTorrent client). From Kino 0.9.51 every playable copy is offered: the first one plays and up to 8 more go as labelled alternative copies in the Servidor menu, in this order: video files before embed pages, and within that 1080p first, then 720p, then anything else, and 2160p/4K last (most phones and TVs here can't decode 4K HEVC). The label is the quality with the server's name ("1080p · Server X"), so two copies of one quality read apart. A Kodi-style address,url|User-Agent=…&Referer=…, is split: what follows the|becomes request headers. When nothing is left the person reads why: "sin resultados", "solo enlaces P2P", or "error del scraper: …" with what the scraper logged.- Downloads work on phones like for any plugin with
download(Downloads), under the same host rules as playing.
Limits that differ from a hand-written plugin¶
| What | Converted Nuvio scraper | Hand-written plugin |
|---|---|---|
resolve time |
75 s (the player counts the wait on screen) | 20 s |
kino.fetch requests per call |
250 | 60 |
Hosts kino.fetch may reach |
any public host (fetchHosts) |
hosts, typed servers, and hosts approved one by one |
| Where the video may be | any public host (streamHosts) |
hosts, unless streamHosts or the broad video permission |
Everything else -- memory, body sizes, the home-network refusals, the other time limits -- is the same.
The runtime a scraper gets¶
The compatibility layer rebuilds, on top of kino, what a Nuvio scraper expects: CommonJS
module/exports/require, a browser-shaped fetch, axios, process.env, global,
setTimeout/clearTimeout, AbortController/AbortSignal, require("crypto") (Node's, for what
scrapers use), the browser Web Crypto API (crypto.subtle, crypto.getRandomValues,
crypto.randomUUID), TMDB_API_KEY, and the real cheerio-without-node-native, crypto-js and
Buffer, bundled only when the scraper's code needs them.
From Kino 0.9.51 (Nuvio compatibility v2) also:
- Nuvio's globals:
window,selfandSCRAPER_ID;getStreamsis found onmodule.exports,exports.getStreams,defaultor as a global; regenerator-based async code works, and so does arequireinside atryor anif. - A Node subset:
path,url,util,events,querystring,timers,bufferandhttp/https/undici(overkino.fetch), plussetInterval,setImmediateandqueueMicrotask.fs,child_process,net,os,streamand the like load as empty modules: every member reads asundefined, so a scraper's own feature check falls back tofetch, and calling one anyway is aTypeError. - Multi-file scrapers: the sibling files it
requires are read from the same repository, through the same fetcher (no new destination). At most 16 files and 1 MiB in total;../inside the repository is fine, a path that escapes the repository (also%-encoded) is refused. A sibling that throws on load is retried. - Timing: 30 s per request. A timer a scraper leaves running is cleared once
getStreamssettles, so the call does not wait for it.
A require of anything not on that list and not a sibling file fails with "Nuvio compat:
require('…') was not bundled with this scraper".
Kino's TMDB key is never written into the converted plugin's code: TMDB_API_KEY holds a fixed
marker, and Kino puts the real key in its place only in https requests to api.themoviedb.org
(the same sealed-secret mechanism a plugin's own keys use). A request that
carries the marker anywhere else is refused before it leaves, and the key is blanked out of every
answer, error and log the plugin sees.
That marker is for converted scrapers only. A plugin you write yourself asks TMDB through
kino.tmdb (Kino 0.9.53): Kino's own key behind Kino's cache and limits, the person's key only when
Kino's fails, and no key in your code.
All of that exists only inside a converted scraper. A plugin you write gets the plain Kino engine: none of those globals (Limits and engine quirks). The same goes for the async-helper workaround of the rejection trap.
The scraper's settings¶
From Kino 0.9.51, a scraper's onSettings becomes the converted plugin's
settings form, in its own Ajustes tab, and what the person picks syncs across their
devices like any plugin setting. The scraper gets it in SCRAPER_SETTINGS, under its own keys. If the
scraper's form does not fit Kino's limits, the consent sheet says "Algunos ajustes del scraper no caben
y quedaron fuera". A setting that asks for a debrid account makes the scraper refused (see below).
What is refused¶
Scrapers of peer-to-peer sources, with or without debrid (Kino 0.9.51): Kino does not carry torrents, so the card says "No compatible" and "Kino no admite scrapers de torrents, ni siquiera con debrid"; it is judged like a Stremio peer-to-peer addon. One installed before this is switched off by its next update check.
Updates¶
A converted plugin's version is 1.<converter revision>.0: it moves only when Kino's converter
itself changes (today 1.4.0), because Nuvio's own version fields are not reliable. To find
updates Kino does not compare versions: "Buscar actualizaciones" (and the background check) re-runs
the whole conversion from the repository and compares the resulting code and manifest with the
installed ones, so a change in the scraper is found even though the version stays the same. A change that only touches code
installs by itself; one that adds hosts or a permission waits for the person's approval, like any
update. A plugin converted by an older Kino asks for approval once for what
newer conversions add (fetchHosts, downloads).
For Nuvio repository maintainers¶
- Keep
manifest.jsonat the root of the default branch, withscrapers[]entries that haveid,name,filename, and ideallysupportedTypes,contentLanguage,version,author,descriptionandlogo: the picker shows and filters by them. - Use
enabled: falseordisabledPlatforms: ["android"]for scrapers that should not be offered. - Return direct
http/httpsvideo addresses when you have them: they go before embed pages. Give every copy aqualityand aname: they are its label in the Servidor menu. - Write the site's own address as a literal in the code (or in a remote domain list): that is how Kino finds the hosts to declare.