Magnetio logo

Magnetio

Magnetio Version License Node.js Docker Stremio Addon Debrid Services Torznab Jackett Prowlarr

A fully self-hosted Stremio addon with 24 torrent providers, 9 debrid services, Torznab/Jackett/Prowlarr integration, and TMDB recommendations.

No external scraper dependency. No third-party backend. Your API keys never leave your server.
magnetio.peterdsp.dev | Configure and Install

--- ## What is Magnetio? Magnetio is an open-source, self-hostable **Stremio addon** that comes with its own **built-in torrent scraper**. It queries 24 torrent providers in parallel (including Torznab-compatible indexers via Jackett or Prowlarr), deduplicates results, and optionally resolves them into **instant direct-download streams** through your debrid service of choice. Everything runs on your own hardware (a Raspberry Pi is enough). No cloud subscriptions, no external scraper APIs, no data leaving your network. ### Why Magnetio? | | Magnetio | Typical Stremio addons | |:---|:---:|:---:| | Fully in-house scraping | Yes | Depends on external APIs | | Self-hosted, private | Yes | Often cloud-hosted | | 24 torrent providers + Torznab | Yes | Varies | | Jackett / Prowlarr support | Yes | Rare | | 9 debrid services | Yes | 1-3 typically | | Background prewarm | Yes | No | | Per-user config (providers, quality, language) | Yes | Limited | | Subtitle sync pipeline | Yes | No | | Redis caching with stale-while-revalidate | Yes | Basic or none | | Prometheus metrics | Yes | Rare | | Docker Compose one-command deploy | Yes | Varies | --- ## How It Works ``` You open a movie in Stremio | v +---------------------------------+ | Magnetio Addon (:7000) | | Receives stream/catalog/meta | | request from Stremio client | +---------------------------------+ | Calls internal scraper | v +---------------------------------+ | Magnetio Scraper (:8080) | | Queries up to 24 providers | | in parallel (max 4 concurrent) | | 22s timeout per provider | +---------------------------------+ | Deduplicates by infoHash (keeps highest-seeder entry per hash) | Content-filters results against the actual requested title/season | v +---------------------------------+ | Back to Addon | | Applies quality/language/size | | filters from user config | | Sorts by chosen strategy | +---------------------------------+ | If debrid keys present: check instant availability on each configured service | +---------+---------+ | | Cached Not cached Resolve to Keep as P2P direct HTTP magnet fallback stream URL (or prewarm it) | | +---------+---------+ | v Return sorted stream list to Stremio for playback ``` Real-Debrid results include both downloaded and uncached candidates. With Debrid prewarm enabled (the default), Magnetio starts the top three uncached results downloading in your Real-Debrid account in the background while the results list is displayed. The limit is configurable. Selecting a result reuses that preparation or starts another download, selects the requested episode/video, and redirects playback through Real-Debrid when ready. Unfinished downloads stay in the account for a later retry. Download speed and availability depend on peers and Real-Debrid; uncached files are not guaranteed to start immediately. Direct P2P fallback is not required. --- ## Supported Torrent Providers Magnetio ships with **24 providers** covering movies, TV series, and anime, plus Torznab support for connecting external indexer managers. The full matrix of ids and the content types each one covers is in [PROVIDERS.md](PROVIDERS.md), generated from the registry. Pass the **id** to `?providers=`: it is not always the site's name (1337x is `1337x`, and its module is `leetx.js`). ### Movies and TV | Provider | ID | Content | Method | |:---|:---|:---|:---| | YTS | `yts` | Movies only | JSON API | | EZTV | `eztv` | Series only | JSON API | | The Pirate Bay | `thepiratebay` | Movies, Series | JSON API (apibay.org) | | TorrentGalaxy | `torrentgalaxy` | Movies, Series | HTML scraper | | 1337x | `leetx` | Movies, Series | HTML scraper (detail pages) | | KickassTorrents | `kickasstorrents` | Movies, Series | HTML scraper | | LimeTorrents | `limetorrents` | Movies, Series | HTML scraper | | Bitsearch | `bitsearch` | Movies, Series | HTML scraper | | BT4G | `bt4g` | Movies, Series | HTML scraper | | BTDig | `btdig` | Movies, Series | HTML scraper | | GloTorrents | `glotorrents` | Movies, Series | HTML scraper | | TorLock | `torlock` | Movies, Series | HTML scraper | | TorrentDownloads | `torrentdownloads` | Movies, Series | HTML scraper | | TheRarBG | `therarbg` | Movies, Series | HTML scraper (RARBG successor) | | Rutor | `rutor` | Movies, Series | HTML scraper (Russian) | | Rutracker | `rutracker` | Movies, Series | HTML scraper (Russian) | ### Anime | Provider | ID | Method | |:---|:---|:---| | Nyaa | `nyaa` | Atom RSS feed | | AnimeSaturn | `animesaturn` | HTML scraper (Italian) | | SubsPlease | `subsplease` | JSON API (fansubs) | | AnimeTosho | `animetosho` | RSS feed (aggregator) | | nekoBT | `nekobt` | Torznab API (fansubs) | ### Torznab / Jackett / Prowlarr | Provider | ID | Method | |:---|:---|:---| | Torznab | `torznab` | Torznab XML API (any compatible endpoint) | The Torznab provider is a universal connector that works with any Torznab-compatible indexer manager. Configure the API URL and key on the configure page. Magnetio will search all indexers you have set up in your Jackett or Prowlarr instance. - **[Jackett](https://github.com/Jackett/Jackett)** - translates queries to 500+ tracker-specific requests. Free, open-source (MIT). - **[Prowlarr](https://github.com/Prowlarr/Prowlarr)** - newer indexer manager with tighter *arr integration. Free, open-source (GPL-3.0). Both are available as optional Docker Compose services (see [Quick Start](#quick-start)). All providers run in parallel with a concurrency limit of 4 and a 22-second timeout. Results are deduplicated by `infoHash`, keeping the entry with the highest seeder count. --- ## Debrid Services Debrid services cache torrents server-side and serve them as direct HTTP links, giving you instant, full-speed streams with no seeding required. | Service | Code | Cache Check | Catalog | Prewarm | |:---|:---:|:---:|:---:|:---:| | [Real-Debrid](https://real-debrid.com) | `RD` | Yes | Yes | Yes | | [Premiumize](https://premiumize.me) | `PM` | Yes | Yes | No | | [AllDebrid](https://alldebrid.com) | `AD` | Yes | No | Yes | | [DebridLink](https://debrid-link.com) | `DL` | No | Yes | Yes | | [EasyDebrid](https://easydebrid.com) | `ED` | Yes | No | No | | [Offcloud](https://offcloud.com) | `OC` | No | No | Yes | | [TorBox](https://torbox.app) | `TB` | Yes | Yes | Yes | | [Put.io](https://put.io) | `PU` | No | Yes | Yes | You can configure multiple debrid services simultaneously. Each one is checked independently for cached content. --- ## Quick Start ### Docker Compose (recommended) ```bash git clone https://github.com/peterdsp/Magnetio.git cd Magnetio # Start all three services (scraper + addon + redis) docker compose up -d # Optional: include Jackett for 500+ additional indexers docker compose --profile jackett up -d # Optional: include Prowlarr instead (or alongside) docker compose --profile prowlarr up -d # Open in browser open http://localhost:7000 ``` That is it. The addon runs on port 7000, the scraper on 8080, and Redis on 6379. Jackett runs on 9117 and Prowlarr on 9696 (both optional). All services share a Docker bridge network. ### Node.js (manual, two terminals) **Terminal 1 (Scraper):** ```bash cd scraper npm install cp .env.example .env # edit if needed node index.js # listens on :8080 ``` **Terminal 2 (Addon):** ```bash cd addon npm install cp .env.example .env # set SCRAPER_URL=http://localhost:8080 node index.js # listens on :7000 ``` Open `http://localhost:7000` to access the configuration page. --- ## Configuration ### Visual Configuration Page Navigate to `http://your-server:7000` in a browser. The configuration page lets you: 1. **Set quality filters** (4K, 1080p, 720p, 480p, CAM) 2. **Set language preferences** for streams and subtitles 3. **Connect Torznab indexers** (Jackett, Prowlarr, or any Torznab endpoint) 4. **Enter debrid API keys** (with show/hide toggle) 5. **Configure sort order and result limits** 6. **Enable or disable background prewarm** 7. **Copy the manifest URL** or click **Install in Stremio** The generated URL encodes your preferences in a pipe-delimited config string that becomes part of the manifest path. No server-side storage of user settings. ### Manual URL Format ``` https://your-server:7000/providers=yts,eztv,1337x|sort=qualityseeders|limit=10|RD=YOUR_KEY/manifest.json ``` ### All Configuration Parameters | Parameter | Values | Default | Description | |:---|:---|:---|:---| | `providers` | Comma-separated provider IDs | All 24 | Which torrent providers to query | | `sort` | `qualityseeders`, `qualitysize`, `seeders`, `size` | `qualityseeders` | How to rank streams | | `limit` | `1` to `50` | `10` | Max streams returned per request | | `qualities` | `4k`, `1080p`, `720p`, `480p`, `cam` | All | Quality whitelist | | `languages` | ISO codes (`en`, `es`, `pt`, `fr`, `de`, `it`, `ja`, `ru`, ...) | All | Stream language filter | | `subtitleLanguages` | ISO codes | `en` | Subtitle language preference | | `torznabUrl` | Full Torznab API URL | - | Torznab endpoint (Jackett/Prowlarr) | | `torznabKey` | API key | - | Torznab API key | | `prewarm` | `1`, `0`, `true`, `false` | `1` | Background-add top uncached torrents to debrid | | `prewarmLimit` | `0` to `10` | `3` | How many uncached results to prewarm per service | | `p2pFallback` | `1`, `0`, `true`, `false` | `0` | Include P2P streams when a configured debrid service has no direct result | | `onDemand` | `1`, `0`, `true`, `false` | `1` | Show uncached Real-Debrid results and on-demand entries for DebridLink, Offcloud and Put.io | | `debridCatalogs` | `1`, `0`, `true`, `false` | `1` | Expose debrid cloud catalogs (Movies/Series) when a key is set | | `excludeSizes` | Size thresholds like `1GB,2GB` | None | Exclude streams below these sizes | | `maxSize` | Bytes | None | Maximum file size | | `RD` | API key | - | Real-Debrid | | `PM` | API key | - | Premiumize | | `AD` | API key | - | AllDebrid | | `DL` | API key | - | DebridLink | | `ED` | API key | - | EasyDebrid | | `OC` | API key | - | Offcloud | | `TB` | API key | - | TorBox | | `PU` | API key | - | Put.io | ### Non-English audio Selecting an audio language other than English changes how the scraper searches, because non-English scene groups name a release after the local title rather than the original one. For each non-English language you select, every provider gets up to three search passes instead of one: | Pass | Query for `tt0068646` with `languages=it` | Needs | |:---|:---|:---| | Catalogue name | `The Godfather 1972` | nothing | | Scene tag | `The Godfather ITA` | nothing | | Localized title | `Il padrino` | nothing | The localized title is also accepted by the content filter, so a release called `Il.Padrino.1972.iTALiAN.BDRip` is no longer dropped for not containing the English name. Only the first pass carries the year. Providers require every word of a query, and a localized release is routinely labelled with the local release year or with none at all, so the year quietly empties the other two passes: `It Follows ITA 2015` returns nothing from TorrentDownloads while `It Follows ITA` returns ten Italian releases, the 2014 4K BDRemux among them. The year is checked again on what those passes return, with a year of slack either way, so a sequel or another film of the same name is still dropped without costing the whole pass. Localized titles come from Wikidata, which needs no API key or signup, so this works on a bare deployment. If a TMDB key is configured it is preferred, since TMDB tracks release titles more closely; supply it per user (the TMDB field in the configuration page, the same one recommendations use) or server-wide with `TMDB_API_KEY`. Wikidata still covers anything TMDB has no translation for. Audio preference is ordered, not a flat set: `languages=it,en` ranks Italian releases above English ones rather than treating both as equally wanted, and English remains as a fallback. A `MULTi` release passes any language whitelist, because the tag means several audio tracks are bundled without naming them, and it is ranked below an explicit match. Audio tags are read from the release name, so `iTA`, `iTALiAN` and `ITALIANO` all register as Italian, while `ENG.SUB-ITA` is reported as English audio with Italian subtitles rather than as an Italian track. Extra passes cost extra requests, so they only ever run for a language you actually asked for. Tune them with `SCRAPER_MAX_ALIAS_QUERIES`, `SCRAPER_MAX_TAG_QUERIES` and `SCRAPER_MAX_ALIAS_LANGUAGES`. ### Presets Quick-start configurations for common use cases: | Preset | What it does | |:---|:---| | `lite` | English-only, no CAM/screener, 3 providers, limit 5 | | `brazuca` | Portuguese-focused, 4 providers, limit 10 | | `italia` | Italian-focused, Italian audio ranked first with English fallback, all providers, limit 10 | Use a preset: `https://your-server:7000/lite/manifest.json` --- ## Architecture Magnetio is a three-service stack, each with a clear responsibility: ``` magnetio/ | | docker-compose.yml # Orchestrates all three services | +-- scraper/ # Torrent scraper backend | | index.js # Express server (:8080) | | package.json | +-- lib/ | | | cache.js # Redis/in-memory cache (Keyv) | | | catalog.js # Cinemeta top-content fetcher for prewarm | | | cinemeta.js # IMDb ID to title/year/season/episode resolver | | | cron.js # Scheduled prewarm job (node-cron) | | | httpClient.js # Throttled Axios client with retries (Bottleneck) | | | logger.js # Winston structured logging | | | magnetHelper.js # Shared infoHash extraction, base32, size parsing | | | prewarm.js # Prewarm orchestrator (scrapes top content into cache) | | +-- titleHelper.js # Quality/codec/source/language/HDR parser | +-- providers/ | | index.js # Parallel aggregator, deduplication, content filter | | yts.js # ...21 built-in provider modules | | torznab.js # Universal Torznab provider (Jackett/Prowlarr) | +-- ... | +-- addon/ # Stremio addon frontend | | index.js # Express server (:7000) + Prometheus metrics | | serverless.js # Stremio SDK router + configure + subtitle proxy | | addon.js # Stremio resource handlers (stream/catalog/meta/subtitles) | | package.json | +-- lib/ | | | manifest.js # Dynamic Stremio manifest generation | | | configuration.js # Config URL parser + presets | | | repository.js # HTTP client to call the scraper service | | | sort.js # Quality-tiered stream sorting | | | filter.js # Quality/language/size stream filtering | | | streamInfo.js # Torrent record to Stremio stream object mapper | | | subtitles.js # OpenSubtitles search integration | | | subtitleProxy.js # Subtitle download + sync pipeline | | | cache.js # Redis/in-memory cache (Keyv) | | | languages.js # Language codes, names, flag emojis | | | magnetHelper.js # Best-tracker list + magnet URI builder | | | namedQueue.js # Request deduplication (prevents duplicate in-flight) | | | landingTemplate.js # HTML/CSS/JS for the visual config page | | | types.js # Shared enums and type definitions | | +-- logger.js # Winston structured logging | +-- moch/ # Debrid service integrations | | moch.js # Debrid orchestrator (cache check + resolve) | | mochHelper.js # Shared utils, per-key blacklisting | | options.js # Debrid service registry | | static.js # Static stream injections | | realdebrid.js # Real-Debrid API client | | premiumize.js # Premiumize API client | | alldebrid.js # AllDebrid API client | | debridlink.js # DebridLink API client | | easydebrid.js # EasyDebrid API client | | offcloud.js # Offcloud API client | | torbox.js # TorBox API client | +-- putio.js # Put.io API client | +-- .github/workflows/ +-- deploy.yml # CI/CD: validate, test, deploy via GitHub Actions ``` ### Service Communication ``` +------------------+ +-------------------+ +--------+ | Stremio Client | ------> | Addon (:7000) | ------> | Redis | | (your TV/phone) | HTTP | stream/catalog | cache | (:6379)| +------------------+ | debrid resolve | <------ +--------+ +--------+----------+ ^ | | | HTTP :8080 | v | +-------------------+ | | Scraper (:8080) | ------------>+ | provider queries | cache | dedup + filter | +-------------------+ ``` --- ## API Reference ### Addon Endpoints (`:7000`) | Method | Path | Description | |:---|:---|:---| | `GET` | `/` | Visual configuration page | | `GET` | `/configure` | Stremio configuration endpoint | | `GET` | `/manifest.json` | Root addon manifest (default config) | | `GET` | `/:config/manifest.json` | User-configured addon manifest | | `GET` | `/:config/stream/:type/:id.json` | Stream results for a title | | `GET` | `/:config/catalog/:type/:id.json` | Debrid catalog (library browsing) | | `GET` | `/:config/meta/:type/:id.json` | Title metadata | | `GET` | `/:config/subtitles/:type/:id.json` | Subtitle results | | `GET` | `/proxy/subtitle/:id.srt` | Subtitle proxy with optional sync | | `GET` | `/health` | Health check (`{ status: "ok" }`) | | `GET` | `/stats` | Analytics dashboard (request counts, unique users) | | `GET` | `/swagger` | Prometheus metrics UI (auth required) | ### Scraper Endpoints (`:8080`) | Method | Path | Description | |:---|:---|:---| | `GET` | `/health` | Health check | | `GET` | `/providers` | List all available provider IDs, names and the content types each covers | | `GET` | `/providers/health` | Which providers are actually answering. `503` when any is degraded | | `GET` | `/streams/:type/:id` | Scrape all providers for a title | | `GET` | `/streams/:type/:id?providers=yts,eztv` | Scrape specific providers only | | `POST` | `/prewarm` | Manually trigger cache prewarm | | `GET` | `/prewarm/status` | Check prewarm job status | ### Stream Record Shape Each torrent result from the scraper follows this structure: ```json { "infoHash": "a1b2c3d4e5f6...", "title": "Movie.Name.2024.1080p.BluRay.x264-GROUP", "seeders": 248, "leechers": 12, "size": 8589934592, "provider": "1337x", "quality": "1080p", "codec": "AVC", "source": "BluRay", "languages": ["en"], "hdr": null, "imdbId": "tt1234567" } ``` --- ## Caching Strategy Both services use **Keyv** backed by Redis (falls back to in-memory if Redis is unavailable). | What | TTL | Notes | |:---|:---|:---| | Scraper stream results | 1 hour | Per title, deduplicated | | Addon stream responses | 1 hour | Stale-while-revalidate: 1h, stale-on-error: 4h | | Empty results | Never cached | Prevents cache poisoning from transient failures | | Prewarm scraper cache | 4 hours | Pre-populated by scheduled job | | Debrid prewarm flags | 6 hours | Prevents re-adding already-prewarmed torrents | | Addon router instances | 5 min | LRU with max 64 entries | | Subtitle proxy cache | 1 hour | Per stream-specific subtitle | --- ## Special Features ### Background Prewarm Magnetio can proactively warm the cache and your debrid accounts: - **Scraper prewarm**: A cron job (default: daily at 4 AM) fetches the top 50 movies and 20 series from Cinemeta, scrapes all providers for each, and stores results in Redis. When a user later requests one of these titles, the response is instant. - **Debrid prewarm**: After filtering and sorting streams for a user request, Magnetio background-adds the top uncached torrents (configurable, default 3) to your debrid account. This is non-blocking. Real-Debrid preparation selects the requested episode/video and is shared with playback requests, so pressing play during prewarm does not create a duplicate job. Pending jobs are retained, and repeated searches are deduplicated per account, torrent and episode. By the time you click play, the torrent may already be downloading or fully cached. ### Subtitle Sync Pipeline For debrid/direct HTTP streams, Magnetio can serve stream-specific subtitles: 1. Fingerprints the media file (filename, size, OpenSubtitles hash via byte-range) 2. Searches OpenSubtitles for the best match 3. Downloads the subtitle 4. Optionally runs `ffsubsync` (audio-based alignment) or `alass` (split/framerate correction) 5. Caches and serves the corrected `.srt` This works reliably for debrid streams where the addon has access to the final HTTP URL. ### Content Filtering The scraper validates that returned torrents actually match the requested content: - For movies: title must contain at least part of the movie name - For series: title must contain a matching season/episode marker (S01E02, 1x02, "Season 1", etc.) - Word matching with a 50% threshold for multi-word titles - Prevents unrelated results from polluting the stream list ### Analytics Redis-backed, best-effort usage tracking: - Daily request counts and per-type breakdowns (stream, catalog, subtitle, page views) - Unique users counted by client IP: each IP (IPv6 reduced to its /64) is salted and hashed, and only the hash goes into a HyperLogLog, so no IPs or other PII are stored - Distinct addon configurations counted separately (`uniqueConfigs` in the JSON) - 7-day rolling history - Available at `/stats` ### Request Deduplication A `NamedQueue` system prevents duplicate in-flight requests. If 10 users request the same movie at the same instant, the scraper runs once and all 10 get the same result. --- ## Deployment ### Production with Docker Compose ```bash # Clone and configure git clone https://github.com/peterdsp/Magnetio.git cd Magnetio cp .env.example .env # Edit .env with your settings (at minimum): # ADDON_PUBLIC_URL=https://your-domain.com # METRICS_PASSWORD=your-secure-password # Launch docker compose up -d ``` ### GitHub Actions CI/CD The included workflow (`.github/workflows/deploy.yml`) handles automated deployment: 1. **Validate**: `npm ci` + `npm test` on Node 22 2. **Deploy**: `rsync` to your server, preserving `.env` files 3. **Build**: `docker compose up -d --build --remove-orphans` on the target 4. **Health check**: Optional ping to your `/health` endpoint The workflow runs on a self-hosted GitHub Actions runner. Push to `main` triggers automatic deployment. The production workflow keeps Magnetio's Node.js services on the Raspberry Pi's SD card so the public addon remains available if USB media is slow or temporarily missing. Docker and its data can still live on `/mnt/media`; a systemd timer retries Docker automatically when that drive appears after boot. Set `DEPLOY_MODE=auto` or `DEPLOY_MODE=docker` when running `scripts/deploy-production.sh` manually to opt back into Compose deployment. ### Exposing to the Internet > **Don't want to self-host?** There's a public hosted instance at [magnetio.peterdsp.dev](https://magnetio.peterdsp.dev) (configure and install at [magnetio.peterdsp.dev/configure](https://magnetio.peterdsp.dev/configure)). Your Debrid keys still stay between your browser and your Debrid provider, the manifest URL just points at the hosted addon. The rest of this section is only relevant if you're running your own instance on a VPS or home server. Stremio's clients (web, desktop, mobile) refuse to install an addon over plain `http://`. This is a Stremio-side restriction, not something the addon can opt out of. If you can hit the configure page on `http://YOUR_IP:7000/configure` but Stremio fails to install, HTTPS is almost always the reason. The fix is to put any HTTPS-terminating reverse proxy in front of the addon. The `ADDON_PUBLIC_URL` env var controls the base URL that appears inside manifests; the landing page uses `location.origin` so it picks up your public domain automatically when accessed through the proxy. #### Caddy (recommended, auto Let's Encrypt) ```caddyfile magnetio.example.com { reverse_proxy localhost:7000 } ``` Caddy handles the certificate, the renewals, and HTTP-to-HTTPS redirect by itself. One file, restart, done. #### Cloudflare Tunnel (no open ports) ```bash cloudflared tunnel create magnetio cloudflared tunnel route dns magnetio magnetio.example.com cloudflared tunnel run --url http://localhost:7000 magnetio ``` Useful when you don't want to open inbound ports on your firewall at all. #### Nginx + certbot, Tailscale, WireGuard Also fine. Anything that gives the addon a real `https://` URL on a public domain works. #### Oracle Cloud / Free Tier VPS notes Oracle's free-tier VMs block inbound traffic in **two** places by default. If you set up the addon, `:7000` works locally on the box, but install from Stremio (or even `curl` from your laptop) fails, you almost certainly need to open both: 1. **VCN security list** (web console): add an ingress rule for TCP `443` (and `7000` if you're testing without a proxy), source `0.0.0.0/0`. 2. **Host iptables** (Oracle Linux ships a strict default): ```bash sudo iptables -I INPUT 6 -p tcp --dport 443 -j ACCEPT sudo iptables -I INPUT 6 -p tcp --dport 7000 -j ACCEPT sudo netfilter-persistent save # or: sudo service iptables save ``` On Ubuntu images use `ufw allow 443/tcp` and `ufw allow 7000/tcp` instead. Combined with the HTTPS step above, that's enough to install Magnetio on an Oracle VPS. --- ## Environment Variables ### Addon (`addon/.env`) | Variable | Default | Description | |:---|:---|:---| | `PORT` | `7000` | HTTP port | | `REDIS_URI` | In-memory | Redis connection string | | `SCRAPER_URL` | `http://localhost:8080` | Internal scraper service URL | | `ADDON_PUBLIC_URL` | Auto-detected | Public URL for manifest and subtitle links | | `METRICS_USER` | `admin` | Username for `/swagger` metrics and `/stats` | | `METRICS_PASSWORD` | `magnetio` | Password for `/swagger` metrics and `/stats` (change it on public instances) | | `STATS_SALT` | Generated, stored in Redis | Secret used to hash client IPs for unique-user counts | | `OPENSUBTITLES_API_KEY` | - | Enables subtitle resource | | `OPENSUBTITLES_USERNAME` | - | OpenSubtitles account (for downloads) | | `OPENSUBTITLES_PASSWORD` | - | OpenSubtitles account password | | `OPENSUBTITLES_USER_AGENT` | `Magnetio v1.0.0` | User-Agent for OpenSubtitles API | | `FFSUBSYNC_PATH` | Auto-detected | Path to `ffsubsync` binary | | `ALASS_PATH` | Auto-detected | Path to `alass` binary | | `SUBTITLE_SYNC_MAX_OFFSET_SECONDS` | `300` | Max offset for ffsubsync | | `ALASS_SPLIT_PENALTY` | `10` | Split penalty for alass | | `LOG_LEVEL` | `info` | Winston log level | ### Scraper (`scraper/.env`) | Variable | Default | Description | |:---|:---|:---| | `PORT` | `8080` | HTTP port | | `REDIS_URI` | In-memory | Redis connection string | | `CACHE_TTL_STREAMS` | `3600` | Stream result cache TTL (seconds) | | `PREWARM_CRON` | `0 4 * * *` | Cron schedule for prewarm job | | `PREWARM_MOVIES` | `50` | Number of top movies to prewarm | | `PREWARM_SERIES` | `20` | Number of top series to prewarm | | `SCRAPER_PROVIDER_TIMEOUT_MS` | `25000` | Per-provider timeout (raise if slow providers keep firing `timed out`) | | `SCRAPER_HARD_TIMEOUT_MS` | `provider + 2000` | Overall scrape budget | | `SCRAPER_EARLY_RETURN_MS` | `6000` | Return early once enough results land | | `SCRAPER_MIN_EARLY_RESULTS` | `10` | Minimum matching results (after the content filter) before early return kicks in | | `SCRAPER_DOMAIN_COOLDOWN_MS` | `300000` | Cooldown when a domain returns 403/5xx/timeout | | `SCRAPER_RATELIMIT_COOLDOWN_MS` | `600000` | Longer cooldown when a domain returns 429 | | `LOG_LEVEL` | `info` | Winston log level | ### Optional Services | Variable | Default | Description | |:---|:---|:---| | `JACKETT_PORT` | `9117` | Exposed port for Jackett UI (profile: `jackett`) | | `PROWLARR_PORT` | `9696` | Exposed port for Prowlarr UI (profile: `prowlarr`) | | `TZ` | `Etc/UTC` | Timezone for Jackett/Prowlarr containers | Start optional services with Docker Compose profiles: ```bash docker compose --profile jackett up -d # Jackett only docker compose --profile prowlarr up -d # Prowlarr only docker compose --profile jackett --profile prowlarr up -d # Both ``` --- ## Tech Stack | Layer | Technology | |:---|:---| | Runtime | Node.js >= 18 (CI runs on 22) | | Module system | ES Modules throughout | | HTTP framework | Express 4 | | Stremio integration | stremio-addon-sdk | | HTML parsing | Cheerio | | HTTP client | Axios | | Rate limiting | Bottleneck (scraper), p-limit + p-queue (addon) | | Caching | Redis 7 Alpine via Keyv | | Monitoring | swagger-stats + prom-client (Prometheus) | | Scheduling | node-cron | | Logging | Winston | | Subtitle sync | ffsubsync, alass (optional) | | Containerization | Docker Compose v3.9 | | CI/CD | GitHub Actions (self-hosted runner) | --- ## Development ```bash # Start Redis (or use in-memory fallback) docker run -d --name redis -p 6379:6379 redis:7-alpine # Terminal 1: Scraper cd scraper && npm install && npm run dev # Terminal 2: Addon cd addon && npm install && npm run dev ``` ### Running Tests ```bash cd addon && npm test cd scraper && npm test ``` --- ## Contributing Contributions are welcome, and the codebase is deliberately shaped so that a first contribution can be small. Adding a torrent provider is one file, three exports, and no knowledge of the rest of the system. - **[CONTRIBUTING.md](CONTRIBUTING.md)** has the full walkthrough: the provider contract, the record shape, how to test a provider in isolation, and how to add a debrid service. - **[`scraper/providers/_template.js`](scraper/providers/_template.js)** is a commented starting point. Copy it, fill it in, register it. - **[Good first issues](https://github.com/peterdsp/Magnetio/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)** are each scoped to a single file and name the interface to implement. Comment on one to claim it. `npm test` in `scraper/` runs a contract test across every registered provider, so you get told what is wrong before a maintainer has to review it. --- ## Disclaimer Magnetio does not host, store, or distribute any copyrighted content. It aggregates publicly available metadata (torrent hashes, magnet links) from third-party indexers. Users are solely responsible for complying with copyright laws in their jurisdiction. Debrid service integrations are convenience features. API keys are passed through configuration URLs and are never stored server-side beyond the caching TTL. --- ## License [Apache License 2.0](LICENSE)