- Understanding Catalogs (searching, filtering, paginating)
- Understanding Cinemeta
- Adding Stream Results to Cinemeta Items
- Getting Metadata from Cinemeta
- Resolving Movie / Series names to IMDB ID
- Using User Data in Addons
- Using Deep Links in Addons
- Proxying Other Addons
- Crawler (Scraping) Addons
The catalog
resource in Stremio addons can be used to:
- show one or more catalogs in the Board and Discover pages, these responses can also be filtered and paginated
- show search results from catalogs
Let's first look at how catalog
is declared in the manifest:
{
"resources": ["catalog"],
"catalogs": [
{
"id": "testcatalog",
"type": "movie"
}
]
}
This is normally all you'd need to make a standard catalog, but it won't support filtering, paginating and it won't be searchable.
To state that your catalog supports searching, you'd need to set it in the extra
property:
catalogs: [
{
"id": "testcatalog",
"type": "movie",
"extra": [
{
"name": "search",
"isRequired": false
}
]
}
]
But then, what if you want your catalog to support only search (as in, not show in the Board or Discover pages at all)?
Then you'd need to state that your catalog supports only searching, and you can do that with:
catalogs: [
{
"id": "testcatalog",
"type": "movie",
"extra": [
{
"name": "search",
"isRequired": true
}
]
}
]
Once you've set search
in extra
, your catalog handler will receive args.extra.search
as the search query (if it is a search request), so here's an example of a search response:
const meta = {
id: 'tt1254207',
name: 'Big Buck Bunny',
releaseInfo: '2008',
poster: 'https://image.tmdb.org/t/p/w600_and_h900_bestv2/uVEFQvFMMsg4e6yb03xOfVsDz4o.jpg',
posterShape: 'poster',
banner: 'https://image.tmdb.org/t/p/original/aHLST0g8sOE1ixCxRDgM35SKwwp.jpg',
type: 'movie'
}
builder.defineCatalogHandler(function(args) {
return new Promise(function(resolve, reject) {
if (args.id == 'testcatalog') {
// this is a request to our catalog id
if (args.extra.search) {
// this is a search request
if (args.extra.search == 'big buck bunny') {
// if someone searched for "big buck bunny" (exact match)
// respond with our meta item
resolve({ metas: [meta] })
} else {
reject(new Error('No search results found'))
}
} else {
// this is a standard catalog request
// just respond with our meta item
resolve({ metas: [meta] })
}
} else {
reject(new Error('Unknown catalog request'))
}
})
})
Maybe you would like your catalog to be filtered by genre
, in this case, we'll set that in the catalog definition:
catalogs: [
{
"id": "testcatalog",
"type": "movie",
"extra": [
{
"name": "genre",
"options": [ "Drama", "Action" ],
"isRequired": false
}
]
}
]
Now we'll receive genre
in our catalog handler:
const meta = {
id: 'tt1254207',
name: 'Big Buck Bunny',
releaseInfo: '2008',
poster: 'https://image.tmdb.org/t/p/w600_and_h900_bestv2/uVEFQvFMMsg4e6yb03xOfVsDz4o.jpg',
posterShape: 'poster',
banner: 'https://image.tmdb.org/t/p/original/aHLST0g8sOE1ixCxRDgM35SKwwp.jpg',
type: 'movie'
}
builder.defineCatalogHandler(function(args) {
return new Promise(function(resolve, reject) {
if (args.id == 'testcatalog') {
// this is a request to our catalog id
if (args.extra.genre) {
// this is a filter request
if (args.extra.genre == "Action") {
// in this example we'll only respon with our
// meta item if the genre is "Action"
resolve({ metas: [meta] })
} else {
// otherwise with no meta item
resolve({ metas: [] })
}
} else {
// this is a standard catalog request
// just respond with our meta item
resolve({ metas: [meta] })
}
} else {
reject(new Error('Unknown catalog request'))
}
})
})
If we want our catalogs to be paginated, we can use skip
as follows:
catalogs: [
{
"id": "testcatalog",
"type": "movie",
"extra": [
{
"name": "skip",
"isRequired": false
}
]
}
]
Optionally, we can also set the steps in which the catalog will request the next items, for example:
catalogs: [
{
"id": "testcatalog",
"type": "movie",
"extra": [
{
"name": "skip",
"options": ["0", "100", "200"],
"isRequired": false
}
]
}
]
This is not a requirement though, as if we don't set options
Stremio will request skip
once it comes to the end of your catalog.
Here's an example of using skip
:
// we only have one meta item
const meta = {
id: 'tt1254207',
name: 'Big Buck Bunny',
releaseInfo: '2008',
poster: 'https://image.tmdb.org/t/p/w600_and_h900_bestv2/uVEFQvFMMsg4e6yb03xOfVsDz4o.jpg',
posterShape: 'poster',
banner: 'https://image.tmdb.org/t/p/original/aHLST0g8sOE1ixCxRDgM35SKwwp.jpg',
type: 'movie'
}
const metaList = []
// but we'll make an array that includes our meta 60 times
for (let i = 0; i++; i < 60) {
metaList.push(meta)
}
builder.defineCatalogHandler(function(args) {
return new Promise(function(resolve, reject) {
if (args.id == 'testcatalog') {
// we'll slice our meta list using
// skip as the starting point
const skip = args.extra.skip || 0
resolve({ metas: metaList.slice(skip, skip + 20) })
} else {
reject(new Error('Unknown catalog request'))
}
})
})
Cinemeta is the primary addon that Stremio uses to show Movie, Series and Anime items. Other addons can choose to create their own catalogs of items or respond with streams to the Cinemeta items.
Cinemeta uses IMDB IDs for their metadata, to understand it's pattern:
tt0111161
is the meta ID (and video ID) of a moviett3107288
is the meta ID of a series, andtt3107288:1:1
is the video ID for season 1, episode 1 of the series with thett3107288
meta ID
To add only stream results to Cinemeta items, you will first need to state that your addons id prefix is tt
(as for IMDB IDs).
Add these to your manifest:
resources: ["stream"]
idPrefixes: ["tt"]
Now here is an example of returning stream responses for Cinemeta items:
builder.defineStreamHandler(function(args) {
return new Promise(function(resolve, reject) {
if (args.type === 'movie' && args.id === 'tt1254207') {
// serve one stream for big buck bunny
const stream = { url: 'http://distribution.bbb3d.renderfarming.net/video/mp4/bbb_sunflower_1080p_30fps_normal.mp4' }
resolve({ streams: [stream] })
} else if (args.type === 'series' && args.id === 'tt8633518:1:1') {
// return one stream for the series Weird City, Season 1 Episode 1
// (free Youtube Originals series)
const stream = { id: 'yt_id::fMnq5v8yZp4' }
resolve({ streams: [stream] })
} else {
reject(new Error('No streams available for: ' + args.id))
}
})
})
There might be cases where you would need metadata based on IMDB ID. To do this, you will need both IMDB ID and the type of the item (either movie
or series
).
Because Cinemeta is also an addon, you can request the metadata from it.
Here is an example using needle
to do a HTTP request to Cinemeta for metadata:
var needle = require('needle')
// we will get metadata for the movie: Big Buck Bunny
var itemType = 'movie'
var itemImdbId = 'tt1254207'
needle.get('https://v3-cinemeta.strem.io/meta/' + itemType + '/' + itemImdbId + '.json', function(err, resp, body) {
if (body && body.meta) {
// log Big Buck Bunny's metadata
console.log(body.meta)
}
})
What if you have a movie or series name, but you need it's IMDB ID?
We recommend using name-to-imdb in this case, and it's really easy to use:
var nameToImdb = require("name-to-imdb");
nameToImdb({ name: "south park" }, function(err, res, inf) {
console.log(res); // prints "tt0121955"
console.log(inf); // inf contains info on where we matched that name - e.g. metadata, or on imdb
})
Also setting the type
and year
in the request helps on ensuring that the IMDB ID that is returned is correct.
The Addon SDK now supports user data, this part of the docs will remain here as it is valid for use with the Express module.
This example does not use the Stremio Addon SDK, it uses Node.js and Express to serve replies.
User data is passed in the Addon Repository URL, so instead of users installing addons from the normal manifest url (for example: https://www.mydomain.com/manifest.json
), users will also need to add the data they want to pass to the addon in the URL (for example: https://www.mydomain.com/c9y2kz0c26c3w4csaqne71eu4jqko7e1/manifest.json
, where c9y2kz0c26c3w4csaqne71eu4jqko7e1
could be their API Authentication Token)
Simplistic Example:
const express = require('express')
const addon = express()
addon.get('/:someParameter/manifest.json', function (req, res) {
res.send({
id: 'org.parameterized.'+req.params.someParameter,
name: 'addon for '+req.params.someParameter,
resources: ['stream'],
types: ['series'],
})
})
addon.get('/:someParameter/stream/:type/:id.json', function(req, res) {
// @TODO do something depending on req.params.someParameter
res.send({ streams: [] })
})
addon.listen(7000, function() {
console.log('http://127.0.0.1:7000/[someParameter]/manifest.json')
})
This is not a working example, it simply shows how data can be inserted by users in the Addon Repository URL so addons can then make use of it.
For working examples, you can check these addons:
- IMDB Lists
- IMDB Watchlist
- Jackett Addon for Stremio (community built)
Another use case for passing user data through the Addon Repository URL is creating proxy addons. This case presumes that the id of a different addon is sent in the Addon Repository URL, then the proxy addon connects to the addon of which the id it got, requests streams, passes the stream url to some API (for example Real Debrid, Premiumize, etc) to get a different streaming url that it then responds with for Stremio.
This guide extends Using User Data in Addons by explaining how to create an Addon Configuration Page.
In order to allow Addons to request user data, you will first need to create a web page on the /configure
path of your addon and set manifest.behaviorHints.configurable
to true
. (you can also set manifest.behaviorHints.configurationRequired
to true
if your addon cannot be installed without user data)
The /configure
web page should include a form with all required data, the form data should be set within the Addon Repository URL (as explained in the Using User Data in Addons) section.
An "Install Addon" button should be available on the page that will use the stremio://
protocol, for example, if your Addon Repository URL is https://my.addon.com/some-user-data/manifest.json
, the "Install Addon" button should point to stremio://my.addon.com/some-user-data/manifest.json
. (the stremio://
protocol links will open or focus the Stremio app with a prompt to install the addon)
Stremio supports deep links, such links can also be used in addons to link internally to Stremio.
First, set the stream
resource in your manifest:
resources: ["stream"]
Here's an example:
// this responds with one stream for the Big Buck Bunny
// movie, that if clicked, will redirect Stremio to the
// Board page
builder.defineStreamHandler(function(args) {
return new Promise(function(resolve, reject) {
if (args.type === 'movie' && args.id === 'tt1254207') {
// serve one stream for big buck bunny
const stream = { externalUrl: 'stremio:///board' }
resolve({ streams: [stream] })
} else {
reject(new Error('No streams found for: ' + args.id))
}
})
})
Stremio addons use a HTTP server to handle requests and responses, this means that other addons can also request their responses.
This can be useful for many reasons, a guide on how this can be done is included in the readme of the IMDB Watchlist Addon which proxies the IMDB Lists addon to get the IMDB List for a particular IMDB user.
IMDB Watchlist only proxies the catalog from IMDB Lists, to proxy other resources you can use the same pattern as IMDB Watchlist does, and check the endpoints and patterns for other resources on the Protocol Documentation page.
Scraping HTML pages presumes downloading the HTML source of a web page in order to get specific data from it.
A guide showing a simplistic version of doing this is in the readme of the IMDB Watchlist Addon. The addon uses needle to request the HTML source and cheerio to start a jQuery instance in order to simplify getting the desired information.
Cheerio is not the only module that can help with crawling / scraping though, other modules that can aid in this: jsdom, xpath, etc