masqueradarr
sources & adapters · 05 the pluggable core

One adapter file, zero core branches

Every provider — ~17 of them, plus your own imports — plugs into the same source-agnostic core through one contract. The generic pipeline never branches per source; every difference lives in the adapter object.

01 The SourceAdapter contract

Adding a provider is one file plus one line

Every adapter implements the SourceAdapter contract (server/src/sources/types.ts) and is registered in server/src/sources/registry.ts. The generic core (buildSource) runs the same pipeline for all of them — the whole point is that it never branches per source.

the generic sync pipeline listChannels → normalize → dedupe → docs
listChannels
raw catalog
the provider's current channels
normalize
one schema
into sourcechannels
dedupe
by id
deterministic _id
project
playlistchannels
the editable store

The adapter supplies the per-source hooks the core calls at the right moments:

  • listChannels / normalize — populate the catalog on sync (the two above).
  • resolveStream / proxy — turn a stored sentinel into a real, playable upstream at play time. These are live — the Rust data-plane calls them per stream through the resolve seam.
  • afterSync — optional; write self-EPG (epgsources/epgchannels/programs) off the same listing the sync already fetched.
the rule

Boot init, the manifest, and the SPA all iterate the registry. To add a source: write adapters/<id>.ts, add one line to registry.ts, and commit an offline seed-data/sources/<id>.snapshot.json. Never add a per-source if in the core.

02 The adapter taxonomy

Six families, one registry

Adapters differ mainly in how a stored entry becomes a playable URL. That resolution strategy sorts them into families — from proxy-only synthetics, to a single authenticated source, to the anonymous scrape / API / macro / identity groups.

registry.ts → families → adapters color = resolution strategy
registry.ts · SOURCES: SourceAdapter[]
Synthetic · proxy-onlysynthetic: trueno catalog · no shell row · manifest omits
direct
Imported
Passthrough for user-imported M3U URLs; identity resolve, any https allowed. Channels carry origin:'direct'.
hdhomerun
HDHomeRun
Local OTA/cable tuner; catalog import is live, playback dormant (needs TS→HLS remux).
local
Local Now
localnow://id?slug sentinel → rotating CDN master; dynamic SSRF allow.
AuthenticatedrequiresAuth: truethe only per-source auth surface
dulo
dulo.tv
Supabase session + device fingerprint, captured via a headful Chromium. dulo://channel/id → fresh playbackUrl per play. EPG: Gracenote crosswalk.
Scrape-basedhtml / mirrorcatalog from a rotating mirror · multi-hop resolve
dlhd
DaddyLive
Scraped rotating-mirror HTML directory; watch.php?id → 3-hop Referer-gated scrape. Segments disguised as image/pdf. EPG: crosswalk + self-EPG.
dami
Dami.TV
dlhd-derived — reuses dlhd's resolve + mirror; own ~878-channel catalog with logos + ISO country grouping.
API sentinelopaque sentinelcatalog from JSON API · resolve = API call per play
tubi
Tubi.TV
tubi://channel/id → Tubi API per play; inline self-EPG on catalog rows.
xumo
Xumo Play
broadcast.json → 3-hop API resolve; paginated market guide.
stirr
STIRR
/playable → 1-hop POST; two-tier per-channel guide.
tcl
TCL TV+
format-stream-url → 1-hop POST; category schedule + batched detail.
pluto
Pluto TV
pluto://region/id → cached region boot + URL construct; per-region timelines. Gracenote crosswalk wired.
roku
The Roku Channel
roku://id → cached session boot + playId resolve; content-proxy fanout guide.
plex
Plex Live TV
plex://compoundId → anonymous JWT (no credentials) → signed library/parts master minted per play. Own per-channel grid self-EPG. Gracenote crosswalk wired.
Macro-fill__MACRO__ slotsstored HLS URL with slots · fill per play
samsung
Samsung TV Plus
jmp2.uk redirect → CDN master; self-EPG. Gracenote crosswalk wired.
lg
LG Channels
HLS URL with {MACRO} slots → expand per play; inline program self-EPG.
whale
Whale TV+
macro fill per play; separate /epg fetch (Vidaa shape).
distro
Distro TV
__MACRO__ VAST slots → fill per play; tvg_id-keyed self-EPG.
freelivesports
FreeLiveSports
device/cb/ref macro slots → fill per play; inline program self-EPG.
Identity / directreal HLS mastercatalog carries the master · pre-allow host only
vizio
Vizio WatchFree+
channelUrls[0] IS the real master; identity resolve + pre-allow host. Separate airings schedule.
vidaa
Vidaa Free TV
macros already expanded at catalog time; identity resolve. Self-EPG via uid. Gracenote crosswalk wired.
03 Key properties, at a glance

The resolve / EPG matrix

The same 20 adapters, sorted by how they resolve a stream and where their guide data comes from.

AdapterAuthResolve strategySelf-EPGGracenote xwalk
directIdentity (passthrough)
hdhomerunCatalog import (playback dormant)
localSentinel → rotating CDN
dulosessiondulo:// → playbackUrlyes
dlhdwatch.php → 3-hop scrapeyesyes
damidlhd resolveStream (shared)yesyes
tubitubi:// → Tubi APIyes (inline)
xumobroadcast.json → 3-hop APIyes
stirr/playable → 1-hop POSTyes
tclformat-stream-url → POSTyes
plutopluto:// → region bootyesyes (wired)
rokuroku:// → session + playIdyes
plexplex:// → anon JWT + signed masteryes (grid)yes (wired)
samsungjmp2.uk redirectyesyes (wired)
lg{MACRO} fill per playyes
whalemacro fill per playyes
distro__MACRO__ fill per playyes
freelivesportsmacro fill per playyes
vizioIdentity (direct master)yes (airings)
vidaaIdentity (macros pre-expanded)yesyes (wired)
04 Custom playlists

Bring your own channels

Beyond the built-in adapters, you can compose your own playlists. Every custom playlist rides the same per-user, token-gated .m3u + XMLTV export machinery as the built-ins.

● clone

Curate from any source

Hand-pick channels from any synced source into a curated playlist. The channels are independent copies (edits don't disturb the originals) but streams still route through the real adapter.

● url import

A remote M3U

Pull in any remote .m3u / .m3u8 URL. Re-syncable via the stored remoteUrl.

● file upload

A static M3U file

Upload a static .m3u; parsed once from the uploaded file (no sync).

● hdhomerun

A LAN tuner

Point at a LAN HDHomeRun tuner (deviceUrl); its channel lineup is imported. Playback (TS→HLS remux) is dormant pending the video engine.

05 Local Now — a deep dive

A fully-managed, market-scoped custom playlist

Local Now is a US FAST service that delivers a market-specific lineup — local broadcast stations, regional news, and national FAST networks — curated by geographic TV market. masqueradarr integrates it as a re-syncable custom playlist with a bundled EPG guide.

US-only

Local Now is geo-gated to US IP addresses. Adding one from a non-US IP returns a clear error — there's no built-in VPN workaround; route the server through a US network to use it.

Picking a market

Each playlist is scoped to a city/market, chosen two ways: a city search typeahead (GET /api/import/local/cities?q=), or auto-detect ("use my detected market", read from Local Now's homepage geo signal, falling back to New York). Every choice resolves to two stored identifiers:

FieldWhat it isExample
marketDmaA numeric Designated Market Area code — the Nielsen/TV-industry ID for a regional TV market.501 (New York)
marketSlugA comma-joined list of Local Now market slugs — a primary city slug plus any associated PBS station slugs.nyNewYorkCity,pbs-wnet,…

What you get

  • Immediate sync — creates a custom playlist row (source:'local', endpoint:'custom') and syncs the market's lineup at once; subscription-locked channels are pruned, the rest deduped by id.
  • Self-linked EPG — each playlist owns a playlist-bound EPG source (playlistBinding:true, id = playlist id). Channels are self-linked at sync — no manual Channel Mapping needed — and the guide renders immediately. Guide data is inline program[] from the same catalog fetch.
  • Hourly refresh — the playlist's own hourly cronjob drives all refreshes; the EPG Sources screen hides its manual controls.
  • Many markets, fully independent — add one playlist per market; each gets its own channels, EPG source, and hourly schedule. Cascade-delete removes a market's playlist, EPG, guide, and cronjob together.
README · Pluggable sources · Channel Adapter Architecture · Local Now restapi-sources skill · the adapter contract .claude/docs/*-datasource.md · per-source detail next → EPG & guide data
masqueradarr · sources & adapters next → epg & guide data