FlixNest Addon Developer Guide

Canonical source: FLIXNEST_ADDON_DEVELOPER_GUIDE.md in the project root.


# FlixNest Addon SDK — Developer Documentation

This document is the authoritative guide for building add-ons that integrate with FlixNest. It is not a compiled SDK; it specifies the Android-side extension interfaces and the HTTP contracts your add-on should implement.

## Table of Contents

1. Overview
2. Addon Types & Android Interfaces
3. Remote Add-on Protocol (Streams & Subtitles)
4. Manifests (Stremio-compatible) & Behavior Hints
5. Dynamic Tabs (customTabs) Contract
6. Community Directory & Details
7. Built-in vs Community Add-ons
8. Install, Order, Remove, Stable IDs
9. Request/Response Schemas
10. Examples (NATIVE, STREMIO, Subtitles)
11. Status/Config Flow
12. Notes & Troubleshooting
13. WebStreamr Integration (FAST adapter)

---

## 1) Overview

FlixNest supports two extension surfaces:

- Built-in add-ons (implemented in Kotlin and shipped with the app)
- Community add-ons (HTTP servers responding with JSON according to this spec)

Core Android components in this repo:

- Addon management: `app/src/main/java/hu/kinetik/streamapp/data/addon/AddonManager.kt`
- Remote scrapers: `app/src/main/java/hu/kinetik/streamapp/data/scraper/RemoteScraper.kt`
- Remote subtitles: `app/src/main/java/hu/kinetik/streamapp/data/subtitle/RemoteSubtitleProvider.kt`
- Community wrapper: `app/src/main/java/hu/kinetik/streamapp/data/addon/CommunityAddonService.kt`, `CommunityAddon.kt`
- Dynamic tabs: `app/src/main/java/hu/kinetik/streamapp/data/addon/AddonTabRegistry.kt`, `AddonTabCache.kt`

---

## 2) Addon Types & Android Interfaces

Types (deduced from manifest `resources`):

- Scraper: provides streams (`resources` contains `stream`)
- Subtitle: provides subtitles (`resources` contains `subtitles`)
- Both: provides both

Scraper interface

```kotlin
interface Scraper {
    val name: String
    suspend fun getStreams(searchQuery: String, context: Context): List<Stream>
}
```

Subtitle interface

```kotlin
interface SubtitleProvider {
    val name: String
    val id: String
    suspend fun searchSubtitlesForMovie(...): List<LocalSubtitle>
    suspend fun searchSubtitlesForTvShow(...): List<LocalSubtitle>
    suspend fun autoLoadSubtitleForMovie(...): LocalSubtitle?
    suspend fun autoLoadSubtitleForTvShow(...): LocalSubtitle?
    suspend fun downloadSubtitle(subtitle: LocalSubtitle): LocalSubtitle?
    suspend fun isAvailable(): Boolean
}
```

---

## 3) Remote Add-on Protocol (Streams & Subtitles)

Streams are fetched via `RemoteScraper` in two modes.

NATIVE mode

- Endpoint: `{baseUrl}/stream?q={urlEncodedSearchQuery}`
- Response: `{ "streams": [ ... ] }` (see schema in section 9)

STREMIO mode

- Endpoint: `{baseUrl}/stream/{type}/{id}.json`
  - `type`: `movie` or `series`
  - `id`: `tt0848228` (movie) or `tt...:S:E` (series episode)
- Response: Stremio `streams` array (app converts to FlixNest `Stream`)

Mode selection

- If URL suggests Stremio (contains `stremio`/`strem.fun` or ends with `/manifest.json`), STREMIO mode is used and default `searchQueryType` is `imdb`.
- Otherwise: NATIVE mode.

Subtitles (RemoteSubtitleProvider)

- Endpoint: `{baseUrl}/subtitles`
- Query: `q={title}&lang={hu|en|...}&type={movie|tv}[&imdb=tt...][&year=YYYY][&season=S][&episode=E]`
- Response: `{ "subtitles": [ { title, language, downloadUrl, sourceName, fileName } ] }`

Common headers from the app

- `User-Agent: StreamApp/1.0`
- `Accept: application/json`
- `Accept-Encoding: gzip, deflate`

Optional endpoints often used by community add-ons

- `/manifest.json`, `/config` or `/config.html`, `/status`, `/health`
- Optional personalized endpoints: `/user/:userId/manifest.json`, `/user/:userId/stream/:type/:id.json` (if you implement per-user config)

---

## 4) Manifests (Stremio-compatible) & Behavior Hints

Minimal Stremio-compatible manifest with FlixNest extensions

```json
{
  "id": "your.addon.id",
  "version": "1.0.0",
  "name": "My Addon",
  "description": "...",
  "types": ["movie", "series"],
  "resources": ["stream"],
  "catalogs": [],
  "customTabs": [
    { "id": "iptv", "title": "IPTV", "icon": "tv", "endpoint": "channels", "itemType": "channel" }
  ],
  "category": { "name": "Top Movies", "itemType": "movie", "endpoint": "catalog/movie/top.json" },
  "behaviorHints": {
    "searchQueryType": "imdb",
    "configurable": true,
    "configurationRequired": false
  }
}
```

Behavior hints

- `searchQueryType`: `imdb` | `title` (explicit preference for stream searches). If omitted, auto-detected; Stremio defaults to `imdb`.
- `configurable`: if false, no config button even if `/config` exists.
- `configurationRequired`: if true, UI treats addon as requiring setup (login, etc.).

Community wrapper shape (also accepted)

```json
{
  "addon": { "name": "...", "version": "...", "description": "...", "author": "...", "type": "stremio" },
  "manifest": { /* stremio manifest as above */ },
  "endpoints": {
    "manifest": ".../manifest.json",
    "config": ".../config",
    "status": ".../status",
    "user_manifest": ".../user/:userId/manifest.json",
    "user_stream": ".../user/:userId/stream/:type/:id.json"
  }
}
```

Catalogs & Categories

- If `manifest.category` is present, it is registered as an addon category.
- Otherwise, Stremio `catalogs` may be converted into categories (movie/tv) by `CommunityAddonService`.

---

## 5) Dynamic Tabs (customTabs) Contract

Add-ons can add custom tabs to the FlixNest home screen using `manifest.customTabs`.

Manifest example

```json
{
  "customTabs": [
    { "id": "iptv", "title": "IPTV", "icon": "tv", "endpoint": "channels", "itemType": "channel" }
  ]
}
```

Data endpoint & response

- App calls `{baseUrl}/{endpoint}` and expects `AddonTabData`:

```json
{
  "items": [
    {
      "id": "ch_001",
      "title": "Channel 1",
      "url": "https://example.com/live.m3u8",
      "sourceTitle": "My IPTV",
      "isLive": true,
      "contentType": "channel",
      "country": "HU",
      "language": "hu",
      "logo": "https://example.com/logo.png"
    }
  ],
  "count": 1,
  "configuration": { "selectedCountries": ["HU"], "maxChannels": 200 },
  "demoMode": false,
  "groupBy": "country"
}
```

See `AddonTabRegistry.kt` for all known fields (`AddonItem`, `AddonTabData`).

---

## 6) Community Directory & Details

- The app fetches a directory from `BuildConfig.COMMUNITY_BASE_URL + BuildConfig.COMMUNITY_ADDON_PREFIX + "/"`.
- It parses an HTML listing into `CommunityAddonDirectory { name, url }`.
- It then fetches details from `.../manifest.json` or a server-side wrapper, registers categories and custom tabs.

---

## 7) Built-in vs Community Add-ons

Built-in (Kotlin; immutable set, can be enabled/disabled):

- Streams: Real-Debrid, WebTor, YouTube Trailers
- Subtitles: OpenSubtitles, Wyzie Subs, Feliratok

Community examples in this repo:

- `flixnest-webstreamr/` — high-speed webstream adapter (`/addon/webstreamr/...`), provides endpoints like `/health`, `/stream?q=tt...`, `/manifest.json`
- `torrentio-addon/` — Stremio-compatible `/stream/{type}/{id}.json`, URL-based config (Base64)
- `iptv-addon/` — `customTabs` + `/channels` endpoint, with `/config` UI
- `solidtorrents-addon/`, `filmweb-test/`, `test-category-addon/` — additional examples/tests

---

## 8) Install, Order, Remove, Stable IDs

Installation

- User enters a base URL; the app normalizes to a manifest URL and fetches it when available.
- Type is inferred from `resources` (stream/subtitles/both).
- `behaviorHints.searchQueryType` is persisted if present; Stremio defaults to `imdb`.

Stable IDs (from `AddonSettingsService.extractAddonIdFromUrl`)

- Normalize to a manifest URL from the actual host/path.
- Derive an ID from `/addon/{id}` or the last path segment or the host, plus a stable hash of the manifest URL.
- Used in keys: `remote_addon_{stable}`, `remote_scraper_*`, `remote_subtitle_*`.

Ordering & Removal

- Addon order and subtitle provider order are persisted; users can reorder.
- Community add-ons can be removed; built-ins cannot (only toggled).

---

## 9) Request/Response Schemas

Streams (NATIVE)

`GET {baseUrl}/stream?q={encoded}` →

```json
{
  "streams": [
    {
      "title": "1080p WEB-DL",
      "url": "magnet:?xt=urn:btih:...",
      "sourceTitle": "My Addon",
      "subtitles": [ { "lang": "English", "url": "https://.../sub.vtt" } ],
      "headers": { "Referer": "https://..." },
      "isWebView": false,
      "isTorrent": true,
      "quality": "1080p",
      "size": "1.7 GB",
      "seeders": 120,
      "leechers": 15,
      "imdbId": "tt0848228",
      "year": 2012,
      "contentType": "movie",
      "seasonNumber": null,
      "episodeNumber": null
    }
  ]
}
```

Streams (STREMIO)

- `GET {baseUrl}/stream/{movie|series}/{tt...|tt...:S:E}.json`
- Return Stremio `streams` entries with `url` or `infoHash`; the app converts to `Stream`.

Subtitles

`GET {baseUrl}/subtitles?q={title}&lang={hu}&type={movie|tv}...` →

```json
{ "subtitles": [ { "title": "Movie.hu.srt", "language": "Hungarian", "downloadUrl": "https://...", "sourceName": "MySubs", "fileName": "Movie.hu.srt" } ] }
```

Dynamic tab data

- See section 5 for `AddonTabData` example.

---

## 10) Examples (NATIVE, STREMIO, Subtitles)

NATIVE stream add-on (Express)

```js
const express = require('express');
const app = express();
app.get('/manifest.json', (req, res) => res.json({ id: 'my.native', version: '1.0.0', name: 'My Native', description: '...', types: ['movie','series'], resources: ['stream'], behaviorHints: { searchQueryType: 'title' } }));
app.get('/stream', (req, res) => res.json({ streams: [{ title: `Result for ${req.query.q||''}`, url: 'https://example.com/video.m3u8', sourceTitle: 'My Native' }] }));
app.listen(3000, () => console.log('Native addon :3000'));
```

STREMIO stream add-on (Express)

```js
const express = require('express');
const app = express();
app.get('/manifest.json', (req, res) => res.json({ id: 'my.stremio', version: '1.0.0', name: 'My Stremio', description: '...', types: ['movie','series'], resources: ['stream'], behaviorHints: { searchQueryType: 'imdb' } }));
app.get('/stream/:type/:id.json', (req, res) => res.json({ streams: [{ title: `${req.params.type} ${req.params.id}`, url: 'https://example.com/video.m3u8' }] }));
app.listen(3001, () => console.log('Stremio addon :3001'));
```

Subtitle add-on (Express)

```js
const express = require('express');
const app = express();
app.get('/subtitles', (req, res) => res.json({ subtitles: [{ title: `HU for ${req.query.q||''}`, language: 'Hungarian', downloadUrl: 'https://example.com/sub.srt', sourceName: 'MySubs', fileName: `${req.query.q||'movie'}.hu.srt` }] }));
app.listen(3002, () => console.log('Subtitle addon :3002'));
```

---

## 11) Status/Config Flow

How config is detected

- If `behaviorHints.configurable == false`: no config UI offered.
- Else, existence of `endpoints.config` (or inferred `baseUrl/config`) controls UI affordance.
- `CommunityAddonService.hasConfigurationUrl()` verifies availability (HEAD request, tries `.html` fallback too).

When configuration is required

- `behaviorHints.configurationRequired == true` forces “needs configuration”.
- Otherwise, if a config endpoint exists and `status.isLoggedIn == false` or `status.configured == false`, the app treats it as not configured.

Status endpoint (optional)

- If present, the app fetches it and may sync server-side settings into local app storage (local-only persistence, server clear best-effort).

Search query type handling

- Stored per addon under `remote_scraper_searchQueryType_{stableId}` if manifest exposes `behaviorHints.searchQueryType`.
- Defaults to `imdb` for Stremio-mode add-ons.

---

## 12) Notes & Troubleshooting

- JSON & compression: app accepts gzip/deflate; return valid JSON arrays/objects.
- URLs: prefer absolute URLs for portability under shared hosting with base paths.
- Timeouts: reasonable (6–12s) on requests; avoid long-running responses.
- Security/TLS: TV compatibility handled; avoid self-signed endpoints unless required.
- Removal: built-ins can’t be removed, only disabled. Community add-ons can be removed and their tabs/categories get unregistered.

Private deployments (non-public repos)

- Directory source: point `BuildConfig.COMMUNITY_BASE_URL` (and `COMMUNITY_ADDON_PREFIX`) to a private domain, or disable the directory feature by returning no entries.
- Auth-protected endpoints: if you protect `/config` and `/status`, consider allowing HEAD/GET 200 for the config URL so detection works, even if the page requires auth to interact.
- Secrets: keep API keys server-side; do not emit credentials in manifests or responses. Prefer per-user sessions stored on the add-on server.
- CORS/Origins: restrict to your app’s origin(s); avoid `*` on production.
- Absolute URLs: use full URLs in responses to avoid base-path issues behind reverse proxies.
- Logging: avoid PII in logs; scrub query strings if they may contain identifiers.
- TLS: use valid certificates; TV devices can be sensitive to TLS quirks.

Useful references in this repo

- Android logic: `AddonManager.kt`, `RemoteScraper.kt`, `RemoteSubtitleProvider.kt`, `CommunityAddonService.kt`, `AddonTabRegistry.kt`
 - Example servers: `flixnest-webstreamr/`, `iptv-addon/`, `torrentio-addon/`

---

## 13) WebStreamr Integration (FAST adapter)

Overview

- The `flixnest-webstreamr/` project includes a FAST adapter (`flixnest-adapter-fast.js`) that runs WebStreamr internally and exposes StreamApp-friendly endpoints under the base path `/addon/webstreamr`.
- It is optimized for speed (caching, parallelism, short timeouts) and supplies a Stremio-style manifest with `behaviorHints.searchQueryType = imdb` and `configurable = false`.

Endpoints (base path)

- Manifest: `{BASE}/addon/webstreamr/manifest.json`
- Stream (NATIVE mode with IMDb query): `{BASE}/addon/webstreamr/stream?q=tt0848228`
- Health: `{BASE}/addon/webstreamr/health`
- Test page: `{BASE}/addon/webstreamr/test`
- Debug info: `{BASE}/addon/webstreamr/debug`

Important integration notes

- Use NATIVE mode for WebStreamr in FlixNest. Install the addon using the base URL without `/manifest.json` (e.g., `https://your-domain.com/addon/webstreamr`). This prevents the app from switching to STREMIO mode for this addon.
- The adapter still provides STREMIO-style endpoints via the internal proxy at `/webstreamr/stream/{type}/{tt...}.json`, but the StreamApp-facing endpoints are under `/addon/webstreamr` and expect `q=tt...`.
- The manifest explicitly sets `behaviorHints.searchQueryType = "imdb"`, so the app will pass IMDb IDs to `/stream?q=...`.

Local run (basic)

```bash
cd flixnest-webstreamr
npm install
node flixnest-adapter-fast.js
# Opens on http://localhost:3003/addon/webstreamr
```

Expected responses

- `GET /addon/webstreamr/manifest.json` → Stremio manifest with `resources: ["stream"]` and `behaviorHints.searchQueryType: "imdb"`
- `GET /addon/webstreamr/stream?q=tt0848228` → `{ "streams": [ ... ] }` (converted to FlixNest format)

Production hosting

- The adapter tries to build and launch WebStreamr internally; if build is unavailable (shared hosting), pre-build locally and upload `dist/` (see `SHARED_HOSTING_GUIDE.md`).
- Prefer absolute URLs and ensure TLS is valid. Restrict CORS as needed.