Skip to main content

Overview

The AddonLegacyTransport provides backward compatibility with Stremio’s legacy addon protocol, which uses JSON-RPC over HTTP. This adapter translates modern ResourcePath requests into legacy JSON-RPC calls.

Protocol Details

Legacy URL Format

Legacy addons use URLs ending with /stremio/v1 and communicate via JSON-RPC requests:
https://addon.example.com/stremio/v1/q.json?b={base64_encoded_request}
The b parameter contains a base64-encoded JSON-RPC request object.
The legacy protocol uses standard base64 encoding (not URL-safe), replicating a historical implementation detail for compatibility.

JSON-RPC Request Format

{
  "params": [null, {/* method-specific parameters */}],
  "method": "method.name",
  "id": 1,
  "jsonrpc": "2.0"
}

Implementation

Structure

pub struct AddonLegacyTransport<'a, T: Env> {
    env: PhantomData<T>,
    transport_url: &'a Url,
}
Fields:
  • transport_url - Reference to the addon’s base URL
  • env - Phantom data for environment type parameter

Constructor

pub fn new(transport_url: &'a Url) -> Self
Creates a new legacy transport adapter. Example:
let url = Url::parse("https://legacy-addon.example.com/stremio/v1")?;
let transport = AddonLegacyTransport::<MyEnv>::new(&url);

Supported Methods

The legacy adapter supports the following JSON-RPC methods:

Catalog (meta.find)

Lists metadata items from a catalog. Request Parameters:
{
  "query": { "type": "movie", "genre": "action" },
  "limit": 100,
  "skip": 0,
  "sort": { "popularity": -1 }
}
Response: Array of MetaItemPreview Modern to Legacy Mapping:
  • ResourcePath.typequery.type
  • extra.genrequery.genre
  • extra.skipskip parameter
  • Catalog ID determines sort order (“top” uses popularity, others use catalog ID)

Meta (meta.get)

Retrieves detailed metadata for a single item. Request Parameters:
{
  "query": { "imdb_id": "tt1254207" }
}
Response: Single MetaItem ID Format Translation:
  • IMDb: tt1254207{"imdb_id": "tt1254207"}
  • YouTube: UC123456{"yt_id": "UC123456"}
  • Custom: custom:value{"custom": "value"}

Stream (stream.find)

Finds available streams for a video. Request Parameters:
{
  "query": {
    "imdb_id": "tt0386676",
    "season": 5,
    "episode": 1,
    "type": "series"
  }
}
Response: Array of Stream ID Format for Episodes:
  • Format: {imdb_id}:{season}:{episode}
  • Example: tt0386676:5:1{"imdb_id": "tt0386676", "season": 5, "episode": 1}

Subtitles (subtitles.find)

Finds subtitles for a video file. Request Parameters:
{
  "query": {
    "itemHash": "tt0386676 5 1",
    "videoHash": "abc123def456",
    "videoSize": 1000000000,
    "videoFilename": "video.mp4"
  }
}
Response: Object with id and all array of Subtitles Parameter Mapping:
  • ResourcePath.iditemHash (colons replaced with spaces)
  • extra.videoHashvideoHash
  • extra.videoSizevideoSize
  • extra.videoFilenamevideoFilename

Manifest Handling

Fetching Legacy Manifests

fn manifest(&self) -> TryEnvFuture<Manifest>
Fetches the addon manifest using the legacy meta method. Request URL:
/q.json?b=eyJwYXJhbXMiOltdLCJtZXRob2QiOiJtZXRhIiwiaWQiOjEsImpzb25ycGMiOiIyLjAifQ==
The base64 string decodes to:
{"params":[],"method":"meta","id":1,"jsonrpc":"2.0"}

Legacy Manifest Structure

pub struct LegacyManifest {
    id: String,
    name: String,
    description: Option<String>,
    logo: Option<String>,
    background: Option<String>,
    version: Version,
    methods: Vec<String>,        // e.g. ["meta.get", "stream.find"]
    types: Vec<String>,          // e.g. ["movie", "series"]
    contact_email: Option<String>,
    id_property: Option<LegacyIdProperty>,
    sorts: Option<Vec<LegacySort>>,
}

Manifest Translation

The adapter converts legacy manifests to modern format: Methods → Resources:
  • meta.get"meta" resource
  • stream.find"stream" resource
  • subtitles.get"subtitles" resource
ID Property → ID Prefixes:
  • "imdb_id""tt"
  • "yt_id""UC"
  • Other → "{property}:"
Sorts → Catalogs: If the addon supports meta.find, catalogs are generated:
// Legacy sorts define multiple catalogs
{
  "sorts": [
    { "prop": "trending", "name": "Trending", "types": ["movie"] },
    { "prop": "popular", "name": "Popular" }
  ]
}

// Generates catalogs:
// - trending/movie
// - popular/movie
// - popular/series (for each type)

ID Format Handling

The legacy protocol uses a flexible ID format that the adapter translates:

IMDb Format

// Pattern: tt{digits}(:{season}:{episode})?
"tt1254207"        → {"imdb_id": "tt1254207"}
"tt0386676:5:1"   → {"imdb_id": "tt0386676", "season": 5, "episode": 1}

YouTube Format

// Pattern: UC{chars}(:{video_id})?
"UC123456"        → {"yt_id": "UC123456"}
"UC123456:video1" → {"yt_id": "UC123456", "video_id": "video1"}

Custom Format

// Pattern: {prefix}:{id}(:{video_id})?
"custom:test"       → {"custom": "test"}
"custom:test:vid1"  → {"custom": "test", "video_id": "vid1"}

Error Handling

Error Types

pub enum LegacyErr {
    JsonRPC(JsonRPCErr),      // JSON-RPC error response
    UnsupportedResource,      // Resource not supported by legacy protocol
    UnsupportedRequest,       // Invalid request format
}

JSON-RPC Errors

pub struct JsonRPCErr {
    message: String,
    code: i64,
}
JSON-RPC errors are converted to EnvError::AddonTransport with formatted message:
rpc error {code}: {message}

Limitations

The legacy adapter has intentional limitations to avoid complexity:
Not Supported:
  • Search functionality (meta.search) - only one known legacy addon used this
  • Some subtitles addons - modern protocol is preferred
Supported Features:
  • Catalog browsing with genre filtering
  • Metadata retrieval
  • Stream finding for movies and series
  • Subtitles with video hash matching

Example Usage

Catalog Request

let path = ResourcePath {
    resource: "catalog".to_string(),
    r#type: "movie".to_string(),
    id: "trending".to_string(),
    extra: vec![ExtraValue {
        name: "genre".to_string(),
        value: "action".to_string(),
    }],
};

let response = transport.resource(&path).await?;
Generates request:
/stremio/v1/q.json?b=eyJpZCI6MSwianNvbnJwYyI6IjIuMCIsIm1ldGhvZCI6Im1ldGEuZmluZCIsInBhcmFtcyI6W251bGwseyJsaW1pdCI6MTAwLCJxdWVyeSI6eyJnZW5yZSI6ImFjdGlvbiIsInR5cGUiOiJtb3ZpZSJ9LCJza2lwIjowLCJzb3J0Ijp7InBvcHVsYXJpdHkiOi0xLCJ0cmVuZGluZyI6LTF9fV19

Stream Request

let path = ResourcePath::without_extra(
    "stream",
    "series",
    "tt0386676:5:1"
);

let response = transport.resource(&path).await?;
Generates JSON-RPC call:
{
  "method": "stream.find",
  "params": [null, {
    "query": {
      "imdb_id": "tt0386676",
      "season": 5,
      "episode": 1,
      "type": "series"
    }
  }]
}

Source Reference

  • Legacy transport: src/addon_transport/http_transport/legacy/mod.rs
  • Manifest conversion: src/addon_transport/http_transport/legacy/legacy_manifest.rs
  • Main HTTP transport: src/addon_transport/http_transport/http_transport.rs

HTTP Transport

Modern HTTP transport implementation

Addon Transport Trait

Core transport interface definition

Build docs developers (and LLMs) love