stremio

package module
v0.30.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 27, 2026 License: MIT Imports: 14 Imported by: 0

README

module-stremio-addons

Stremio addon-source module for the Mosaic platform — a Go client of the Stremio addon protocol, built against the Mosaic SDK.

It is an optional Mosaic module: its own Go module that imports only sdk and the standard library, compiled into a Mosaic Platform binary and invoked through the Platform's capability registry. It owns no schema; everything it does goes through the published ContentService.

What it does

Given one or more Stremio addon base URLs (configured at runtime as module settings), it consumes the addon protocol as a client and reflects content into a Mosaic library:

  • Metadata (the addon meta resource) creates the Work and its season/episode tree with an external-id source binding.
  • Streams (the addon stream resource) attach a RemoteLocation Part.

The two are independent and driven by what each addon's manifest declares: a metadata-only addon yields a library with no stream Parts, so it can enrich local media without any remote streaming. Movies and TV series are both supported.

Configuration

Addons are user-managed settings, set through the Platform at runtime — a JSON document naming the addons. Either an addon's base URL or the …/manifest.json URL you copy from Stremio works (the stremio:// install scheme is accepted too); they are normalised to the same base.

This module bundles no addon of its own. It used to include Cinemeta so that a fresh install had metadata; that guarantee now belongs to module-cinemeta, a core module that cannot be switched off or misconfigured (ADR 0072). With nothing configured here, this module simply contributes nothing — Mosaic still has metadata and search. A settings document written before the change may still carry disableDefaultAddons; the key is ignored, so nothing needs migrating.

{
  "addons": [
    "https://v3-cinemeta.strem.io/manifest.json",
    "https://torrentio.strem.fun/manifest.json"
  ]
}

Metadata and streams come from different addons. A metadata addon (e.g. Cinemeta) serves the meta resource and builds the library — Works, seasons, episodes — but no playable Parts. A stream addon (e.g. Torrentio) serves the stream resource and adds RemoteLocation Parts. With only a metadata addon, an import is complete but has no streams — that is the meta-only case working as intended, not a failure.

A stream addon on its own does not yet make a searchable library playable. Torrentio declares no catalog, so it produces no search results; the results come from the core metadata module, and an import routes to whichever provider produced the ref — so it materialises metadata with no Parts. Configuring a metadata addon here as well is what makes this module produce refs its own stream addons can attach to. Composing one provider's metadata with another's streams is an open Platform question, not something this module can answer.

Build and test

Everything runs in a container; nothing is built or tested on the host. The gate — gofmt, go build, go vet, go test — is one command:

docker compose -f docker-compose.test.yml run --rm test

Append bash for a shell in the same environment. The SDK is a published dependency resolved from the module proxy (go.mod requires it at a tagged version, no replace), so no sibling checkout is needed to build. The container resolves it exactly as a third party would — which is the point: this module compiles against the published SDK and the standard library and nothing else, and boundary_test.go enforces that by parsing every import. It also reaches real addons over TLS, so the container has network and certificates; if those tests start failing, check that before suspecting the addons.

A note on Stremio

This is an unofficial module. It is not affiliated with, sponsored by, or endorsed by Stremio or SmartCode OOD. "Stremio" is used only nominatively, to describe the addon protocol this module is compatible with. The module contains no Stremio source code; it is an independent implementation of the publicly documented addon protocol, which is itself published under the MIT License.

License

MIT (see LICENSE). This module depends only on the Apache-2.0 Mosaic SDK; it may be compiled into a Mosaic Platform binary under the Platform's Module Linking Exception.

Documentation

Overview

Package stremio is the Stremio addon-source module: the first official optional Module, built exactly as a third party would build one. It is its own Go module (github.com/mosaic-media/module-stremio-addons) importing only the published SDK (contracts/platform/v1) and the standard library, and it is compiled into the Platform binary and invoked through the capability registry (ADR 0007, ADR 0008).

It consumes the Stremio addon protocol as a client: it points at one or more addon HTTP endpoints and, guided by each addon's manifest, uses whatever resources that addon declares. Metadata (the meta resource) creates the Work and its season/episode tree with an external-id source binding; streams (the stream resource) attach a RemoteLocation Part. The two are independent — a meta-only addon yields metadata with no Parts, so a user can enrich local media through Stremio addons without adopting remote streaming. Streams are opt-in by which addons are configured, not by the module.

It owns no schema (ADR 0012): everything it does to the graph goes through ContentService, acting as the Caller the Platform hands it (ADR 0017). Stream locations are snapshotted at import; resolving or transcoding them at play time is a separate, future concern (the Remote Media module), deliberately not here.

Index

Constants

View Source
const (
	// CapabilityID is the id the Platform registers this module under and a
	// caller names to invoke it.
	CapabilityID = "stremio"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type AddonCatalogEntry

type AddonCatalogEntry struct {
	TransportURL string   `json:"transportUrl"`
	Manifest     Manifest `json:"manifest"`
}

AddonCatalogEntry is one installable addon a catalog of addons lists (ADR 0038): its transport (manifest) URL and enough of its manifest to name it.

type AddonInfo

type AddonInfo struct {
	Base         string
	Name         string
	Description  string
	Configurable bool
}

AddonInfo is the display detail for one configured addon (ADR 0038): the name, logo and description from its manifest, whether it is configurable, and the normalised base URL that identifies it. An addon whose manifest cannot be fetched still yields an entry (named by its URL) so a user can remove it.

type AppExtras added in v0.15.0

type AppExtras struct {
	Cast      []Credit `json:"cast"`
	Directors []Credit `json:"directors"`
	Writers   []Credit `json:"writers"`
}

AppExtras is the richer credit block some addons attach alongside the standard meta fields.

type Capability

type Capability struct {
	// contains filtered or unexported fields
}

Capability is the Stremio addon-source module (ADR 0008's capability surface, first populated). It holds only an HTTP client; the addons it sources from are whatever a user adds through its settings, handed in at invocation time (ADR 0021), so one registered module serves whatever each user configures. It owns no schema and imports no Platform internals.

It bundles no addon of its own. It used to bundle Cinemeta so that a fresh install had metadata (ADR 0035); that guarantee now belongs to `module-cinemeta`, a core module that cannot be switched off (ADR 0072), and this module is purely what a user chose.

func New

func New(httpClient *http.Client) *Capability

New builds the capability over an HTTP client (nil for a default). Addon URLs are not supplied here — they arrive as settings on each invocation.

func (*Capability) CatalogItems

CatalogItems lists one collection's entries as virtual candidates the admin can select to publish (ADR 0028). It does not touch the object graph.

func (*Capability) Catalogs

Catalogs lists the collections the configured addons expose (RoleCatalog — the addon `catalog` resource), for the admin collection browser.

func (*Capability) Import

Import materialises the virtual item named by req.Ref — a result a search or catalog browse produced (ADR 0028) — from the configured addons into the Platform. It fetches metadata (required), searches to avoid duplicating, creates the Work with an external-id binding, builds the tree, and attaches a RemoteLocation Part wherever a stream addon serves one. Metadata alone is a complete import; streams are additive.

func (*Capability) Manifest

func (c *Capability) Manifest() v1.Manifest

Manifest is the module's self-declaration, including the provider roles it fills (ADR 0027). It sources metadata and searches and browses catalogs, and resolves streams — the four Stremio addon resources.

func (*Capability) Metadata

Metadata resolves descriptive detail for a ref (RoleMetadata — the addon `meta` resource). It is the enrichment surface: the descriptive fields, not the containment tree, which Import builds where the source's structure is known (ADR 0027).

func (*Capability) Search

Search returns virtual candidates for free text (RoleSearch — the addon `catalog/…/search` resource). No raw id: this is what makes user search in Mosaic work over source content that is not in the library (ADR 0028).

func (*Capability) SettingsUI

SettingsUI renders the module's own settings screen as SDUI (RoleSettingsUI, ADR 0038): add an addon by manifest URL, view the installed addons as cards (name, logo, description) with a way to configure or remove them, and browse a grid of installable addons (the addon_catalog resource) to add without a URL. Every mutating control is an Invoke of the Platform's configureModule command with the complete new settings document, so the Platform stays the one that persists them. The screen is returned as serialised UINode JSON — the SDK stays SDUI-agnostic.

It builds its clients directly rather than through clientFrom, which refuses an empty addon list. That refusal is right for a provider role and wrong here: with no bundled default, no addons configured is what a fresh install looks like, and this screen is the only way out of it.

func (*Capability) Streams

func (c *Capability) Streams(ctx context.Context, req v1.StreamRequest) (v1.StreamResponse, error)

Streams resolves playable locations for a materialised item's ref (RoleStream — the addon `stream` resource). Import snapshots streams at materialise time; this exposes the same resolution as a role other flows can call. It returns an empty response, no error, when no addon serves a stream (the meta-only case).

func (*Capability) Subtitles

Subtitles resolves subtitle tracks for a materialised item's ref (RoleSubtitles — the addon `subtitles` resource, ADR 0037). Like Streams it is a source role; the consumer is a player that does not exist yet, so this is built ahead of it. It returns an empty response, no error, when no addon serves subtitles.

type CatalogDecl

type CatalogDecl struct {
	Type string `json:"type"`
	ID   string `json:"id"`
	Name string `json:"name"`
	// Extra is the modern shape: a list of accepted parameters, each named.
	Extra []ExtraDecl `json:"extra"`
	// ExtraSupported is the older shape: a bare list of accepted parameter
	// names. Both are read so search is detected whichever an addon uses.
	ExtraSupported []string `json:"extraSupported"`
}

CatalogDecl is one entry of a manifest's catalogs array — a collection the addon exposes (Popular, Top). A catalog is typed and addressed by its id; the extra declarations say which query parameters it accepts, the one that matters here being "search".

func (CatalogDecl) GenreOptions added in v0.29.0

func (c CatalogDecl) GenreOptions() []string

GenreOptions returns the values the catalog's `genre` extra accepts, empty when it declares none.

**`genre` is the protocol's one general-purpose narrowing, and addons use it for whatever they like.** Cinemeta puts *years* under it. That is fine here precisely because the values are declared: the options are shown as labels and sent back verbatim, and nothing in Mosaic ever has to decide what they mean.

func (CatalogDecl) RequiresAnArgument added in v0.29.0

func (c CatalogDecl) RequiresAnArgument() bool

RequiresAnArgument reports whether the catalog declares an extra it cannot be listed without.

func (CatalogDecl) SupportsSearch

func (c CatalogDecl) SupportsSearch() bool

SupportsSearch reports whether the catalog accepts a search query.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a minimal client of the Stremio addon protocol — the HTTP contract documented at stremio.github.io/stremio-addon-sdk. It talks to one or more addons and routes each request to an addon whose manifest declares the needed resource and type. A meta-only addon therefore serves metadata and a stream addon serves streams, and neither resource depends on the other.

func NewClient

func NewClient(httpClient *http.Client, addonURLs ...string) *Client

NewClient builds a client over the given addon URLs. A nil httpClient gets a default with a sane timeout. Each URL is normalised to the addon's base URL (see normaliseAddonURL) so that either the base URL or the manifest URL a user copies from Stremio works. Manifests are not fetched here; they are fetched on first use so construction stays offline.

func (*Client) AddonCatalog

func (c *Client) AddonCatalog(ctx context.Context) ([]AddonCatalogEntry, error)

AddonCatalog fetches the union of every configured addon's addon catalogs — the installable addons a user can add without pasting a manifest URL. Addons that do not serve the resource contribute nothing; a fetch that errors is skipped rather than failing the whole browse. De-duped by transport URL.

func (*Client) CatalogItems

func (c *Client) CatalogItems(ctx context.Context, typ, id, genre string, skip int) ([]MetaPreview, bool, error)

CatalogItems lists one catalog's entries, from the first addon whose manifest declares a catalog of that type and id. skip pages through a large catalog (0 for the first page), and genre narrows it when non-empty — the caller has already checked the value against what that catalog declared. It returns nil, no error, when no configured addon declares the catalog.

It also reports whether another page exists. That is the **weaker** of the two statements SDK v0.25.0 describes — a full page rather than a reported total — and it is still the provider's to make, because only the provider knows the page size. Its cost is one empty fetch at the end of an exactly-full catalog.

func (*Client) Catalogs

func (c *Client) Catalogs(ctx context.Context) ([]CatalogDecl, error)

func (*Client) InstalledAddons

func (c *Client) InstalledAddons(ctx context.Context) []AddonInfo

InstalledAddons returns display detail for each configured addon, in the client's effective (deduped, default-first) order. Manifests are fetched and cached; an unreachable addon is named by its URL rather than dropped, so it stays removable.

func (*Client) Meta

func (c *Client) Meta(ctx context.Context, typ, id string) (Meta, bool, error)

Meta fetches metadata for a content id from the first configured addon whose manifest serves the meta resource for the type. It returns ok=false, no error, when no configured addon serves meta for it.

func (*Client) MetaMerged added in v0.13.0

func (c *Client) MetaMerged(ctx context.Context, typ, id string) (Meta, MetaProvenance, bool, error)

MetaMerged asks every addon that serves `meta` and combines their answers by the tiered rule above. It also reports which addon supplied each tier, so an odd-looking detail screen is an answerable question rather than a guess.

func (*Client) Search

func (c *Client) Search(ctx context.Context, query string) ([]MetaPreview, error)

Search queries every search-capable catalog across every configured addon and returns the union, de-duplicated by content id. A catalog that errors on the search query is skipped rather than failing the whole search, so one broken addon does not blank the results. It returns nil, no error, when nothing matches or no addon offers search.

func (*Client) Stream

func (c *Client) Stream(ctx context.Context, typ, id string) (Stream, bool, error)

func (*Client) Streams added in v0.10.0

func (c *Client) Streams(ctx context.Context, typ, id string) ([]Stream, error)

Stream fetches the best stream for a content id (a movie id, or an episode id of the form tt...:season:episode) from the first addon whose manifest serves the stream resource for the type. Stremio ranks streams best-first, so the first entry is taken. It returns ok=false, no error, when no configured addon serves a stream — the metadata-only case. Streams fetches every stream a configured addon offers for a content id.

Import stores all of them (ADR 0049): a candidate never expires, so keeping the set costs nothing to keep correct, and it is what lets a consumer choose a release the calling client can actually play instead of being handed whatever happened to be first. Only the resolved URL is perishable, and that is cached separately.

func (*Client) Subtitles

func (c *Client) Subtitles(ctx context.Context, typ, id string) ([]Subtitle, bool, error)

Subtitles fetches subtitle tracks for a content id (a movie id or an episode id of the form tt...:season:episode) from the first addon whose manifest serves the subtitles resource for the type. It returns ok=false, no error, when no configured addon serves subtitles (ADR 0037).

type Credit added in v0.15.0

type Credit struct {
	Name      string `json:"name"`
	Character string `json:"character"`
	Photo     string `json:"photo"`
}

Credit is one person with the two things `links` cannot express: the character they played, and a photograph of them.

type ExtraDecl

type ExtraDecl struct {
	Name string `json:"name"`
	// Options are the values the parameter accepts, when the addon says. An
	// extra with none is a free-text parameter (`search`) or one whose values
	// the caller must already hold (`lastVideosIds`); either way it cannot back
	// a control built from a declared list, so it is not offered as a filter.
	Options []string `json:"options"`
	// IsRequired is whether a listing *cannot be fetched* without this
	// parameter. It is read so a catalog that needs an argument a browse surface
	// cannot invent is skipped rather than rendered as a row that answers
	// nothing.
	IsRequired bool `json:"isRequired"`
}

ExtraDecl is one accepted catalog parameter.

type Link struct {
	Name     string `json:"name"`
	Category string `json:"category"`
	URL      string `json:"url"`
}

Link is one entry of a meta's `links` array — the modern Cinemeta shape that carries cast, directors, writers and genres as categorised references.

type Manifest

type Manifest struct {
	ID          string         `json:"id"`
	Name        string         `json:"name"`
	Version     string         `json:"version"`
	Description string         `json:"description"`
	Resources   []ResourceDecl `json:"resources"`
	Types       []string       `json:"types"`
	Catalogs    []CatalogDecl  `json:"catalogs"`
	// AddonCatalogs are catalogs of *other addons* this addon exposes (the
	// `addon_catalog` resource) — how a user discovers installable addons without
	// a manifest URL (ADR 0038).
	AddonCatalogs []CatalogDecl      `json:"addonCatalogs"`
	BehaviorHints addonBehaviorHints `json:"behaviorHints"`
}

Manifest is the subset of a Stremio addon manifest this client reads.

type Meta

type Meta struct {
	ID         string `json:"id"`
	Type       string `json:"type"`
	Name       string `json:"name"`
	Poster     string `json:"poster"`
	Background string `json:"background"`
	// LandscapePoster is wide key art an addon proxying a real artwork database
	// returns beside the portrait poster. It is not part of the base Stremio meta
	// shape — Cinemeta has no such field — so it is empty for sources that do not
	// carry it, and translated here rather than left for the Platform to learn
	// (ADR 0051).
	LandscapePoster string   `json:"landscapePoster"`
	Description     string   `json:"description"`
	ReleaseInfo     string   `json:"releaseInfo"`
	Genres          []string `json:"genres"`
	// ImdbRating is Cinemeta's rating, a string ("8.0") in its API.
	ImdbRating string `json:"imdbRating"`
	// Runtime is a display string whose format varies ("120 min").
	Runtime string `json:"runtime"`
	// Cast is the legacy top-level cast (names). Modern Cinemeta also puts cast
	// in Links with category "Cast"; both are read (see castNames).
	Cast   []string `json:"cast"`
	Links  []Link   `json:"links"`
	Videos []Video  `json:"videos"`
	// AppExtras is where an addon proxying a real metadata database puts credits
	// that the base Stremio meta shape has no room for. `links` carries cast as
	// a name and a search URL — no character, no photo — so an addon with better
	// data has nowhere to put it and puts it here instead.
	AppExtras AppExtras `json:"app_extras"`
}

Meta is the subset of a meta response this client reads. For a series, Videos lists the episodes, each carrying its season and episode number. Logo/ImdbRating/Runtime/Cast/Links back the rich detail surface (ADR 0034); Cinemeta provides them all — the module simply decoded none of them before.

type MetaPreview

type MetaPreview struct {
	ID          string `json:"id"`
	Type        string `json:"type"`
	Name        string `json:"name"`
	Poster      string `json:"poster"`
	ReleaseInfo string `json:"releaseInfo"`
}

MetaPreview is the subset of a catalog/search meta entry this client reads — the lightweight item shape a catalog returns, distinct from the full Meta a meta lookup returns. ReleaseInfo is a year ("2017") or a range ("2008-2013").

type MetaProvenance added in v0.14.0

type MetaProvenance struct {
	Identity     string
	Artwork      string
	Contributors []string
}

MetaProvenance records which addon supplied each tier of a merged record.

It costs almost nothing and turns "why does this look odd" into something answerable. Metadata assembled from several sources is exactly the kind of thing that is hard to debug after the fact without it.

type ResourceDecl

type ResourceDecl struct {
	Name       string
	Types      []string
	IDPrefixes []string
}

ResourceDecl is one entry of a manifest's resources array. Stremio allows each entry to be either a bare string ("meta") or an object carrying its own types and id prefixes; this unmarshals both shapes.

func (*ResourceDecl) UnmarshalJSON

func (r *ResourceDecl) UnmarshalJSON(b []byte) error

UnmarshalJSON accepts either a bare string or the object form.

type Stream

type Stream struct {
	Name          string        `json:"name"`
	Title         string        `json:"title"`
	Description   string        `json:"description"`
	URL           string        `json:"url"`
	InfoHash      string        `json:"infoHash"`
	FileIdx       int           `json:"fileIdx"`
	BehaviorHints behaviorHints `json:"behaviorHints"`
}

Stream is the subset of a stream object this client reads. A stream is either a direct URL or a torrent identified by InfoHash. Description/ BehaviorHints carry the release detail (quality, size) addons pack into the title, which parseStreamMeta teases back out (ADR 0037).

func (Stream) Ref

func (s Stream) Ref() string

Ref is the location reference to store for this stream: the direct URL when present, otherwise a magnet URI built from the torrent info hash. It is empty when the stream carries neither.

type Subtitle

type Subtitle struct {
	ID   string `json:"id"`
	URL  string `json:"url"`
	Lang string `json:"lang"`
}

Subtitle is the subset of a subtitles response entry this client reads (ADR 0037): a track's language and the file URL.

type Video

type Video struct {
	ID       string `json:"id"`
	Title    string `json:"title"`
	Name     string `json:"name"`
	Season   int    `json:"season"`
	Episode  int    `json:"episode"`
	Overview string `json:"overview"`
	// Thumbnail is a still image URL for the episode.
	Thumbnail string `json:"thumbnail"`
	// Released is the episode's air date (an ISO datetime in Cinemeta).
	Released string `json:"released"`
}

Video is one episode of a series' meta. Overview/Thumbnail/Released back the episode preview (ADR 0034); Cinemeta provides them on each video entry.

func (Video) EpisodeTitle

func (v Video) EpisodeTitle() string

EpisodeTitle is the video's title, falling back to its name and then a generated label, so an item always has something to show.

Directories

Path Synopsis
cmd
module-stremio-addons command
Command module-stremio-addons runs this module as its own process, for a Platform that hosts it out of process (ADR 0064, ADR 0077).
Command module-stremio-addons runs this module as its own process, for a Platform that hosts it out of process (ADR 0064, ADR 0077).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL