aiostreams

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Jul 24, 2026 License: MIT Imports: 10 Imported by: 0

README

module-aiostreams

A dedicated stream provider for the Mosaic platform, sourcing from AIOStreams.

It is an optional Mosaic module: its own Go module importing only sdk and sdui, compiled into a Mosaic Platform binary and invoked through the Platform's capability registry. It owns no schema.

Why this exists beside module-stremio-addons

module-stremio-addons can source from any Stremio addon, which is both its value and its problem. The addon ecosystem is community-made and unreviewed: nothing about an addon a user pastes in can be guaranteed — not its behaviour, not its reachability, not what it does with the request — and Mosaic has no access-control story that makes an open addon list safe to recommend. An install that just wants streams should not have to adopt that whole surface to get them.

This module talks to exactly one upstream. AIOStreams is itself an aggregator — it searches many sources, applies the user's filters and sorting, and returns one list — so the breadth is unchanged while the trust decision collapses from "every addon a user might add" to "one instance URL, shown in settings".

The two coexist. Both fill the stream role, and the Platform asks stream providers in module-id order, so aiostreams is asked before stremio and its results are the ones attached.

What it does, and what it deliberately does not

It fills stream and subtitles, plus its own settings screen. It fills no metadata, search or catalog role, so it never puts a title into the library. Titles are added from a metadata module (Cinemeta, TMDB) and this fills in what plays, through the Platform's stream-enrichment pass (ADR 0073).

It addresses content by IMDB id, which is what Stremio-protocol sources key on, composing an episode as <series>:<season>:<episode> at this boundary. A title whose IMDB id is unknown is declined — with no error, because being asked about content it cannot address is normal, not a failure.

Configuration

One setting: the instance.

{ "instanceUrl": "https://aiostreams.elfhosted.com/stremio/<profile>/<secret>/manifest.json" }

Set through the module's settings screen at runtime (ADR 0021), not env vars and not Platform config. The base URL, the /manifest.json URL, the /configure page URL and a stremio:// install link are all accepted and normalise to the same base — with the profile segments preserved, because those are the configuration.

The default is a host, not a working configuration. With nothing set the module points at the public instance ElfHosted runs, https://aiostreams.elfhosted.com/stremio. AIOStreams serves nothing until a profile exists: that manifest declares configurationRequired and an empty resources array, so stream requests against it correctly return nothing. The settings screen says so in as many words and links straight to the instance's configuration page; a user creates a profile there and pastes back the manifest URL it gives them.

Defaulting to it anyway is the point — a fresh install has a named instance and a working "Configure" button rather than an empty box and a URL to go and find.

Everything else — which sources to aggregate, which debrid service, how to filter, sort and format results — is configured on the instance, not here. Mirroring any of it would be two places to change one setting.

The instance URL is a credential. Its path carries the profile id and its encrypted password, so anyone holding the URL holds that user's whole configuration, including whatever debrid key built it. The settings screen masks those segments, and telemetry records the instance host and never the URL.

Build and test

Everything runs in a container; nothing is built or tested on the host.

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

That is gofmt, go build, go vet and go test. Append bash for a shell in the same environment.

The tests are hermetic — a fake AIOStreams over httptest, reached by putting the fake's URL in the module's settings, which needs no seam in the production type because the instance is a setting. There is no instance CI could point at that is not somebody's, and a profile URL is a credential no CI should hold.

A note on AIOStreams and ElfHosted

This is an unofficial module. It is not affiliated with, sponsored by or endorsed by AIOStreams, its author, or ElfHosted. It contains no AIOStreams source code; it is a client of the publicly documented Stremio addon protocol that AIOStreams speaks. Mosaic bundles no content and indexes nothing — what a configured instance returns is the instance owner's business.

License

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

Documentation

Overview

Package aiostreams is the AIOStreams module: a **dedicated stream provider** for one named upstream, as distinct from `module-stremio-addons`, which is a host for whatever addons a user pastes in.

The distinction is the reason this module exists. The addon module can source from any Stremio addon, and that is its value and its problem: the addon ecosystem is community-made, unreviewed, and unbounded, so nothing about a configured addon can be guaranteed — not its behaviour, not its reachability, not what it does with the request. An install that wants streams should not have to adopt that whole surface to get them. This module talks to exactly one upstream, AIOStreams, which is itself an aggregator: the breadth stays, and the trust decision collapses from "every addon a user might add" to "one instance, named in settings".

It fills the stream and subtitle roles and nothing else (ADR 0027, ADR 0037). It sources no metadata, contributes no search results and declares no catalogs, so it never puts a title into the library — it fills in what plays for titles some other module described (ADR 0073). An install with only this module and a metadata module is exactly the intended shape.

Its own Go module (github.com/mosaic-media/module-aiostreams) importing only the published SDK, the published SDUI contract and the standard library, and an anti-corruption layer for AIOStreams' dialect of the Stremio addon protocol (ADR 0051): the addressing, the release-text conventions and the instance URL shape stop here, and the Platform learns none of them.

Index

Constants

View Source
const (
	// CapabilityID is the id the Platform registers this module under and a
	// caller names to reach its settings screen.
	//
	// It sorts before "stremio", and that has a consequence worth stating rather
	// than discovering: the Platform resolves stream providers in module-id order
	// and stops at the first that answers (ADR 0073), so on an install running
	// both, AIOStreams is asked first and its releases are the ones attached. That
	// is the intended precedence — a single curated aggregator ahead of an
	// open-ended addon list — but it is alphabetical accident rather than a policy
	// the Platform holds, exactly like the cinemeta/tmdb ordering beside it.
	CapabilityID = "aiostreams"
)
View Source
const DefaultInstance = "https://aiostreams.elfhosted.com/stremio"

DefaultInstance is the public AIOStreams instance run by ElfHosted, and the module's default.

**It is a default host, not a working configuration**, and that distinction is the single most important fact about this module. AIOStreams resolves nothing until a profile exists: the manifest at this base declares `configurationRequired`, an empty `resources` array and no types, so a stream request against it correctly yields nothing. A user creates a profile on the instance and pastes the resulting manifest URL — which carries their profile id — into this module's settings.

Defaulting to it anyway is deliberate. It means the settings screen can offer a working "Configure" link on a fresh install rather than an empty box and a URL to go and find, and it names the instance the module is pointed at instead of leaving that implicit.

Variables

This section is empty.

Functions

This section is empty.

Types

type Capability

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

Capability is the AIOStreams module: a dedicated stream provider for one named upstream.

**What it deliberately does not fill is the point.** It declares no metadata, search or catalog role, so it can never put a title into the library or offer one in a search — an install adds titles through a metadata module and this fills in what plays (ADR 0073). That is a narrower surface than `module-stremio-addons`, which hosts whatever a user pastes in and therefore inherits whatever those addons do; here the trust decision is a single instance URL a user can read.

It holds an HTTP client and a manifest cache. The instance it points at is a user-managed setting handed in per invocation (ADR 0021), so one registered module serves whatever each install configured.

func New

func New(httpClient *http.Client) *Capability

New builds the capability over an HTTP client (nil for a default). The instance URL is not supplied here — it arrives as settings on each invocation.

The Platform passes its own client, which routes through the dial guard that stops a user-supplied URL reaching the deployment's private network. A module that built its own would bypass it, which is precisely the seam this module sits on: the instance URL is text a user typed.

func (*Capability) Import

Import is refused, and that refusal is the module's shape rather than an unimplemented corner.

Import materialises a ref a *read* role produced (ADR 0028). This module fills no read role that produces one: it has no search, no catalogs and no metadata, so nothing can hand it a ref it made, and materialising from a stream listing would mean inventing a title out of release names. Titles come from a metadata module; this fills in what plays.

func (*Capability) Manifest

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

Manifest is the module's self-declaration (ADR 0027): the two source roles it fills, and the settings screen that configures them.

func (*Capability) SettingsUI

SettingsUI renders the module's own settings screen as SDUI (RoleSettingsUI, ADR 0038): which instance is in use and whether it is actually serving, a way to open that instance's configuration page, a field to point the module at a different instance, and a way back to the default.

This role is not decoration for this module. The public default is a *host*, not a working configuration — AIOStreams serves nothing until a profile exists — so this screen is the whole path from "installed" to "resolving streams". A capability with no client path is not done, it is owed.

Every mutating control is an Invoke of the Platform's configureModule command carrying the complete new settings document, so the Platform stays the one that persists it and the module holds no state between invocations. The screen is returned as serialised UINode JSON, which is what keeps the SDK SDUI-agnostic.

func (*Capability) Streams

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

Streams resolves playable locations for a materialised item (RoleStream).

It is called by the Platform's enrichment pass for content some *other* module sourced (ADR 0073), which is the only way it is ever called: a ref this module produced does not exist. So the ref carries a shared external identity, and addressOf decides whether it is one AIOStreams can speak.

Returning an empty response with no error is a normal answer here, not a degraded one. Three ordinary situations produce it — an identity that is not an IMDB id, an instance with no profile configured yet, and a title the instance's sources simply do not have — and erroring on any of them would fail a user's import over a title that was never this module's to know.

func (*Capability) Subtitles

Subtitles resolves subtitle tracks for an item (RoleSubtitles, ADR 0037). Like Streams it is a source role and the consumer is a player; it returns an empty response, no error, when the instance serves none.

type Client

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

Client talks to one AIOStreams instance. One instance, not a list: this module is a provider for a named upstream, and "which of several sources answered" is the question `module-stremio-addons` exists to ask.

func (*Client) Base

func (c *Client) Base() string

Base is the instance URL this client addresses.

func (*Client) Instance

func (c *Client) Instance(ctx context.Context) InstanceInfo

Instance reports what the configured instance says about itself, for the settings screen. It never returns an error: every failure is a state the screen has to render, so it is carried in the value.

func (*Client) Streams

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

Streams fetches every stream the instance offers for a content id.

All of them rather than the best one (ADR 0049): AIOStreams' whole purpose is to return a filtered, sorted *set*, and which member of it a particular client can play is not knowable here. The order is the instance's own, which is the user's configured sort — so it is carried through rather than re-ranked.

It returns nothing, and no error, when the instance declares no stream resource for the type. That is the unconfigured-instance case and it is normal, not a failure.

func (*Client) Subtitles

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

Subtitles fetches subtitle tracks for a content id, de-duplicated by language and file. It returns nothing, and no error, when the instance declares no subtitles resource — AIOStreams serves them only when the user's profile enables a subtitle source.

type InstanceInfo

type InstanceInfo struct {
	// Base is the normalised instance URL requests are built from.
	Base string
	// Reachable is false when the manifest could not be fetched — a wrong URL, an
	// instance that is down, a profile that has been deleted.
	Reachable bool
	// Err is why it was not reachable, for a screen that has to say something
	// more useful than "no".
	Err error
	// Configured is true when the instance serves a stream resource. An
	// AIOStreams base with no profile is reachable and *not* configured, which is
	// the state a fresh install is in.
	Configured  bool
	Name        string
	Version     string
	Description string
}

InstanceInfo is the display detail for the configured instance, for the settings screen: what the manifest says about itself, and whether it is reachable and configured at all.

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"`
	BehaviorHints addonBehaviorHints `json:"behaviorHints"`
}

Manifest is the subset of an addon manifest this module reads. AIOStreams is a Stremio addon by protocol, so this is the Stremio manifest shape — narrowed to what a stream and subtitle provider needs, which is why there are no catalog fields here.

type ResourceDecl

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

ResourceDecl is one entry of a manifest's resources array. The protocol allows each entry to be either a bare string ("stream") or an object carrying its own types and id prefixes; both shapes are unmarshalled.

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 module reads. A stream is either a direct URL — which is what AIOStreams returns for a debrid or proxied result, and the common case — or a torrent identified by InfoHash.

func (Stream) Ref

func (s Stream) Ref() string

Ref is the location reference to record for this stream: the direct URL when there is one, otherwise a magnet built from the info hash. Empty when the stream carries neither, which the caller drops.

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 module reads (ADR 0037): a track's language and file URL.

Directories

Path Synopsis
cmd
module-aiostreams command
Command module-aiostreams runs this module as its own process, for a Platform that hosts it out of process (ADR 0064, ADR 0077).
Command module-aiostreams 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