From 0f0dfe696d6a6bbb091a2f4605b07e57e33b9b26 Mon Sep 17 00:00:00 2001 From: <> Date: Thu, 2 Jul 2026 15:20:50 +0000 Subject: [PATCH] Deployed ef2e880 with MkDocs version: 1.6.1 --- .nojekyll | 0 404.html | 1234 +++ ADVANCED_CONFIG/index.html | 2088 +++++ API/index.html | 1333 ++++ CNAME | 1 + CREATING_A_SERVICE/index.html | 2120 ++++++ DEBUG_LOGGING/index.html | 1565 ++++ DOWNLOAD_CONFIG/index.html | 2090 +++++ DRM_CONFIG/index.html | 2315 ++++++ GLUETUN/index.html | 2395 ++++++ NETWORK_CONFIG/index.html | 1997 +++++ OUTPUT_CONFIG/index.html | 2343 ++++++ SERVICE_CONFIG/index.html | 1916 +++++ SUBTITLE_CONFIG/index.html | 1529 ++++ api/authentication/index.html | 1507 ++++ api/client-configuration/index.html | 1514 ++++ api/download-job-lifecycle/index.html | 1397 ++++ api/endpoint-map/index.html | 1424 ++++ api/endpoints/index.html | 2256 ++++++ api/error-responses/index.html | 1343 ++++ api/quick-start/index.html | 1391 ++++ api/remote-service-sessions/index.html | 1860 +++++ assets/favicon.svg | 1 + assets/images/favicon.png | Bin 0 -> 1870 bytes assets/javascripts/bundle.79ae519e.min.js | 16 + assets/javascripts/bundle.79ae519e.min.js.map | 7 + assets/javascripts/lunr/min/lunr.ar.min.js | 1 + assets/javascripts/lunr/min/lunr.da.min.js | 18 + assets/javascripts/lunr/min/lunr.de.min.js | 18 + assets/javascripts/lunr/min/lunr.du.min.js | 18 + assets/javascripts/lunr/min/lunr.el.min.js | 1 + assets/javascripts/lunr/min/lunr.es.min.js | 18 + assets/javascripts/lunr/min/lunr.fi.min.js | 18 + assets/javascripts/lunr/min/lunr.fr.min.js | 18 + assets/javascripts/lunr/min/lunr.he.min.js | 1 + assets/javascripts/lunr/min/lunr.hi.min.js | 1 + assets/javascripts/lunr/min/lunr.hu.min.js | 18 + assets/javascripts/lunr/min/lunr.hy.min.js | 1 + assets/javascripts/lunr/min/lunr.it.min.js | 18 + assets/javascripts/lunr/min/lunr.ja.min.js | 1 + assets/javascripts/lunr/min/lunr.jp.min.js | 1 + assets/javascripts/lunr/min/lunr.kn.min.js | 1 + assets/javascripts/lunr/min/lunr.ko.min.js | 1 + assets/javascripts/lunr/min/lunr.multi.min.js | 1 + assets/javascripts/lunr/min/lunr.nl.min.js | 18 + assets/javascripts/lunr/min/lunr.no.min.js | 18 + assets/javascripts/lunr/min/lunr.pt.min.js | 18 + assets/javascripts/lunr/min/lunr.ro.min.js | 18 + assets/javascripts/lunr/min/lunr.ru.min.js | 18 + assets/javascripts/lunr/min/lunr.sa.min.js | 1 + .../lunr/min/lunr.stemmer.support.min.js | 1 + assets/javascripts/lunr/min/lunr.sv.min.js | 18 + assets/javascripts/lunr/min/lunr.ta.min.js | 1 + assets/javascripts/lunr/min/lunr.te.min.js | 1 + assets/javascripts/lunr/min/lunr.th.min.js | 1 + assets/javascripts/lunr/min/lunr.tr.min.js | 18 + assets/javascripts/lunr/min/lunr.vi.min.js | 1 + assets/javascripts/lunr/min/lunr.zh.min.js | 1 + assets/javascripts/lunr/tinyseg.js | 206 + assets/javascripts/lunr/wordcut.js | 6708 +++++++++++++++++ .../workers/search.2c215733.min.js | 42 + .../workers/search.2c215733.min.js.map | 7 + assets/stylesheets/main.484c7ddc.min.css | 1 + assets/stylesheets/main.484c7ddc.min.css.map | 1 + assets/stylesheets/palette.ab4e12ef.min.css | 1 + .../stylesheets/palette.ab4e12ef.min.css.map | 1 + index.html | 1421 ++++ search/search_index.json | 1 + sitemap.xml | 83 + sitemap.xml.gz | Bin 0 -> 398 bytes 70 files changed, 44401 insertions(+) create mode 100644 .nojekyll create mode 100644 404.html create mode 100644 ADVANCED_CONFIG/index.html create mode 100644 API/index.html create mode 100644 CNAME create mode 100644 CREATING_A_SERVICE/index.html create mode 100644 DEBUG_LOGGING/index.html create mode 100644 DOWNLOAD_CONFIG/index.html create mode 100644 DRM_CONFIG/index.html create mode 100644 GLUETUN/index.html create mode 100644 NETWORK_CONFIG/index.html create mode 100644 OUTPUT_CONFIG/index.html create mode 100644 SERVICE_CONFIG/index.html create mode 100644 SUBTITLE_CONFIG/index.html create mode 100644 api/authentication/index.html create mode 100644 api/client-configuration/index.html create mode 100644 api/download-job-lifecycle/index.html create mode 100644 api/endpoint-map/index.html create mode 100644 api/endpoints/index.html create mode 100644 api/error-responses/index.html create mode 100644 api/quick-start/index.html create mode 100644 api/remote-service-sessions/index.html create mode 100644 assets/favicon.svg create mode 100644 assets/images/favicon.png create mode 100644 assets/javascripts/bundle.79ae519e.min.js create mode 100644 assets/javascripts/bundle.79ae519e.min.js.map create mode 100644 assets/javascripts/lunr/min/lunr.ar.min.js create mode 100644 assets/javascripts/lunr/min/lunr.da.min.js create mode 100644 assets/javascripts/lunr/min/lunr.de.min.js create mode 100644 assets/javascripts/lunr/min/lunr.du.min.js create mode 100644 assets/javascripts/lunr/min/lunr.el.min.js create mode 100644 assets/javascripts/lunr/min/lunr.es.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.he.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hu.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hy.min.js create mode 100644 assets/javascripts/lunr/min/lunr.it.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ja.min.js create mode 100644 assets/javascripts/lunr/min/lunr.jp.min.js create mode 100644 assets/javascripts/lunr/min/lunr.kn.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ko.min.js create mode 100644 assets/javascripts/lunr/min/lunr.multi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.nl.min.js create mode 100644 assets/javascripts/lunr/min/lunr.no.min.js create mode 100644 assets/javascripts/lunr/min/lunr.pt.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ro.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ru.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sa.min.js create mode 100644 assets/javascripts/lunr/min/lunr.stemmer.support.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sv.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ta.min.js create mode 100644 assets/javascripts/lunr/min/lunr.te.min.js create mode 100644 assets/javascripts/lunr/min/lunr.th.min.js create mode 100644 assets/javascripts/lunr/min/lunr.tr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.vi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.zh.min.js create mode 100644 assets/javascripts/lunr/tinyseg.js create mode 100644 assets/javascripts/lunr/wordcut.js create mode 100644 assets/javascripts/workers/search.2c215733.min.js create mode 100644 assets/javascripts/workers/search.2c215733.min.js.map create mode 100644 assets/stylesheets/main.484c7ddc.min.css create mode 100644 assets/stylesheets/main.484c7ddc.min.css.map create mode 100644 assets/stylesheets/palette.ab4e12ef.min.css create mode 100644 assets/stylesheets/palette.ab4e12ef.min.css.map create mode 100644 index.html create mode 100644 search/search_index.json create mode 100644 sitemap.xml create mode 100644 sitemap.xml.gz diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/404.html b/404.html new file mode 100644 index 00000000..a6bd7cce --- /dev/null +++ b/404.html @@ -0,0 +1,1234 @@ + + + + + + + + + + + + + + + + + + + + + + + + unshackle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ +
+ +

404 - Not found

+ +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/ADVANCED_CONFIG/index.html b/ADVANCED_CONFIG/index.html new file mode 100644 index 00000000..7a53f92a --- /dev/null +++ b/ADVANCED_CONFIG/index.html @@ -0,0 +1,2088 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Advanced & System - unshackle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+ +
+ + + + + + + + +

Advanced & System Configuration

+

This document covers advanced features, debugging, and system-level configuration options.

+

serve (dict)

+

Configuration for the integrated server that provides CDM endpoints (Widevine/PlayReady) and a REST API for remote downloading.

+

Start the server with:

+
unshackle serve                          # Default: localhost:8786
+unshackle serve -h 0.0.0.0 -p 8888      # Listen on all interfaces
+unshackle serve --no-key                 # Disable authentication
+unshackle serve --api-only               # REST API only, no CDM endpoints
+unshackle serve --remote-only            # Only expose remote service session endpoints
+
+

CLI Options

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionDefaultDescription
-h, --host127.0.0.1Host to serve from
-p, --port8786Port to serve from
--caddyfalseAlso serve with Caddy reverse-proxy for HTTPS
--api-onlyfalseServe only the REST API, disable CDM endpoints
--no-widevinefalseDisable Widevine CDM endpoints
--no-playreadyfalseDisable PlayReady CDM endpoints
--no-keyfalseDisable API key authentication (allows all requests)
--debug-apifalseInclude tracebacks and stderr in API error responses
--debugfalseEnable debug logging for API operations
--remote-onlyfalseOnly expose remote service session endpoints (health, services, search, session)
+

Configuration

+
    +
  • api_secret - Secret key for REST API authentication. Required unless --no-key is used. All API requests must include this key via the X-Secret-Key header.
  • +
  • compression_level - Compression level for API payloads (manifests, cache, cookies). 0=off, 1=fast, 6=balanced, 9=max. Default: 1.
  • +
  • session_ttl - Session inactivity timeout in seconds. Each request resets the timer. Default: 300.
  • +
  • max_sessions - Maximum concurrent sessions before the oldest is evicted. Default: 100.
  • +
  • services - Optional global service allowlist. Only these service tags are exposed. If omitted, all services are available.
  • +
  • devices - List of Widevine device files (.wvd). If not specified, auto-populated from the WVDs directory.
  • +
  • playready_devices - List of PlayReady device files (.prd). If not specified, auto-populated from the PRDs directory.
  • +
  • users - Dictionary mapping user secret keys to their access configuration:
  • +
  • devices - List of Widevine devices this user can access
  • +
  • playready_devices - List of PlayReady devices this user can access
  • +
  • username - Internal logging name for the user (not visible to users)
  • +
  • services - Optional per-user service allowlist. Effective access is the intersection of global and per-user allowlists.
  • +
+

Server-side dl defaults

+

Any key accepted by /api/download (see docs/API.md) can also be declared directly under serve: and the REST API will treat it as a default. Per-request bodies still win. Use this to raise concurrency, force best_available, etc. without each client repeating the values:

+
serve:
+  api_secret: "..."
+  users: { ... }
+  downloads: 4              # parallel tracks per job
+  workers: 16               # threads per track
+  best_available: true
+  no_proxy_download: false
+
+

Layering: built-in defaults < serve.* overrides < service-specific defaults < request body.

+

For example,

+
serve:
+  api_secret: "your-secret-key-here"
+  compression_level: 1
+  session_ttl: 300
+  max_sessions: 100
+  # services:           # global allowlist (optional)
+  #   - EXAMPLE1
+  #   - EXAMPLE2
+  users:
+    secret_key_for_jane: # 32bit hex recommended, case-sensitive
+      devices: # list of allowed Widevine devices for this user
+        - generic_nexus_4464_l3
+      playready_devices: # list of allowed PlayReady devices for this user
+        - my_playready_device
+      username: jane # only for internal logging, users will not see this name
+      # services:        # per-user allowlist (optional)
+      #   - EXAMPLE1
+    secret_key_for_james:
+      devices:
+        - generic_nexus_4464_l3
+      username: james
+  # devices can be manually specified by path if you don't want to add it to
+  # unshackle's WVDs directory for whatever reason
+  # devices:
+  #   - 'C:\Users\john\Devices\test_devices_001.wvd'
+  # playready_devices:
+  #   - '/path/to/device.prd'
+
+

REST API

+

When the server is running, interactive API documentation is available at:

+
    +
  • Swagger UI: http://localhost:8786/api/docs/
  • +
+

See API.md for full REST API documentation with endpoints, parameters, and examples.

+
+

max_concurrent_downloads (int)

+

Maximum number of /api/download jobs the serve queue manager will execute in parallel. Each job runs the full dl pipeline (authenticate, fetch tracks, decrypt, mux) in its own worker subprocess. This is independent of serve.downloads, which controls parallel tracks inside a single job. Default: 2.

+
max_concurrent_downloads: 4
+
+
+

download_job_retention_hours (int)

+

How long completed, failed, or cancelled download jobs remain queryable via /api/download/jobs/{job_id} before the periodic cleanup loop drops them. Default: 24.

+
download_job_retention_hours: 48
+
+
+

serve.history_limit (int)

+

How many terminal jobs to keep in the persistent history file (api_history.jsonl, stored under the cache directory so it stays out of the config/data tree). Older entries are dropped as new jobs finish. Set to 0 for unlimited. Default: 100.

+
serve:
+  history_limit: 250
+
+
+

debug (bool)

+

Enables comprehensive debug logging. Default: false

+

When enabled (either via config or the -d/--debug CLI flag): +- Sets console log level to DEBUG for verbose output +- Creates JSON Lines (.jsonl) debug log files with structured logging +- Logs detailed information about sessions, service configuration, DRM operations, and errors with full stack traces

+

For example,

+
debug: true
+
+
+

debug_keys (bool)

+

Controls whether actual decryption keys (CEKs) are included in debug logs. Default: false

+

When enabled: +- Content encryption keys are logged in debug output +- Only affects content_key and key fields (the actual CEKs) +- Key metadata (kid, keys_count, key_id) is always logged regardless of this setting +- Passwords, tokens, cookies, and session tokens remain redacted even when enabled

+

For example,

+
debug_keys: true
+
+
+

debug_requests (bool)

+

Controls whether HTTP-library logs are shown during --debug sessions. Default: false

+

When --debug is active and this is false, unshackle silences the noisy per-request logs emitted by urllib3, requests, rnet, httpx, and related libraries (e.g. GET ...seg-001.m4s 200) so that unshackle's own structured DEBUG output stays readable. Set to true to see every individual HTTP request, including per-segment downloads.

+

Has no effect when --debug is not active.

+
debug_requests: true
+
+
+

set_terminal_bg (bool)

+

Controls whether unshackle applies its theme background color to the terminal. Default: false

+

When enabled, the Rich console theme's background color is appended to the foreground styles for both the general color scheme and the ASCII art banner, so the full color palette renders correctly on terminals whose default background differs from the theme. Set to true if the banner or colored output looks off on your terminal.

+
set_terminal_bg: true
+
+
+

update_checks (bool)

+

Check for updates from the GitHub repository on startup. Default: true.

+
+

update_check_interval (int)

+

How often to check for updates, in hours. Default: 24.

+
+

title_cache_enabled (bool)

+

Enable or disable title metadata caching globally. Default: true.

+
+

title_cache_time (int)

+

Title cache duration in seconds. Default: 1800 (30 minutes).

+
+

title_cache_max_retention (int)

+

Maximum cache retention in seconds, used as fallback when the upstream API fails. Default: 86400 (24 hours).

+
+

unicode_filenames (bool)

+

When false, replaces non-ASCII characters in output filenames with ASCII equivalents. Default: false.

+
+

ipinfo_api_key (str)

+

Optional ipinfo.io token. When set, unshackle uses the ipinfo.io Lite endpoint for IP/geolocation lookups instead of the unauthenticated fallback.

+
+

tmdb_api_key (str)

+

Optional TMDB API key, used for metadata enrichment and IMDb/TMDb tagging.

+
+

simkl_client_id (str)

+

Optional Simkl client ID for metadata lookups.

+
+

decrypt_labs_api_key (str)

+

Optional Decrypt Labs API key, used by services that integrate with the service.

+
+ + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/API/index.html b/API/index.html new file mode 100644 index 00000000..0363ee6c --- /dev/null +++ b/API/index.html @@ -0,0 +1,1333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Overview - unshackle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ +
+ + + + + + + + +

REST API Documentation

+

The unshackle REST API allows you to control downloads, search services, drive remote downloads from a thin client, and (optionally) co-host the pywidevine/pyplayready CDM. Start the server with unshackle serve and access the interactive Swagger UI at http://localhost:8786/api/docs/.

+

The server is built on aiohttp (not FastAPI). Implementation lives in unshackle/commands/serve.py and unshackle/core/api/ (routes.py, handlers.py, session_store.py, input_bridge.py, download_manager.py, download_worker.py).

+ + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 00000000..db59a924 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +docs.unshackle.dev diff --git a/CREATING_A_SERVICE/index.html b/CREATING_A_SERVICE/index.html new file mode 100644 index 00000000..c8500869 --- /dev/null +++ b/CREATING_A_SERVICE/index.html @@ -0,0 +1,2120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Creating a Service - unshackle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+ +
+ + + + + + + + +

Creating a Service

+

This guide walks through writing a new service adapter, section by section, using the bundled +reference implementation at +unshackle/services/EXAMPLE/__init__.py +as the canonical example. EXAMPLE is deliberately exhaustive - it shows every framework feature +in one file so you have one place to copy from. It does not run against a real API.

+

File layout

+

A service is a directory under unshackle/services/ named by its tag (the short code used on +the CLI - EXAMPLE, or its aliases EX/DOMAIN, in the reference service). The directory needs:

+
unshackle/services/TAG/
+├── __init__.py     # required - the Service subclass
+└── config.yaml     # optional - endpoints, headers, client profiles
+
+

The class inside __init__.py must have the same name as the directory (class TAG(Service): +in services/TAG/__init__.py). unshackle/core/services.py discovers services by globbing +services/*/__init__.py and looking up a class named after the directory; a mismatch raises +no class named '<TAG>' found ... the class name must match the directory name at load time, so +the failure is loud, not silent.

+

Lifecycle at a glance

+

Before the method-by-method breakdown, here's the order the framework calls your service during a +download - your methods are the hooks, the framework drives the loop:

+
unshackle dl TAG <title>
+  └─ cli(ctx, title=...)            → constructs TAG(ctx, ...)
+       └─ __init__ → super().__init__(ctx)   builds session/cache/track_request, resolves geofence proxy
+  1. authenticate(cookies, credential)        login / token exchange (always called; base just loads cookies/credential)
+  2. get_titles()                              → Movies / Series / Album   (wrapped by get_titles_cached + title_map)
+     · framework applies episode/season filters
+  3. per title:
+       a. get_tracks(title)                    → Tracks   (parse manifest, post-process, store license data)
+       b. get_chapters(title)                  → Chapters
+       c. framework selects tracks             resolution / range / codec / language  (best_available softens misses)
+       d. per track: download → license callback → decrypt → repackage → mux → move to final filename
+
+

Two things follow from this order:

+
    +
  • get_tracks() runs before any track selection, so it must return everything; the framework + filters afterward in step ©.
  • +
  • The license callbacks (get_widevine_license etc.) fire in step (d), long after + get_tracks() returned - they only receive challenge, title, and track (nothing else from + get_tracks()), so anything they need (license URL, session token) must be stashed during + get_tracks().
  • +
+

Class docstring and class variables

+
class EXAMPLE(Service):
+    """
+    Reference service for domain.com - ...
+
+    Version: 2.0.0
+    Author: sp4rk.y
+    Date: 2026-06-28
+    Authorization: Cookies + Credentials
+    Geofence: US, UK
+    Robustness:
+        Widevine:
+            L1: 2160p, HDR10, HDR10+, DV
+            L3: 1080p, SDR
+        PlayReady:
+            SL3000: 2160p
+        ClearKey: 1080p (DRM-free fallback)
+
+    Tips:
+        - Input may be a full URL or a bare ID/slug
+    """
+
+    ALIASES = ("EX", "DOMAIN")
+    GEOFENCE = ("US", "UK")
+    TITLE_RE = r"^(?:https?://(?:www\.)?domain\.com/details/)?(?P<title_id>[^/?#]+)"
+    NO_SUBTITLES = False
+    VAULT_TAG = "DIFFERENT_NAME"
+
+

The docstring is the unshackle dl TAG --help output, so it follows Click's conventions:

+
    +
  • Keep Version, Author, date, Authorization, Geofence, and Robustness - they're the fields + users and other contributors check first when deciding whether a service still works.
  • +
+

Class variables, all optional except where noted:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VariablePurpose
ALIASES: tuple[str, ...]Extra CLI tags that resolve to this service, e.g. dl EX ...
GEOFENCE: tuple[str, ...]ISO country codes required; the framework warns/auto-proxies on mismatch
TITLE_RE: strRegex with named groups (e.g. (?P<title_id>...)) used in get_titles() to accept either a full URL or a bare ID
NO_SUBTITLES: boolConvention checked via hasattr by dl.py, not declared on the base class - set True if the service has no subtitle tracks, to skip subtitle handling
VAULT_TAG: Optional[str]Store/read keys under a different vault namespace than this service's own tag - lets sibling services (e.g. regional variants of the same backend) share one key vault
AUTH_METHODS: tuple[str, ...]Declares which of "cookies"/"credentials" the service accepts. Optional - when unset, unshackle serve's /services endpoint infers it by inspecting authenticate()'s source. Set it explicitly if that inference would guess wrong
+

See Service Integration & Authentication for the +full list of self.* helpers available once super().__init__(ctx) has run.

+

cli() - the Click entry point

+
@staticmethod
+@click.command(name="EXAMPLE", short_help="https://domain.com", help=__doc__)
+@click.argument("title", type=str)
+@click.option("-m", "--movie", is_flag=True, default=False, help="Treat the title as a movie.")
+@click.option(
+    "-d", "--device",
+    type=click.Choice(["android_tv", "web", "ios"], case_sensitive=False),
+    default="android_tv",
+    help="Client profile block to use from config.yaml.",
+)
+@click.pass_context
+def cli(ctx: click.Context, **kwargs: Any) -> EXAMPLE:
+    return EXAMPLE(ctx, **kwargs)
+
+

Keep name="EXAMPLE" in @click.command matching the class/directory name - resolution of +unshackle dl EXAMPLE ... is actually driven by the directory name matching the class name (see +File layout above), but a mismatched name= produces confusing --help/usage output. +help=__doc__ reuses the class docstring above as the --help text. Add @click.option entries here for anything a user should be able to toggle per +download (device/client profile, movie-vs-series disambiguation, region, etc.) - they arrive as +keyword arguments into __init__.

+

__init__()

+
def __init__(self, ctx: click.Context, title: str, movie: bool, device: str):
+    self.title = title
+    self.movie = movie
+    self.device = device
+
+    super().__init__(ctx)
+
+    self.cdm = ctx.obj.cdm
+    self.is_playready = is_playready_cdm(self.cdm)
+    self.is_widevine = is_widevine_cdm(self.cdm)
+
+    if self.is_playready:
+        self.device = "playready_tv"
+    elif self.is_widevine:
+        self.log.info(" + Widevine CDM detected")
+
+    # The active -p/--profile, used below to key per-profile token caches.
+    self.profile = (ctx.parent.params.get("profile") if ctx.parent else None) or "default"
+
+    if any(r != Video.Range.SDR for r in self.track_request.ranges):
+        self.track_request.codecs = [Video.Codec.HEVC]
+
+    if self.is_widevine and getattr(self.cdm, "security_level", None) == 3:
+        self.track_request.codecs = [Video.Codec.AVC]
+        self.track_request.ranges = [Video.Range.SDR]
+
+    if self.config is None:
+        raise EnvironmentError("config.yaml is missing for this service.")
+
+

Order matters here:

+
    +
  1. Store CLI args before super().__init__(ctx) only if base-class init needs them; otherwise + it doesn't matter, but storing first is the convention every service follows.
  2. +
  3. super().__init__(ctx) wires up self.config, self.log, self.session, self.cache, + self.title_cache, self.current_region, and builds self.track_request from the global dl + flags. Nothing below this line works without it. (self.request_input is a base-class method, + always available - it isn't assigned here.)
  4. +
  5. After that, inspect ctx.obj.cdm and classify it with is_playready_cdm/is_widevine_cdm + (unshackle.core.cdm.detect) - never hand-roll isinstance checks, these helpers also handle + remote/wrapper CDMs.
  6. +
  7. Read or rewrite self.track_request for service-level constraints: e.g. force HEVC when any + non-SDR range was requested, or clamp a Widevine L3 box down to AVC/SDR.
  8. +
+

self.track_request fields: codecs: list[Video.Codec] (empty = no preference), +ranges: list[Video.Range] (defaults to [SDR]), best_available: bool. Read codec/range +preferences from self.track_request, not from ctx.parent.params directly - it's the same data, +but track_request is what _get_tracks_for_variants() and the rest of the framework consult.

+

self.session and get_session()

+

super().__init__(ctx) sets self.session = self.get_session(). The base get_session() is a +plain requests.Session with config.headers applied and a retry adapter mounted (5 retries, +backoff on 429/500/502/503/504) - it does not use rnet/TLS-impersonation by default. If a +service needs TLS fingerprinting to get past bot detection, override get_session():

+
from unshackle.core.session import session
+
+@staticmethod
+def get_session() -> RnetSession:
+    return session("Chrome131")  # any rnet.Impersonate preset name, e.g. OkHttp4_12, Edge101, Safari18
+
+

RnetSession is a drop-in requests.Session replacement (same .get/.post/cookie jar +interface), so nothing else in the service needs to change. For custom SSL cipher requirements +instead, mount unshackle.core.utils.sslciphers.SSLCiphers on a plain requests.Session the same +way.

+

authenticate()

+
def authenticate(self, cookies: Optional[CookieJar] = None, credential: Optional[Credential] = None) -> None:
+    super().authenticate(cookies, credential)
+    self.session.headers.update({"user-agent": self.config["client"][self.device]["user_agent"]})
+
+    cache = self.cache.get(f"tokens_{self.device}_{self.profile}")
+    if cache and cache.data.get("expires_in", 0) > int(datetime.now().timestamp()):
+        self.log.info(" + Using cached tokens")
+    elif cache and cache.data.get("refresh_token"):
+        refresh = self.session.post(url=self.config["endpoints"]["refresh"], data=...).json()
+        cache.set(data=refresh, expiration=refresh.get("expires_in"))
+    else:
+        if cookies:
+            ...  # decode a JWT cookie
+        elif credential:
+            otp = self.request_input("Enter the OTP sent to your device: ")
+            ...
+        else:
+            raise EnvironmentError("Service requires either Cookies or Credentials.")
+        token = self.session.post(url=self.config["endpoints"]["login"], data=body).json()
+        cache.set(data=token, expiration=token.get("expires_in"))
+
+    self.token = cache.data["token"]
+
+

Skip this method entirely for services that need no auth at all. Otherwise:

+
    +
  • Always call super().authenticate(cookies, credential) first - it loads the cookie jar into + self.session and stores self.credential.
  • +
  • Cache tokens with self.cache.get(key) / cache.set(data, expiration=seconds). Key the cache + by whatever varies per session (device, profile, or credential.sha1) so multiple profiles + don't collide.
  • +
  • Use self.request_input(prompt) instead of bare input() for anything interactive (OTP, 2FA + codes) - locally it prompts via the shared Rich console (console.input, so the prompt renders + correctly alongside progress/log output), but under serve the attached InputBridge relays the + prompt to the remote client instead of hanging the server.
  • +
  • Raise EnvironmentError if auth is required but neither cookies nor a credential were given.
  • +
  • Don't hold onto the raw Credential object past this method - store the derived token instead.
  • +
+ +
def search(self) -> Generator[SearchResult, None, None]:
+    results = self.session.get(url=self.config["endpoints"]["search"], params={"q": self.title}).json()
+    for result in results["entries"]:
+        yield SearchResult(
+            id_=result["id"],
+            title=result["title"],
+            description=result.get("description"),
+            label="SERIES" if result["programType"] == "series" else result["programType"].upper(),
+            url=result.get("url"),
+        )
+
+

Optional - implement it to support unshackle search TAG <query>. A generator yielding +SearchResult objects; nothing else consumes its return value, so don't collect to a list first.

+

get_titles()

+
def get_titles(self) -> Titles_T:
+    match = re.match(self.TITLE_RE, self.title)
+    if not match:
+        raise ValueError("Could not parse a title ID - is the URL/ID correct?")
+    title_id = match.group("title_id")
+
+    metadata = self.session.get(url=self.config["endpoints"]["metadata"].format(title_id=title_id)).json()
+    original_lang = Language.find(metadata["languages"][0])
+
+    if self.movie or metadata["programType"] == "movie":
+        return Movies([
+            Movie(
+                id_=metadata["id"],
+                service=self.__class__,
+                name=metadata["title"],
+                year=metadata.get("releaseYear") or None,
+                language=original_lang,
+                data=metadata,
+            )
+        ])
+
+    episodes = [...]  # flatten seasons into Episode(...) the same way
+    return Series(episodes)
+
+

Required. Returns Movies, Series, or (for music) Album - never a raw list.

+
    +
  • Parse self.title with TITLE_RE first; raise ValueError with a clear message if it doesn't + match.
  • +
  • language= on each Movie/Episode/Song must be the title's original recorded language, + not the user's preferred language - it's the source of truth to_tracks() uses later to flag + is_original_lang on every track, which drives -l best/all selection and the "Original" audio + label.
  • +
  • service=self.__class__, not service=self - a common copy-paste mistake.
  • +
  • Pass the full API response through data= on the title. get_tracks() and get_chapters() + read it back as title.data - this is the standard way to avoid a second request later.
  • +
+

get_tracks()

+

Two shapes, depending on what the service's API gives you:

+

Pattern A - separate manifest per codec/range. Use the _get_tracks_for_variants() helper:

+
def get_tracks(self, title: Title_T) -> Tracks:
+    def _fetch_variant(title, codec, range_):
+        vcodec_str = "H265" if codec == Video.Codec.HEVC else "H264"
+        return self._fetch_dash_manifest(title, vcodec=vcodec_str, range_=range_)
+    return self._get_tracks_for_variants(title, _fetch_variant)
+
+

The helper walks every codec x range combination from self.track_request, handles HYBRID +(fetches HDR10 and DV separately and merges them), and - when --best-available is set - skips +combos the service can't deliver instead of raising.

+

Pattern B - one manifest contains every variant. Just parse and return; dl.py filters by +the user's selection:

+
def get_tracks(self, title: Title_T) -> Tracks:
+    playback = self.session.get(url=self.config["endpoints"]["playback"].format(title_id=title.id)).json()
+    return HLS.from_url(url=playback["manifest_url"], session=self.session).to_tracks(language=title.language)
+
+

ISM (Microsoft Smooth Streaming) follows the same shape as HLS here.

+

Whichever pattern you use, the manifest fetch is also where you post-process tracks before +returning them - see _fetch_dash_manifest in EXAMPLE for the full set of fixups:

+
    +
  • Store DRM/license data on self (e.g. self.license_data = {...}) or on title.data - + get_widevine_license() only receives title and track, not the manifest response, so this + is the only place to capture license URLs/tokens.
  • +
  • Stamp video.range - manifests can't always be trusted to signal range correctly; set it + explicitly to what you asked for.
  • +
  • Flip HDR10 → HDR10+ when the service is known to embed HDR10+ SEI but only labels HDR10 in + the manifest - this is a bitstream feature the HLS parser intentionally does not infer.
  • +
  • Set video.dv_compatible_bitstream = True for HEVC tracks carrying DV RPU NALs in a + container that only signals plain HEVC - DVFixup then round-trips the bitstream through + dovi_tool before mux so the output is recognised as Dolby Vision.
  • +
  • Drop decoy renditions (e.g. unencrypted "clear" copies some APIs return alongside the real + ones).
  • +
  • Mark descriptive audio from the manifest's Role/label data (audio.descriptive = True).
  • +
  • Set audio.joc when the API tells you a track is Dolby Atmos explicitly (the framework + also detects JOC itself from the bitstream).
  • +
  • Build subtitles by hand when the manifest's own subtitle list is wrong or incomplete - in + that case compute is_original_lang yourself with is_close_match(sub_lang, [title.language]), + the same helper to_tracks() uses internally.
  • +
  • Add cover art as an Attachment if the API provides one.
  • +
+
def get_tracks(self, title: Title_T) -> Tracks:
+    # ... never filter by resolution/codec/language here - return everything,
+    # the framework applies the user's selection after this returns.
+
+

This is the single most common service bug: filtering inside get_tracks(). Don't.

+

get_chapters()

+
def get_chapters(self, title: Title_T) -> Chapters:
+    chapters = []
+    seen = set()
+    for marker in title.data.get("markers", []):
+        ts = marker["start"]            # seconds, ms, or "HH:MM:SS.mmm"
+        if ts <= 0 or ts in seen:       # skip a marker at 0 and any duplicate timestamp
+            continue
+        seen.add(ts)
+        chapters.append(Chapter(timestamp=ts, name=marker.get("label")))
+    return Chapters(chapters)
+
+

Return Chapters() (or []) if the service has no chapter data - don't skip implementing the +method. Timestamps accept float seconds, int milliseconds, or "HH:MM:SS.mmm" strings. Prefer +descriptive names ("Intro", "End Credits") over generic ones ("Chapter 01").

+

The opening chapter at 00:00:00.000 is created for you - never add your own. The moment you +add the first Chapter, Chapters.add() injects a Chapter(0) at the start if one isn't already +present, so every file gets an opening chapter automatically. Two consequences you must handle:

+
    +
  • Don't add a "Chapter 1" at timestamp 0 yourself - it's redundant with the auto-inserted one.
  • +
  • Guard against duplicate timestamps. Chapters.add() raises ValueError if a chapter already + exists at that exact timestamp, so a marker the API reports at 0 (an intro/recap that starts at + the very beginning) collides with the auto-inserted opening chapter, and two markers at the same + time collide with each other. Skip any marker at 0 and de-duplicate timestamps before adding, + as the seen set above does.
  • +
+

A Chapter with no name is valid - it inserts an unnamed chapter break at that timestamp, which +is the standard way to close out a named range (e.g. mark the end of an "Intro" so the next chapter +boundary lands there).

+

DRM license methods

+
def get_widevine_service_certificate(self, **_: Any) -> Optional[str]:
+    return self.config.get("certificate")  # or None to use the common privacy cert
+
+def get_widevine_license(self, *, challenge: bytes, title: Title_T, track: AnyTrack) -> Optional[Union[bytes, str]]:
+    license_url = self.license_data.get("url") or self.config["endpoints"].get("widevine_license")
+    response = self.session.post(url=license_url, data=challenge, ...)
+    response.raise_for_status()
+    return response.content
+
+def get_playready_license(self, *, challenge: bytes, title: Title_T, track: AnyTrack) -> Optional[Union[bytes, str]]:
+    response = self.session.post(url=self.config["endpoints"]["playready_license"], data=challenge)
+    response.raise_for_status()
+    return response.content
+
+def get_clearkey_license(self, *, challenge: bytes, title: Title_T, track: AnyTrack) -> Optional[Union[bytes, str, dict]]:
+    license_url = self.config["endpoints"].get("clearkey_license")
+    if not license_url:
+        return None  # fall back to the manifest-provided Laurl, if any
+    response = self.session.post(url=license_url, data=challenge)
+    response.raise_for_status()
+    return response.json()
+
+
    +
  • get_widevine_service_certificate() - return the service's privacy cert string to enable + privacy-mode license requests, or None to use the common cert.
  • +
  • get_widevine_license() - POST challenge (raw CDM challenge bytes) to the license endpoint, + return the raw license (bytes), or JSON-unwrap it first if the service wraps it + (response.json()["license"]). track is passed in case the service rotates keys per segment + and you need its per-segment PSSH.
  • +
  • get_playready_license() - same shape as Widevine. If omitted, the base class delegates to + get_widevine_license(), so only implement it when the service has a genuinely separate + PlayReady endpoint.
  • +
  • get_clearkey_license() - only for DASH org.w3.clearkey (W3C EME). challenge is the W3C + JSON license request; return the JWK Set as dict, str, or bytes. Three integration levels, + simplest first:
      +
    1. Manifest carries a <Laurl> - implement nothing; the framework POSTs there itself.
    2. +
    3. Custom endpoint/headers - override this method as shown above.
    4. +
    5. Obfuscated/bespoke key delivery - fetch and unwrap the key yourself, then pre-populate + drm.content_keys[kid] = key_hex on the track's ClearKeyCENC during get_tracks(); the + framework skips the license round-trip entirely when every KID is already keyed.
    6. +
    +
  • +
  • HLS AES-128 ClearKey and fully unencrypted content have no license callback at all - the key + comes straight from the manifest or a side endpoint and is attached to the track's DRM object + directly.
  • +
+

Other extension points

+

A handful of base-class hooks aren't exercised by EXAMPLE because it has nothing interesting to +do in them, but are there when you need them:

+
    +
  • Event hooks - no-ops on the base class, fire during the download pipeline if overridden: + on_segment_downloaded(track, segment: Path), on_track_downloaded(track), + on_track_decrypted(track, drm, segment=None), on_track_repacked(track), + on_track_multiplex(track). Useful for progress reporting or service-specific cleanup tied to + pipeline stages.
  • +
  • resolve_server_keys(title) - not declared on Service at all; dl.py checks for it with + hasattr and calls it if present. Use it for services where decryption keys need to be resolved + server-side ahead of the normal license flow, separate from the per-track license callbacks.
  • +
  • get_titles_cached() / apply_title_map() - base-class wrappers around your get_titles() + that add the title-metadata cache and title_map config rewriting (see + title_map). dl.py calls get_titles_cached(), not + get_titles() directly - implement get_titles(), don't override the cached wrapper.
  • +
+

config.yaml

+
endpoints:
+  base_url: "https://api.example.com"
+  content: "https://api.example.com/content"
+  search: "https://api.example.com/search"
+  license: "https://license.example.com/v1"
+headers:
+  User-Agent: "..."
+client:
+  id: "..."
+device:
+  platform_name: "..."
+
+

No enforced schema - self.config is just whatever YAML you put here, merged with any +services.<TAG> overrides from the user's unshackle.yaml (see +Service Integration & Authentication). Conventions:

+
    +
  • All URLs live here, never hardcoded in __init__.py. Use {} or {name} placeholders for + dynamic segments and .format(...) at the call site.
  • +
  • Treat this file as shared/checked-in - don't put per-user secrets in it. Sensitive, + user-specific values (API keys, device IDs) belong in the user's unshackle.yaml under + services.<TAG>, which gets merged into self.config at runtime.
  • +
  • client:/device: blocks are a common idiom for services with multiple device profiles + (web/android_tv/ios) - index into them with whatever -d/--device option you exposed in cli().
  • +
+

Use -d/--debug (before the subcommand) for structured JSONL logging while iterating - see +Structured Debug Logging.

+ + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/DEBUG_LOGGING/index.html b/DEBUG_LOGGING/index.html new file mode 100644 index 00000000..3e0a44e3 --- /dev/null +++ b/DEBUG_LOGGING/index.html @@ -0,0 +1,1565 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Debug Logging - unshackle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+ +
+ + + + + + + + +

Structured debug logging

+

unshackle emits structured JSON Lines (JSONL) when -d/--debug (a global flag, before the +subcommand: unshackle -d dl ...) or config.debug is set. Output lands at +config.directories.logs / unshackle_debug_<service>_<time>.jsonl. The logger is built for +developers troubleshooting pipeline flow — maximum signal, minimum noise — not end users.

+

Adding logging to a new feature

+

Use the two primitives from unshackle.core.utilities. Both are no-ops when debug logging is +disabled, so they are always safe to call unguarded — no if debug_logger: needed.

+
from unshackle.core.utilities import log_event, timed_operation
+
+# One-shot event:
+log_event("myfeature_done", message="Did the thing", context={"count": n})
+
+# Time a block (logs once at the end with duration_ms; ERROR + exception if it raises):
+with timed_operation("myfeature_run", context={"input": str(path)}):
+    do_the_work()
+
+

That's it. Do not write if dl := get_debug_logger(): dl.log(...) in new code — log_event +replaces that boilerplate.

+

External tools (ffmpeg, mkvmerge, dovi_tool, …)

+

Route binary calls through the helpers in unshackle.core.utils.subprocess:

+
    +
  • run_step(args, *, label=..., output=...) — runs a CLI step and auto-logs a tool_run + entry (label, tool, returncode, duration). Prefer this for new tool calls.
  • +
  • ffprobe(uri) — auto-logs its tool_run.
  • +
  • log_tool_run(label, tool, returncode, *, duration_ms=..., **ctx) — for a direct + subprocess.run you can't route through run_step; call it right after the process returns.
  • +
+

Conventions

+
    +
  • Operation names: <area>_<event> lowercase, e.g. manifest_dash_parse, drm_decrypt, + mux_complete, vault_get_key, tool_run. Names are plain inline strings (no central registry).
  • +
  • Levels = the flow skeleton. One INFO milestone per stage (a dev runs + jq 'select(.level=="INFO")' to read the end-to-end flow); internals at DEBUG; failures at + ERROR. Keep INFO sparse.
  • +
  • Every entry carries a one-sentence message that reads on its own; structured data + (context, duration_ms, counts, ids) lives in fields, not prose.
  • +
  • No raw dumps. Counts, ids, sizes, and safe_display_url(url) only — never a full Tracks, + MPD/manifest body, or response payload.
  • +
  • Secrets, URLs & paths. Every logged string passes through redact_all = + redact_text (mask password/token/secret/auth/cookie keys + URL userinfo/secret query params) + → redact_url (collapse any http(s) URL to redacted[.ext], hiding CDN/content/manifest/api + locations while keeping the extension, e.g. redacted.mpd) → redact_path (strip local base + dirs: install root → <unshackle>, venv → <venv>, home → ~). key fields are also redacted + unless config.debug_keys. Pass user data via context=/request=/kwargs so it is sanitized. + Net effect: a shared JSONL leaks no account URLs, machine paths, or usernames. + Set redact_paths: false in unshackle.yaml to show full absolute paths instead of <unshackle>/ + <venv>/~ tokens (config-only, no CLI flag). Affects redact_path everywhere it is used — + the debug log and the service load-summary detail. Secret/URL redaction is unaffected.
  • +
  • Service calls are intentionally not logged (no per-request POST/GET to services). Manifest + parsing (manifest_*_parse) is the seam for request-level visibility.
  • +
+

Reading the output

+
# Flow skeleton (one line per milestone):
+jq -r 'select(.level=="INFO") | "\(.operation)\t\(.message)"' unshackle/logs/unshackle_debug_*.jsonl
+
+# Everything for one correlated operation:
+jq 'select(.operation_id=="abc12345")' <log>
+
+

Primitives reference

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SymbolModulePurpose
log_event(op, *, level, message, **ctx)core.utilitiesone-shot structured entry
timed_operation(op, *, level, message, **ctx)core.utilitiescontext manager; logs once at end with duration_ms (ERROR on raise)
DebugLogger.log_drm_operation / log_vault_query / log_service_callcore.utilitiestyped convenience wrappers (accept message=/level= overrides)
run_step / ffprobe / log_tool_runcore.utils.subprocessexternal-tool tool_run logging
get_debug_logger()core.utilitieslow-level accessor (rarely needed directly)
+ + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/DOWNLOAD_CONFIG/index.html b/DOWNLOAD_CONFIG/index.html new file mode 100644 index 00000000..df0338fd --- /dev/null +++ b/DOWNLOAD_CONFIG/index.html @@ -0,0 +1,2090 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Download & Processing - unshackle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+ +
+ + + +
+ +
+ + + + + + + + +

Download & Processing Configuration

+

This document covers configuration options related to downloading and processing media content.

+

downloader

+

unshackle ships a single unified downloader at unshackle/core/downloaders/requests.py. The legacy +aria2c, curl_impersonate, and n_m3u8dl_re backends have been removed; their config blocks no +longer have any effect.

+

The unified downloader:

+
    +
  • Works with both a standard requests.Session and RnetSession (rnet/BoringSSL TLS impersonation, + which replaces the previous curl_cffi backend). When a service exposes its own session via + self.session, TLS fingerprinting is preserved on every segment.
  • +
  • Uses adaptive chunk sizing between 512 KB and 4 MB, picked from the response Content-Length.
  • +
  • Spawns up to min(16, cpu_count + 4) worker threads by default for segmented downloads + (override via --workers / dl.workers).
  • +
  • Resumes interrupted downloads via HTTP Range requests (a sibling <file>.!dev control file + marks an in-progress download).
  • +
  • Has a single-URL fast path: if the server supports byte ranges and the file is at least 64 MB, + the file is split into 16 MB parts and downloaded in parallel into a pre-allocated file.
  • +
  • Is selected per-track via track.downloader, which defaults to this unified requests downloader.
  • +
+

There is no downloader: config key to set anymore. Setting one to a legacy value will emit a +DeprecationWarning and otherwise be ignored.

+
+

dl (dict)

+

Pre-define default options and switches of the dl command. +The values will be ignored if explicitly set in the CLI call.

+

The Key is the option name with dashes as underscores (--v-lang -> v_lang). range and list +work as-is; their internal Python names (range_, list_, suffixed to avoid the builtins) are +also accepted.

+

For example to set the default primary language to download to German,

+
lang: de
+
+

You can also set multiple preferred languages using a list, e.g.,

+
lang:
+  - en
+  - fr
+
+

to set how many tracks to download concurrently to 4 and download threads to 16,

+
downloads: 4
+workers: 16
+
+

to set --bitrate=CVBR for a specific service,

+
lang: de
+EXAMPLE:
+  bitrate: CVBR
+
+

or to change the output subtitle format from the default (original format) to WebVTT,

+
sub_format: vtt
+
+

All Available dl Keys

+

Below is a comprehensive list of keys that can be pre-defined in the dl section. Each corresponds +to a CLI option on the dl command. CLI arguments always take priority over config values.

+

Quality and codec:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyTypeDefaultDescription
qualityint or listbestResolution(s) to download (e.g., 1080, [1080, 2160])
vcodecstr or listanyVideo codec(s): H264, H265, VP9, AV1, VC1
acodecstr or listanyAudio codec(s): AAC, AC3, EC3, AC4, OPUS, FLAC, ALAC, DTS
vbitrateinthighestVideo bitrate in kbps
abitrateinthighestAudio bitrate in kbps
vbitrate_rangestrnoneVideo bitrate window in kbps, format MIN-MAX (e.g., 6000-7000)
abitrate_rangestrnoneAudio bitrate window in kbps, format MIN-MAX
real_video_bitrateboolfalseProbe actual media size to compute true video bitrates, overriding the manifest's declared value (-rvb). See Real bitrate probing
real_audio_bitrateboolfalseSame as above for audio tracks (-rab). Slower than video (more renditions)
range (or range_)str or listSDRColor range(s): SDR, HDR10, HDR10+, HLG, DV, HYBRID
channelsfloatanyAudio channels (e.g., 5.1, 7.1)
worstboolfalseSelect the lowest bitrate track within the specified quality. Requires quality
best_availableboolfalseContinue if requested quality is unavailable
+

Language:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyTypeDefaultDescription
langstr or listorigLanguage for video and audio (orig = original language)
v_langlist[]Language override for video tracks only
a_langlist[]Language override for audio tracks only
s_langlist["all"]Language for subtitles
require_subslist[]Required subtitle languages (skip title if missing)
forced_subsboolfalseInclude forced subtitle tracks
exact_langboolfalseExact language matching (no regional variants)
latest_episodeboolfalseDownload only the single most recent episode of a series
+

Track selection:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyTypeDefaultDescription
video_onlyboolfalseOnly download video tracks
audio_onlyboolfalseOnly download audio tracks
subs_onlyboolfalseOnly download subtitle tracks
chapters_onlyboolfalseOnly download chapters
no_videoboolfalseSkip video tracks
no_audioboolfalseSkip audio tracks
no_subsboolfalseSkip subtitle tracks
no_chaptersboolfalseSkip chapters
no_atmosboolfalseExclude Dolby Atmos audio tracks
audio_descriptionboolfalseInclude audio description tracks
+

Output and tagging:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyTypeDefaultDescription
tagstrconfig defaultOverride group tag
repackboolfalseAdd REPACK tag to output filename
sub_formatstroriginalOutput subtitle format: srt, vtt, ass, ssa, ttml
no_folderboolfalseDisable folder creation for TV shows
no_sourceboolfalseRemove source tag from filename
no_muxboolfalseDo not mux tracks into a container file
split_audioboolfalseCreate separate output files per audio codec
exportboolfalseWrite a JSON sidecar with manifest URLs, subtitles, per-track KID:KEY, codec/track info
+

Metadata enrichment:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyTypeDefaultDescription
tmdb_idintnullUse specific TMDB ID for tagging
imdb_idstrnullUse specific IMDB ID (e.g., tt1375666)
animeapi_idstrnullAnime database ID via AnimeAPI (e.g., mal:12345, anilist:98765)
enrichboolfalseOverride show title and year from external source. Requires tmdb_id, imdb_id, or animeapi_id
+

Download behavior:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyTypeDefaultDescription
downloadsint1Concurrent track downloads
workersintmin(16, cpu_count + 4)Max threads per track download (segments / ranged parts)
slowbool or MIN-MAXfalseRandomized delay between titles. true uses 60-120s; pass MIN-MAX (e.g., 20-40) for a custom range
no_proxy_downloadboolfalseBypass proxy for segment downloads only. Manifest, license, and auth still use proxy
skip_dlboolfalseSkip download, only get decryption keys
cdm_onlyboolnullOnly use CDM (true) or only vaults (false)
+

Real bitrate probing

+

Some services declare inaccurate bandwidth/BANDWIDTH in their manifests — often +a peak or nominal figure that is far from the real average. Because track.bitrate +drives the track listing, sorting, and --vbitrate / --vbitrate-range selection, +a wrong value picks the wrong track.

+

-rvb / --real-video-bitrate (and -rab / --real-audio-bitrate for audio) +probe the actual media size and overwrite track.bitrate with the measured value +(bytes * 8 / duration) before listing and selection. So -rvb --list shows the +true numbers, and -rvb --vbitrate-range 6000-7000 selects against them. Without +the flag, behaviour is unchanged (the manifest value is used).

+

How it works:

+
    +
  • Single-file tracks (one whole file per rendition — e.g. DASH SegmentBase + or services that collapse to a BaseURL) are measured exactly: the whole + file size over the track duration.
  • +
  • Multi-segment tracks (most HLS) are a sampled estimate — a spread of + segments is probed and extrapolated, typically within a few percent. Segment + bytes include container overhead, so MPEG-TS HLS reads a few percent above the + demuxed stream (this is the real delivered size).
  • +
  • Only the top renditions per quality tier are probed (video grouped by + codec + range, audio by codec + channels + language), in parallel, then extended + downward only as far as needed to keep ranking correct. This keeps the pass fast + even when a service exposes dozens of renditions.
  • +
  • Tracks whose duration cannot be determined fall back to ffprobe; probe failures + are non-fatal and leave the manifest bitrate in place.
  • +
+

Per-track before→after values are logged at debug level (run with -d); the +corrected values always appear in the Available Tracks panel.

+

You can also set per-service dl overrides under the services section (see +Service Integration & Authentication Configuration). Per-service values beat the +global dl: section; explicit CLI arguments beat both:

+
dl:
+  lang: en
+  downloads: 4
+  workers: 16
+
+services:
+  EXAMPLE:
+    dl:
+      v_lang: [en]
+      quality: 1080
+
+
+

audio (dict)

+

Configuration for audio track selection.

+
    +
  • codec_priority + Optional list of audio codec names defining the preferred order when multiple audio + tracks share the same bitrate and language. Listed codecs are ranked in the order given. + Codecs not in the list retain their bitrate-based ordering and are placed after all + listed codecs (i.e. soft priority — nothing is dropped).
  • +
+

Atmos tracks still take precedence over codec priority, and audio description tracks + are still moved to the end.

+

Valid codec names: AAC, AC3, EC3, AC4, OPUS, OGG, DTS, ALAC, FLAC.

+

For example,

+
audio:
+  codec_priority: [FLAC, ALAC, AC4, EC3, DTS, AC3, OPUS, AAC, OGG]
+
+

Or to only prefer a subset (e.g. surround codecs first, everything else falls back to +bitrate order):

+
audio:
+  codec_priority: [EC3, DTS, AC3, AAC]
+
+

When unset, audio tracks are sorted by bitrate alone (with Atmos/descriptive rules still +applied).

+
+

subtitle (dict)

+

Configuration for subtitle processing and conversion.

+
    +
  • conversion_method + Method to use for converting subtitles between formats. Default: "auto"
  • +
  • "auto" — Smart routing: uses subby for WebVTT/SAMI, pycaption for others.
  • +
  • "subby" — Always use subby with advanced processing.
  • +
  • "pycaption" — Use only pycaption library (no SubtitleEdit, no subby).
  • +
  • "subtitleedit" — Prefer SubtitleEdit when available, fall back to pycaption.
  • +
  • "pysubs2" — Use pysubs2 library (supports SRT/SSA/ASS/WebVTT/TTML/SAMI/MicroDVD/MPL2/TMP).
  • +
  • sdh_method + Method to use for SDH (hearing impaired) stripping. Default: "auto"
  • +
  • "auto" — Try subby (SRT only), then SubtitleEdit (if available), then subtitle-filter.
  • +
  • "subby" — Use subby library (SRT only).
  • +
  • "subtitleedit" — Use SubtitleEdit tool (Windows only, falls back to subtitle-filter).
  • +
  • "filter-subs" — Use subtitle-filter library directly.
  • +
  • strip_sdh + Automatically create stripped (non-SDH) versions of SDH subtitles. Default: true
  • +
  • convert_before_strip + Auto-convert VTT/other formats to SRT before using subtitle-filter for SDH stripping. + Ensures compatibility when subtitle-filter is used as fallback. Default: true
  • +
  • preserve_formatting + Preserve original subtitle formatting (tags, positioning, styling). + When true, skips pycaption processing for WebVTT files to keep tags like <i>, <b>, + positioning intact. Combined with no sub_format setting, ensures subtitles remain in + their original format. Default: true
  • +
  • output_mode + Output mode for subtitles. Default: "mux"
  • +
  • "mux" — Embed subtitles in MKV container only.
  • +
  • "sidecar" — Save subtitles as separate files only.
  • +
  • "both" — Embed in MKV and save as sidecar files.
  • +
  • sidecar_format + Format for sidecar subtitle files when output_mode is "sidecar" or "both". Default: "srt" + Options: srt, vtt, ass, original (keep current format).
  • +
+

For example,

+
subtitle:
+  conversion_method: auto
+  sdh_method: auto
+  strip_sdh: true
+  convert_before_strip: true
+  preserve_formatting: true
+  output_mode: mux
+  sidecar_format: srt
+
+
+

decryption (str | dict)

+

Choose what software to use to decrypt DRM-protected content throughout unshackle where needed. +You may provide a single decryption method globally or a mapping of service tags to +decryption methods.

+

Options:

+ +

Note that Shaka Packager is the traditional method and works with most services. mp4decrypt +is an alternative that may work better with certain services that have specific encryption formats.

+

Example mapping:

+
decryption:
+  EXAMPLE: mp4decrypt
+  EXAMPLE2: shaka
+  default: shaka
+
+

The default entry is optional. If omitted, shaka will be used for services not listed.

+

Simple configuration (single method for all services):

+
decryption: mp4decrypt
+
+
+ + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/DRM_CONFIG/index.html b/DRM_CONFIG/index.html new file mode 100644 index 00000000..492153fa --- /dev/null +++ b/DRM_CONFIG/index.html @@ -0,0 +1,2315 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DRM & CDM - unshackle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+ +
+ + + + + + + + +

DRM & CDM Configuration

+

This document covers Digital Rights Management (DRM) and Content Decryption Module (CDM) configuration options.

+

cdm (dict)

+

Pre-define which Widevine or PlayReady device to use for each Service by Service Tag as Key (case-insensitive). +The value should be a WVD or PRD filename without the file extension. When +loading the device, unshackle will look in both the WVDs and PRDs directories +for a matching file.

+

For example,

+
EXAMPLE: chromecdm_903_l3
+EXAMPLE2: nexus_6_l1
+
+

You may also specify this device based on the profile used.

+

For example,

+
EXAMPLE: chromecdm_903_l3
+EXAMPLE2: nexus_6_l1
+EXAMPLE3:
+  john_sd: chromecdm_903_l3
+  jane_uhd: nexus_5_l1
+
+

You can also specify a fallback value to predefine if a match was not made. +This can be done using default key. This can help reduce redundancy in your specifications.

+

For example, the following has the same result as the previous example, as well as all other +services and profiles being pre-defined to use chromecdm_903_l3.

+
EXAMPLE2: nexus_6_l1
+EXAMPLE3:
+  jane_uhd: nexus_5_l1
+default: chromecdm_903_l3
+
+

You can also select CDMs based on video resolution using comparison operators (>=, >, <=, <) +or exact match on the resolution height.

+

For example,

+
EXAMPLE:
+  "<=1080": generic_android_l3   # Use L3 for 1080p and below
+  ">1080": nexus_5_l1            # Use L1 for above 1080p (1440p, 2160p)
+  default: generic_android_l3    # Fallback if no quality match
+
+

You can mix profiles and quality thresholds in the same service:

+
EXAMPLE:
+  john: example_l3_profile       # Profile-based selection
+  "<=720": example_mobile_l3     # Quality-based selection
+  "1080": example_standard_l3    # Exact match for 1080p
+  ">=1440": example_premium_l1   # Quality-based selection
+  default: example_standard_l3   # Fallback
+
+
+

remote_cdm (list[dict])

+

Configure remote CDM (Content Decryption Module) APIs to use for decrypting DRM-protected content. +Remote CDMs allow you to use high-security CDMs (L1/L2 for Widevine, SL2000/SL3000 for PlayReady) without +having the physical device files locally.

+

unshackle supports multiple types of remote CDM providers:

+
    +
  1. DecryptLabs CDM - Official DecryptLabs KeyXtractor API with intelligent caching
  2. +
  3. Custom API CDM - Highly configurable adapter for any third-party CDM API
  4. +
  5. Legacy PyWidevine Serve - Standard pywidevine serve-compliant APIs
  6. +
+

The name of each defined remote CDM can be referenced in the cdm configuration as if it was a local device file.

+

DecryptLabs Remote CDM

+

DecryptLabs provides a professional CDM API service with support for multiple device types and intelligent key caching.

+

Supported Devices: +- Widevine: ChromeCDM (L3), L1 (Security Level 1), L2 (Security Level 2) +- PlayReady: SL2 (SL2000), SL3 (SL3000)

+

Configuration:

+
remote_cdm:
+  # Widevine L1 Device
+  - name: decrypt_labs_l1
+    type: decrypt_labs              # Required: identifies as DecryptLabs CDM
+    device_name: L1                 # Required: must match exactly (L1, L2, ChromeCDM, SL2, SL3)
+    host: https://keyxtractor.decryptlabs.com
+    secret: YOUR_API_KEY            # Your DecryptLabs API key
+
+  # Widevine L2 Device
+  - name: decrypt_labs_l2
+    type: decrypt_labs
+    device_name: L2
+    host: https://keyxtractor.decryptlabs.com
+    secret: YOUR_API_KEY
+
+  # Chrome CDM (L3)
+  - name: decrypt_labs_chrome
+    type: decrypt_labs
+    device_name: ChromeCDM
+    host: https://keyxtractor.decryptlabs.com
+    secret: YOUR_API_KEY
+
+  # PlayReady SL2000
+  - name: decrypt_labs_playready_sl2
+    type: decrypt_labs
+    device_name: SL2
+    device_type: PLAYREADY          # Required for PlayReady
+    host: https://keyxtractor.decryptlabs.com
+    secret: YOUR_API_KEY
+
+  # PlayReady SL3000
+  - name: decrypt_labs_playready_sl3
+    type: decrypt_labs
+    device_name: SL3
+    device_type: PLAYREADY
+    host: https://keyxtractor.decryptlabs.com
+    secret: YOUR_API_KEY
+
+

Features: +- Intelligent key caching system (reduces API calls) +- Automatic integration with unshackle's vault system +- Support for both Widevine and PlayReady +- Multiple security levels (L1, L2, L3, SL2000, SL3000)

+
+

Note

+

The device_type field determines whether the CDM operates in PlayReady or Widevine mode. +Setting device_type: PLAYREADY (or using device_name: SL2 / SL3) activates PlayReady mode. +The security_level field is auto-computed from device_name when not specified (e.g., SL2 defaults +to 2000, SL3 to 3000, and Widevine devices default to 3). You can override these if needed.

+
+

Custom API Remote CDM

+

A highly configurable CDM adapter that can work with virtually any third-party CDM API through YAML configuration. +This allows you to integrate custom CDM services without writing code.

+

Basic Example:

+
remote_cdm:
+  - name: custom_chrome_cdm
+    type: custom_api                # Required: identifies as Custom API CDM
+    host: https://your-cdm-api.com
+    timeout: 30                     # Optional: request timeout in seconds
+
+    device:
+      name: ChromeCDM
+      type: CHROME                  # CHROME, ANDROID, PLAYREADY
+      system_id: 27175
+      security_level: 3
+
+    auth:
+      type: bearer                  # bearer, header, basic, body
+      key: YOUR_API_TOKEN
+
+    endpoints:
+      get_request:
+        path: /get-challenge
+        method: POST
+      decrypt_response:
+        path: /get-keys
+        method: POST
+
+    caching:
+      enabled: true                 # Enable key caching
+      use_vaults: true              # Integrate with vault system
+
+

Full field reference:

+
remote_cdm:
+  - name: advanced_custom_api
+    type: custom_api
+    host: https://api.example.com
+    timeout: 30                     # Default request timeout (seconds); overridden per-endpoint
+
+    device:
+      name: L1                      # Passed as 'scheme' in base request params
+      type: ANDROID                 # CHROME, ANDROID, or PLAYREADY
+      system_id: 26830              # Widevine system ID (informational; not sent)
+      security_level: 1             # 1–3 for Widevine; 2000/3000 for PlayReady
+
+    # Authentication — applied to every request via session headers or Basic auth
+    auth:
+      type: header                  # bearer | header | basic | body | query
+      header_name: X-API-Key        # used when type=header (default: Authorization)
+      key: YOUR_SECRET_KEY          # API key / token / password
+      # bearer_token: TOKEN         # explicit token for type=bearer (falls back to 'key')
+      # username: user              # required for type=basic
+      # password: pass              # required for type=basic
+      custom_headers:               # merged into every request
+        User-Agent: Unshackle/3.1.0
+        X-Client-Version: "1.0"
+
+    # Endpoints — both keys are required
+    endpoints:
+      get_request:                  # first call: obtain challenge or cached keys
+        path: /v2/challenge
+        method: POST
+        timeout: 30                 # overrides top-level timeout for this endpoint
+      decrypt_response:             # second call: exchange license response for keys
+        path: /v2/decrypt
+        method: POST
+        timeout: 30
+
+    # request_mapping — transform outgoing parameters before each request
+    # Base params sent to get_request:  scheme, init_data, [service], [service_certificate]
+    # Base params sent to decrypt_response: scheme, session_id, init_data, license_request, license_response
+    request_mapping:
+      get_request:
+        param_names:                # rename base param keys
+          init_data: pssh
+          scheme: device_type
+        static_params:              # always-added fixed values
+          api_version: "2.0"
+        conditional_params:         # added only when condition is true
+          - condition: "is_playready == True"
+            params:
+              drm_type: playready
+        transforms:                 # applied after renaming; type values listed below
+          - param: pssh
+            type: base64_encode
+        nested_params:              # group named params under a parent key
+          payload:
+            - pssh
+            - device_type
+        exclude_params:             # remove unwanted params before sending
+          - service_certificate
+      decrypt_response:
+        param_names:
+          license_request: challenge
+          license_response: license
+
+    # response_mapping — extract and validate fields from API responses
+    response_mapping:
+      get_request:
+        fields:                     # map standard names to dot-path locations in response JSON
+          challenge: data.challenge
+          session_id: session.id
+          message: status.message
+        response_types:             # classify response so CDM can choose the right path
+          - condition: "message_type == 'license-request'"
+            type: license_request   # CDM expects challenge + session_id
+          - condition: "message_type == 'cached-keys'"
+            type: cached_keys       # CDM skips license round-trip
+        success_conditions:         # all must be true; failure raises RequestException
+          - "message == 'success'"
+        error_fields:               # inspected when success_conditions fail (default: error, message, details)
+          - error
+          - details
+        transforms:                 # applied to extracted fields
+          - field: challenge
+            type: base64_decode
+      decrypt_response:
+        fields:
+          keys: data.keys
+          message: status.message
+        key_fields:                 # map key-object property names to standard names
+          kid: key_id               # default: kid
+          key: content_key          # default: key
+          type: key_type            # default: type (value used as-is; CONTENT treated as content key)
+        success_conditions:
+          - "message == 'success'"
+
+    # caching — vault and API-level key caching
+    caching:
+      enabled: true                 # master switch (default: true)
+      use_vaults: true              # check/write unshackle key vaults (default: true)
+      check_cached_first: true      # check vaults before making any API call (default: true)
+
+

Supported transforms types (for both request_mapping and response_mapping): +base64_encode, base64_decode, hex_encode, hex_decode, json_stringify, json_parse, parse_key_string

+

Supported authentication types: +- bearer — Authorization: Bearer <key> (or bearer_token field) +- header — arbitrary header; set header_name (default Authorization) +- basic — HTTP Basic auth via username / password +- body — credentials injected into request body (handled by request_mapping) +- query — credentials appended to query string (handled by request_mapping)

+

PyPlayReady Remote CDM

+

Connects to a running unshackle serve instance for PlayReady licensing. Uses +pyplayready.remote.remotecdm.RemoteCdm under the hood. No type field is needed — +the entry is routed to PlayReady when device_type: PLAYREADY is set (same branching +logic as the legacy Widevine path).

+
remote_cdm:
+  - name: playready_remote
+    device_name: my_prd_device      # Device name registered on the serve instance
+    device_type: PLAYREADY          # Required to select the PlayReady code path
+    system_id: 0                    # Not used by pyplayready RemoteCdm; set to 0
+    security_level: 3000            # 2000 = SL2000, 3000 = SL3000
+    host: http://127.0.0.1:8786/playready  # Must include the /playready path suffix
+    secret: your-api-secret-key     # X-Secret-Key sent to the serve instance
+
+
+

Note

+

The /playready path suffix in host is required — pyplayready.RemoteCdm +appends its own endpoint paths on top of this base, so omitting the suffix will result +in 404 errors.

+
+

Legacy PyWidevine Serve Format

+

Standard pywidevine serve-compliant remote CDM configuration (backwards compatibility).

+
remote_cdm:
+  - name: legacy_chrome_cdm
+    device_name: chrome
+    device_type: CHROME
+    system_id: 27175
+    security_level: 3
+    host: https://domain.com/api
+    secret: secret_key
+
+
+

Note

+

If the type field is not specified, the entry is treated as a legacy pywidevine serve CDM.

+
+
+

decrypt_labs_api_key (str)

+

API key for DecryptLabs CDM service integration.

+

When set, enables the use of DecryptLabs remote CDM services in your remote_cdm configuration. +This is used specifically for type: "decrypt_labs" entries in the remote CDM list.

+

For example,

+
decrypt_labs_api_key: "your_api_key_here"
+
+
+

Note

+

This is different from the per-CDM secret field in remote_cdm entries. This provides a global +API key that can be referenced across multiple DecryptLabs CDM configurations. If a remote_cdm entry with +type: "decrypt_labs" does not have a secret field specified, the global decrypt_labs_api_key will be +used as a fallback.

+
+
+

decryption (str|dict)

+

Configure which decryption tool to use for DRM-protected content. Default: shaka.

+

Supported values: +- shaka - Shaka Packager (default) +- mp4decrypt - Bento4 mp4decrypt

+

You can specify a single decrypter for all services:

+
decryption: shaka
+
+

Or configure per-service with a DEFAULT fallback:

+
decryption:
+  DEFAULT: shaka
+  EXAMPLE: mp4decrypt
+  EXAMPLE2: shaka
+
+

Service keys are case-insensitive (normalized to uppercase internally).

+
+

MonaLisa DRM

+

MonaLisa is a WASM-based DRM system that uses local key extraction and two-stage segment decryption. +Unlike Widevine and PlayReady, MonaLisa does not use a challenge/response flow with a license server. +Instead, the PSSH value (ticket) is provided directly by the service API, and keys are extracted +locally via a WASM module.

+

Requirements

+
    +
  • ML-Worker binary: Must be available on your system PATH (discovered via binaries.ML_Worker). + This is the binary that performs stage-1 decryption.
  • +
+

Decryption stages

+
    +
  1. ML-Worker binary: Removes MonaLisa encryption layer (bbts -> ents). The key is passed via command-line argument.
  2. +
  3. AES-ECB decryption: Final decryption with service-provided key.
  4. +
+

MonaLisa uses per-segment decryption during download (not post-download like Widevine/PlayReady), +so segments are decrypted as they are downloaded.

+
+

Note

+

MonaLisa is configured per-service rather than through global config options. Services +that use MonaLisa handle ticket/key retrieval and CDM initialization internally.

+
+
+

ClearKey DRM

+

Two distinct ClearKey mechanisms are supported; neither needs a CDM device or any DRM config:

+

HLS AES-128 ClearKey

+

The key is fetched from (or near) the M3U8 EXT-X-KEY URI and segments are decrypted with +pure-Python AES-CBC. Fully automatic — nothing to configure.

+

DASH ClearKey (org.w3.clearkey)

+

W3C EME ClearKey for DASH CENC content. The DASH parser recognises the clearkey +ContentProtection scheme (urn:uuid:e2719d58-a985-b3c9-781a-b030af78d30e), takes the KID from +cenc:default_KID, and reads the license server URL from the manifest's <Laurl> element when +present.

+

License flow: the W3C JSON license request ({"kids": [...], "type": "temporary"}) is POSTed to +the license server, which returns the content key as a JWK Set. Keys land in the same vault and +--export paths as Widevine/PlayReady, and decryption uses the same shaka-packager/mp4decrypt +CENC backends (decryption config option applies).

+

Service integration (simplest first): +1. Manifest carries a <Laurl> — works with zero service code. +2. Custom endpoint/headers — service overrides get_clearkey_license. +3. Bespoke key delivery — service pre-populates the DRM object's keys in get_tracks.

+
+

key_vaults (list[dict])

+

Key Vaults store your obtained Content Encryption Keys (CEKs) and Key IDs per-service.

+

This can help reduce unnecessary License calls even during the first download. This is because a Service may +provide the same Key ID and CEK for both Video and Audio, as well as for multiple resolutions or bitrates.

+

You can have as many Key Vaults as you would like. It's nice to share Key Vaults or use a unified Vault on +Teams as sharing CEKs immediately can help reduce License calls drastically.

+

Four types of Vaults are in the Core codebase: API, SQLite, MySQL, and HTTP. API and HTTP make HTTP requests to a RESTful API, +whereas SQLite and MySQL directly connect to an SQLite or MySQL Database.

+

Note: SQLite and MySQL vaults have to connect directly to the Host/IP. It cannot be in front of a PHP API or such. +Beware that some Hosting Providers do not let you access the MySQL server outside their intranet and may not be +accessible outside their hosting platform.

+

Additional behavior:

+
    +
  • no_push (bool): Optional per-vault flag. When true, the vault will not receive pushed keys (writes) but + will still be queried and can provide keys for lookups. Useful for read-only/backup vaults.
  • +
  • timeout (float): Optional per-vault network timeout in seconds for the remote API and HTTP vaults. + Defaults to the global vault_timeout (10 seconds) so an unreachable vault host fails fast instead of + hanging the download. Set it on a vault to override the global, e.g. timeout: 5.0. Has no effect on + SQLite (local) or MySQL (uses its driver's own connect timeout).
  • +
+

Set a global default for all remote vaults at the top level of the config:

+
vault_timeout: 10.0   # seconds; per-vault `timeout:` overrides this
+
+

Using an API Vault

+

API vaults use a specific HTTP request format, therefore API or HTTP Key Vault APIs from other projects or services may +not work in unshackle. The API format can be seen in the API Vault Code.

+
- type: API
+  name: "John#0001's Vault" # arbitrary vault name
+  uri: "https://key-vault.example.com" # api base uri (can also be an IP or IP:Port)
+  # uri: "127.0.0.1:80/key-vault"
+  # uri: "https://api.example.com/key-vault"
+  token: "random secret key" # authorization token
+  # no_push: true            # optional; make this API vault read-only (lookups only)
+  # timeout: 5.0             # optional; per-vault network timeout (seconds), overrides vault_timeout
+
+

Using a MySQL Vault

+

MySQL vaults can be either MySQL or MariaDB servers. I recommend MariaDB. +A MySQL Vault can be on a local or remote network, but I recommend SQLite for local Vaults.

+
- type: MySQL
+  name: "John#0001's Vault" # arbitrary vault name
+  host: "127.0.0.1" # host/ip
+  # port: 3306               # port (defaults to 3306)
+  database: vault # database used for unshackle
+  username: jane11
+  password: Doe123
+  # no_push: false           # optional; defaults to false
+
+

I recommend giving only a trustable user (or yourself) CREATE permission and then use unshackle to cache at least one CEK +per Service to have it create the tables. If you don't give any user permissions to create tables, you will need to +make tables yourself.

+
    +
  • Use a password on all user accounts.
  • +
  • Never use the root account with unshackle (even if it's you).
  • +
  • Do not give multiple users the same username and/or password.
  • +
  • Only give users access to the database used for unshackle.
  • +
  • You may give trusted users CREATE permission so unshackle can create tables if needed.
  • +
  • Other uses should only be given SELECT and INSERT permissions.
  • +
+

Using an SQLite Vault

+

SQLite Vaults are usually only used for locally stored vaults. This vault may be stored on a mounted Cloud storage +drive, but I recommend using SQLite exclusively as an offline-only vault. Effectively this is your backup vault in +case something happens to your MySQL Vault.

+
- type: SQLite
+  name: "My Local Vault" # arbitrary vault name
+  path: "C:/Users/Jane11/Documents/unshackle/data/key_vault.db"
+  # no_push: true           # optional; commonly true for local backup vaults
+
+
+

Note

+

You do not need to create the file at the specified path. +SQLite will create a new SQLite database at that path if one does not exist. +Try not to accidentally move the db file once created without reflecting the change in the config, or you will end +up with multiple databases.

+
+

If you work on a Team I recommend every team member having their own SQLite Vault even if you all use a MySQL vault +together.

+

Using an HTTP Vault

+

HTTP Vaults provide flexible HTTP-based key storage with support for multiple API modes. This vault type +is useful for integrating with various third-party key vault APIs.

+
- type: HTTP
+  name: "My HTTP Vault"
+  host: "https://vault-api.example.com"
+  api_key: "your_api_key"       # or use 'password' field
+  api_mode: "json"              # query, json, or decrypt_labs
+  # username: "user"            # required for query mode only
+  # no_push: false              # optional; defaults to false
+  # timeout: 5.0                # optional; per-vault network timeout (seconds), overrides vault_timeout
+
+

Supported API Modes:

+
    +
  • query - Uses GET requests with query parameters. Requires username field.
  • +
  • json - Uses POST requests with JSON payloads. Token-based authentication.
  • +
  • decrypt_labs - DecryptLabs API format. Read-only mode (no_push is forced to true).
  • +
+

Example configurations:

+
# Query mode (requires username)
+- type: HTTP
+  name: "Query Vault"
+  host: "https://api.example.com/keys"
+  username: "myuser"
+  password: "mypassword"
+  api_mode: "query"
+
+# JSON mode
+- type: HTTP
+  name: "JSON Vault"
+  host: "https://api.example.com/vault"
+  api_key: "secret_token"
+  api_mode: "json"
+
+# DecryptLabs mode (read-only)
+- type: HTTP
+  name: "DecryptLabs Cache"
+  host: "https://keyxtractor.decryptlabs.com/cache"
+  api_key: "your_decrypt_labs_api_key"
+  api_mode: "decrypt_labs"
+
+
+

Note

+

The decrypt_labs mode is always read-only and cannot receive pushed keys.

+
+
+ + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/GLUETUN/index.html b/GLUETUN/index.html new file mode 100644 index 00000000..505d03bd --- /dev/null +++ b/GLUETUN/index.html @@ -0,0 +1,2395 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Gluetun VPN - unshackle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+ +
+ + + + + + + + +

Gluetun VPN Proxy

+

Gluetun provides Docker-managed VPN proxies supporting 50+ VPN providers.

+

Prerequisites

+

Docker must be installed and running.

+
# Linux
+curl -fsSL https://get.docker.com | sh
+sudo usermod -aG docker $USER  # Then log out/in
+
+# Windows/Mac
+# Install Docker Desktop: https://www.docker.com/products/docker-desktop/
+
+

Quick Start

+

1. Configuration

+

Add to ~/.config/unshackle/unshackle.yaml:

+
proxy_providers:
+  gluetun:
+    providers:
+      windscribe:
+        vpn_type: openvpn
+        credentials:
+          username: "YOUR_OPENVPN_USERNAME"
+          password: "YOUR_OPENVPN_PASSWORD"
+
+

2. Usage

+

Use 2-letter country codes directly:

+
unshackle dl SERVICE CONTENT --proxy gluetun:windscribe:us
+unshackle dl SERVICE CONTENT --proxy gluetun:windscribe:uk
+
+

Format: gluetun:provider:region

+

Provider Credential Requirements

+

OpenVPN (Recommended): Most providers support OpenVPN with just username and password - the simplest setup.

+

WireGuard: Requires private keys and varies by provider. See the Gluetun Wiki for provider-specific requirements. Note that vpn_type defaults to wireguard if not specified.

+

Getting Your Credentials

+

Windscribe (OpenVPN)

+
    +
  1. Go to windscribe.com/getconfig/openvpn
  2. +
  3. Log in with your Windscribe account
  4. +
  5. Select any location and click "Get Config"
  6. +
  7. Copy the username and password shown
  8. +
+

NordVPN (OpenVPN)

+
    +
  1. Go to NordVPN Service Credentials
  2. +
  3. Log in with your NordVPN account
  4. +
  5. Generate or view your service credentials
  6. +
  7. Copy the username and password
  8. +
+
+

Note

+

Use service credentials, NOT your account email/password.

+
+

WireGuard Credentials (Advanced)

+

WireGuard requires private keys instead of username/password. See the Gluetun Wiki for provider-specific WireGuard setup.

+

Configuration Examples

+

OpenVPN (Recommended)

+

Most providers support OpenVPN with just username and password:

+
providers:
+  windscribe:
+    vpn_type: openvpn
+    credentials:
+      username: YOUR_OPENVPN_USERNAME
+      password: YOUR_OPENVPN_PASSWORD
+
+  nordvpn:
+    vpn_type: openvpn
+    credentials:
+      username: YOUR_SERVICE_USERNAME
+      password: YOUR_SERVICE_PASSWORD
+
+

WireGuard (Advanced)

+

WireGuard can be faster but requires more complex credential setup:

+
# NordVPN/ProtonVPN (only private_key needed)
+providers:
+  nordvpn:
+    vpn_type: wireguard
+    credentials:
+      private_key: YOUR_PRIVATE_KEY
+
+# Surfshark/Mullvad/IVPN (private_key AND addresses required)
+  surfshark:
+    vpn_type: wireguard
+    credentials:
+      private_key: YOUR_PRIVATE_KEY
+      addresses: 10.x.x.x/32
+
+# Windscribe (all three credentials required)
+  windscribe:
+    vpn_type: wireguard
+    credentials:
+      private_key: YOUR_PRIVATE_KEY
+      addresses: 10.x.x.x/32
+      preshared_key: YOUR_PRESHARED_KEY
+
+

Server Selection

+

Most providers use SERVER_COUNTRIES, but some use SERVER_REGIONS:

+ + + + + + + + + + + + + + + + + +
VariableProviders
SERVER_COUNTRIESNordVPN, ProtonVPN, Surfshark, Mullvad, ExpressVPN, and most others
SERVER_REGIONSWindscribe, VyprVPN, VPN Secure
+

Unshackle handles this automatically - just use 2-letter country codes.

+

Per-Provider Server Mapping

+

You can explicitly map region codes to country names, cities, or hostnames per provider:

+
providers:
+  nordvpn:
+    vpn_type: openvpn
+    credentials:
+      username: YOUR_USERNAME
+      password: YOUR_PASSWORD
+    server_countries:
+      us: "United States"
+      uk: "United Kingdom"
+    server_cities:
+      us: "New York"
+    server_hostnames:
+      us: "us1239.nordvpn.com"
+
+

Specific Server Selection

+

Use a <country><number> region (e.g. us1239) to target a specific server. Unshackle builds the +hostname automatically per provider:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ProviderHostname format
NordVPNus1239.nordvpn.com
Surfsharkus-1239.prod.surfshark.com
ExpressVPNus-1239.expressvpn.com
CyberGhostus-s1239.cg-dialup.net
Otherus1239 (passed as-is to SERVER_HOSTNAMES)
+

Extra Environment Variables

+

You can pass additional Gluetun environment variables per provider using extra_env:

+
providers:
+  nordvpn:
+    vpn_type: openvpn
+    credentials:
+      username: YOUR_USERNAME
+      password: YOUR_PASSWORD
+    extra_env:
+      LOG_LEVEL: debug
+
+

Global Settings

+
proxy_providers:
+  gluetun:
+    providers: {...}
+    base_port: 8888           # Starting port (default: 8888)
+    auto_cleanup: true        # Remove containers on exit (default: true)
+    verify_ip: true           # Verify IP matches region (default: true)
+    container_prefix: "unshackle-gluetun"  # Docker container name prefix (default: "unshackle-gluetun")
+    auth_user: username       # Proxy auth (optional)
+    auth_password: password   # Proxy auth (optional)
+
+

Features

+
    +
  • Container Reuse: First request takes 10-30s; subsequent requests are instant. Containers created by other unshackle processes are auto-detected via docker inspect and reused.
  • +
  • Ready Detection: Waits up to 60s for both the HTTP proxy to listen ([http proxy] listening) and the VPN tunnel to come up (initialization sequence completed or public ip address is) before returning the proxy URI. Bails early on fatal or invalid credentials log lines.
  • +
  • IP Verification: When verify_ip: true (default), looks up the exit IP via ipinfo.io through the proxy and compares country code to the requested region. Retries 3 times with exponential backoff (1s, 2s, 4s).
  • +
  • Concurrent Sessions: Multiple downloads share the same container; ports are allocated thread-safely starting at base_port.
  • +
  • Specific Servers: Use --proxy gluetun:nordvpn:us1239 for specific server selection (see table above).
  • +
  • Automatic Image Pull: The Gluetun Docker image (qmcgaw/gluetun:latest) is pulled automatically on first use (5 min timeout).
  • +
  • Secure Credentials: Credentials are passed via temporary env files (mode 0600), then zero-overwritten and unlinked after docker run. They never appear in process listings.
  • +
  • Auto Cleanup: Containers are removed via atexit (Ctrl+C still works normally). Disable with auto_cleanup: false to leave them stopped instead.
  • +
+

Container Management

+
# View containers
+docker ps | grep unshackle-gluetun
+
+# Check logs
+docker logs unshackle-gluetun-nordvpn-us
+
+# Remove all containers
+docker ps -a | grep unshackle-gluetun | awk '{print $1}' | xargs docker rm -f
+
+

Troubleshooting

+

Docker Permission Denied (Linux)

+
sudo usermod -aG docker $USER
+# Then log out and log back in
+
+

VPN Connection Failed

+

Check container logs for specific errors: +

docker logs unshackle-gluetun-nordvpn-us
+

+

Common issues: +- Invalid/missing credentials +- Windscribe WireGuard requires preshared_key (can be empty string, but must be set in credentials) +- VPN provider server issues +- Container startup timeout (default 60 seconds)

+

Resources

+ +
+

ExpressVPN HTTPS Proxy

+

ExpressVPN is a standalone HTTPS proxy provider — it does not use Docker or the gluetun sub-provider system. It authenticates via a browser-extension OAuth flow (Keycloak/PKCE) and returns an authenticated https://cat:<token>@<host>:443 proxy URL.

+

Prerequisites

+

An active ExpressVPN subscription is required. The provider resolves proxy-capable locations through ExpressVPN's own API, so no local VPN client or Docker container is needed.

+

Authentication

+

The provider uses a token cascade (tried in order):

+
    +
  1. Cached tokens — cache/global/expressvpn_tokens.json (auto-written after first auth; access and refresh tokens are refreshed automatically when they expire).
  2. +
  3. Exported browser cookies — cookies/vpn/expressvpn.txt (also accepted at cookies/vpns/expressvpn.txt). Must contain the KEYCLOAK_IDENTITY and KEYCLOAK_SESSION cookies from auth.expressvpn.com. Used for a one-time PKCE bootstrap that populates the token cache.
  4. +
  5. Desktop account.json — configured via the account_json key (see below).
  6. +
+

Export cookies from a browser that is logged in to auth.expressvpn.com. Any Netscape-format, JSON array, or name=value file is accepted.

+

Configuration

+

Add to ~/.config/unshackle/unshackle.yaml under proxy_providers:

+
proxy_providers:
+  expressvpn:
+    region_map:
+      us: "ny"        # --proxy expressvpn:us  →  New York (default city)
+      gb: "london"    # --proxy expressvpn:gb  →  London
+      de: "frankfurt" # --proxy expressvpn:de  →  Frankfurt
+      es: "madrid"    # --proxy expressvpn:es  →  Madrid
+      mx:             # --proxy expressvpn:mx  →  smart/random MX location
+
+

region_map maps a country code to a default city preset. An empty (null) value enables smart connection (a random location in that country). The preset is only applied when the CLI query contains no city; an explicit city in the query always takes precedence.

+

Optional keys

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyDefaultDescription
region_map{}Country → default city presets (see above)
server_map{}Alias → ExpressVPN location slug or direct hostname
cookie_pathcookies/vpn/expressvpn.txtPath to exported browser cookies
cache_pathcache/global/expressvpn_tokens.jsonToken cache location
account_json(none)Path to ExpressVPN desktop account.json
refresh_token(none)OAuth refresh token (manual override)
timeout10.0HTTP request timeout in seconds
+

Usage

+
unshackle dl SERVICE CONTENT --proxy expressvpn:us        # random/smart US location
+unshackle dl SERVICE CONTENT --proxy expressvpn:us-ny     # specific city (New York)
+unshackle dl SERVICE CONTENT --proxy expressvpn:us-ny-2   # pinned server #2 in New York
+unshackle dl SERVICE CONTENT --proxy expressvpn:mx        # random/smart Mexico location
+unshackle dl SERVICE CONTENT --proxy expressvpn:es-madrid # specific city (Madrid)
+
+

Query format: expressvpn:<country>[-<city>[-<N>]]

+
    +
  • country — 2-letter country code (e.g. us, gb, de).
  • +
  • city — optional city abbreviation or slug matched against location names using first-letter abbreviation (ny → "New York"), exact slug, prefix, then substring strategies.
  • +
  • N — optional 1-based server index (e.g. -2 selects the second server in the matched city). If N exceeds the available server count, a random server is selected.
  • +
+

City matching

+

The city part of the query is matched against ExpressVPN location names using these strategies (in priority order):

+
    +
  1. First-letter abbreviation — ny matches "New York"
  2. +
  3. Exact slug — miami matches "Miami"
  4. +
  5. Prefix match — mia matches "Miami"
  6. +
  7. Substring match — york matches "New York"
  8. +
+

Token cache

+

After the first successful authentication the token cache is written to cache/global/expressvpn_tokens.json (mode 0600). Subsequent runs refresh expired access/SRT/connection tokens automatically without re-reading cookies.

+ + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/NETWORK_CONFIG/index.html b/NETWORK_CONFIG/index.html new file mode 100644 index 00000000..6224b60a --- /dev/null +++ b/NETWORK_CONFIG/index.html @@ -0,0 +1,1997 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Network & Proxy - unshackle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+ +
+ + + + + + + + +

Network & Proxy Configuration

+

This document covers network and proxy configuration options for bypassing geofencing and managing connections.

+

proxy_providers (dict)

+

Enable external proxy provider services. These proxies will be used automatically where needed as defined by the +Service's GEOFENCE class property, but can also be explicitly used with --proxy. You can specify which provider +to use by prefixing it with the provider key name, e.g., --proxy basic:de or --proxy nordvpn:de. Some providers +support specific query formats for selecting a country/server.

+

basic (dict[str, str|list])

+

Define a mapping of country to proxy to use where required. +The keys are region Alpha 2 Country Codes. Alpha 2 Country Codes are [a-z]{2} codes, e.g., us, gb, and jp. +Don't get this mixed up with language codes like en vs. gb, or ja vs. jp.

+

Do note that each key's value can be a list of strings, or a string. For example,

+
us:
+  - "http://john%40email.tld:password123@proxy-us.domain.tld:8080"
+  - "http://jane%40email.tld:password456@proxy-us.domain2.tld:8080"
+de: "https://127.0.0.1:8080"
+
+

Note that if multiple proxies are defined for a region, then by default one will be randomly chosen. +You can choose a specific one by specifying it's number, e.g., --proxy basic:us2 will choose the +second proxy of the US list.

+

nordvpn (dict)

+

Set your NordVPN Service credentials with username and password keys to automate the use of NordVPN as a Proxy +system where required.

+

You can also specify specific servers to use per-region with the server_map key. +Sometimes a specific server works best for a service than others, so hard-coding one for a day or two helps.

+

You can also select servers by city using the format --proxy nordvpn:us:seattle or --proxy nordvpn:ca:calgary.

+

For example,

+
username: zxqsR7C5CyGwmGb6KSvk8qsZ # example of the login format
+password: wXVHmht22hhRKUEQ32PQVjCZ
+server_map:
+  us: 12 # force US server #12 for US proxies
+
+

The username and password should NOT be your normal NordVPN Account Credentials. +They should be the Service credentials which can be found on your Nord Account Dashboard.

+

Once set, you can also specifically opt in to use a NordVPN proxy by specifying --proxy nordvpn:gb or such. +You can even set a specific server number this way, e.g., --proxy nordvpn:gb2366.

+

Note that gb is used instead of uk to be more consistent across regional systems.

+

protonvpn (dict)

+

Enable Proton VPN as a proxy provider. Proton mints short-lived HTTPS proxy credentials per session, so +authentication reuses a Proton account session (obtained via TV login or an exported browser session) rather than +static credentials. Free Proton accounts work, limited to the free-tier exit countries.

+

There are two ways to obtain that session — TV login or cookie export. You only need one. They can also coexist: a +cached or cookie session is always tried first, and TV login is only used as a fallback when no session is present.

+ +

Set enable: true and run any command that uses the proxy. On first use, when no session is cached, unshackle +prints a code and pauses:

+
    +
  1. Open https://account.proton.me/vpn/tv/code in a browser where you are signed in to Proton.
  2. +
  3. Enter the code shown in the terminal and approve it.
  4. +
  5. Return to the terminal and press Enter to continue.
  6. +
+

The minted refresh token is cached at <cache>/vpn/protonvpn.json and refreshes on its own forever after, so +this is genuinely a one-time step — later runs load straight from the cache without prompting. The prompt is off +by default (enable: false) so nobody is prompted unexpectedly, and it is skipped automatically when there is no +interactive terminal (e.g. under serve, cron, or piped input) — in those contexts, use a cookie export or run an +interactive command once to seed the cache first.

+ +

Export the cookies for account.proton.me or account.protonvpn.com (Netscape cookies.txt or a JSON cookie +list) and save them at <cookies>/vpn/protonvpn.txt. The provider auto-loads when that file exists (no YAML +required); cookies for other Proton apps (e.g. mail.proton.me) are ignored automatically.

+
    +
  • The export must include the AUTH-<UID> cookie. It is HttpOnly — many one-click cookie-export + extensions skip HttpOnly cookies by default, which is the most common reason an export "doesn't work". Use an + exporter that includes them.
  • +
  • A browser session cannot be refreshed headlessly — Proton rotates browser refresh tokens and ties them to + the live browser. unshackle therefore uses the cookie's access token only (it never tries to refresh it) and the + session works until that token expires (Proton's TTL, typically ~24h); after that you must re-export. For a + set-and-forget setup, prefer TV login.
  • +
  • Cookies are read fresh from the file on every run and are never written to the cache. If a TV-login cache + also exists, it is preferred unless the cookie file is newer — so to force the cookie session, just re-save + (re-export) the cookie file.
  • +
+

Resetting

+

To start clean, delete both <cookies>/vpn/protonvpn.txt and <cache>/vpn/protonvpn.json.

+

Troubleshooting

+
    +
  • Proton: no session available … — no cookies and no cache were found. Either export cookies, or set + enable: true and run from an interactive terminal (the prompt is skipped on non-tty stdin).
  • +
  • Proton: session expired; re-export … cookies — a cookie session's access token lapsed and cannot be + refreshed (expected for cookie auth). Re-export the cookies, or switch to TV login.
  • +
+

Configuration

+

Configuration is optional — only add a block to enable TV login or override defaults:

+
proxy_providers:
+  protonvpn:
+    enable: true  # prompt the one-time TV login when no session is cached (default false)
+    cookie_path: /path/to/protonvpn.txt  # optional; defaults to <cookies>/vpn/protonvpn.txt
+    cache_path: /path/to/protonvpn.json  # optional; defaults to <cache>/vpn/protonvpn.json
+    timeout: 10  # optional; per-request timeout in seconds (default 10)
+    server_map:
+      stream-us: us:ny  # optional alias -> country / country:city / countryNN
+
+

Query formats (after the provider prefix). City uses the NordVPN-style colon, like nordvpn:us:seattle:

+
    +
  • --proxy protonvpn:us — random server in the country (the chosen server name is logged)
  • +
  • --proxy protonvpn:us:ny / protonvpn:ca:vancouver — by city (name or initials)
  • +
  • --proxy protonvpn:de203 — pin Proton server #203 (the number shown in the log, e.g. DE#203)
  • +
+

Use uk (Proton's code for the United Kingdom); gb is accepted and mapped to uk automatically. Proton's +server numbers are not sequential (a country may have #203, #813, …), so pick the number from a previous +run's log line rather than guessing.

+

Selection only offers servers your plan can use (paid accounts are detected automatically) and prefers paid +servers over the free pool, falling back to free only when no paid server is available — a pinned #NN always +wins. Tor and Secure Core servers are excluded (both are slow for a plain proxy). Secure Core servers use +port 443, all others 4443 — handled automatically.

+

surfsharkvpn (dict)

+

Enable Surfshark VPN proxy service using Surfshark Service credentials (not your login password). +You may pin specific server IDs per region using server_map.

+

You can also select servers by city using the format --proxy surfsharkvpn:us:seattle.

+
username: your_surfshark_service_username # https://my.surfshark.com/vpn/manual-setup/main/openvpn
+password: your_surfshark_service_password # service credentials, not account password
+server_map:
+  us: 3844 # force US server #3844
+  gb: 2697 # force GB server #2697
+  au: 4621 # force AU server #4621
+
+

hola

+

Enable Hola VPN proxy service. Requires the hola-proxy binary to be installed and available in your PATH. +No configuration is needed under proxy_providers. Hola is loaded automatically when the hola-proxy binary +is detected.

+

Once available, use --proxy hola:us or similar to connect through Hola.

+

windscribevpn (dict)

+

Enable Windscribe VPN proxy service using static OpenVPN service credentials.

+

Use the service credentials from https://windscribe.com/getconfig/openvpn (not your account login credentials).

+
proxy_providers:
+  windscribevpn:
+    username: openvpn_username  # From https://windscribe.com/getconfig/openvpn
+    password: openvpn_password  # Service credentials, NOT your account password
+
+

Server Mapping

+

You can optionally pin specific servers using server_map:

+
proxy_providers:
+  windscribevpn:
+    username: openvpn_username
+    password: openvpn_password
+    server_map:
+      us: us-central-096.totallyacdn.com  # Force specific US server
+      gb: uk-london-001.totallyacdn.com   # Force specific UK server
+
+

Once configured, use --proxy windscribevpn:us or --proxy windscribevpn:gb etc. to connect through Windscribe.

+

You can also select specific servers by number (e.g., --proxy windscribevpn:sg007) or filter by city +(e.g., --proxy windscribevpn:ca:toronto).

+

gluetun (dict)

+

Docker-managed VPN proxy supporting 50+ VPN providers via Gluetun. See GLUETUN.md for full +configuration and usage details.

+
proxy_providers:
+  gluetun:
+    providers:
+      windscribe:
+        vpn_type: openvpn
+        credentials:
+          username: "YOUR_OPENVPN_USERNAME"
+          password: "YOUR_OPENVPN_PASSWORD"
+
+

Usage: --proxy gluetun:windscribe:us

+
+

headers (dict)

+

Case-Insensitive dictionary of headers that all Services begin their Request Session state with. +All requests will use these unless changed explicitly or implicitly via a Server response. +These should be sane defaults and anything that would only be useful for some Services should not +be put here.

+

Avoid headers like 'Accept-Encoding' as that would be a compatibility header that the underlying +HTTP backend (rnet) will set for you as part of its browser impersonation profile.

+

I recommend using,

+
Accept-Language: "en-US,en;q=0.8"
+User-Agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
+
+
+

HTTP Session Backend

+

unshackle uses rnet (Rust + BoringSSL) for HTTP with TLS +fingerprinting. RnetSession is a drop-in requests.Session replacement and is what +self.session exposes to services. It supports:

+
    +
  • Browser/app impersonation via named rnet.Impersonate presets (Chrome, Edge, Firefox, Safari, + OkHttp, etc.) — picks JA3, ALPN, HTTP/2 SETTINGS and header order to match the chosen client.
  • +
  • Native rnet proxy support (HTTP, HTTPS, SOCKS5) — used by all proxy providers below.
  • +
  • Cookie-jar and requests-style data= / json= / headers= kwargs for compatibility.
  • +
+

The legacy curl_cffi backend has been removed.

+

network (dict)

+

HTTP session settings: which client to impersonate and how the underlying rnet +client behaves.

+
network:
+  browser: Chrome131   # exact rnet.Impersonate preset name
+
+

browser must be an exact rnet.Impersonate preset name (e.g. Chrome131, Chrome124, +Edge101, Firefox133, Safari18, OkHttp4_12). See the rnet README for the full list. +Default when unset: Chrome131.

+

The same section accepts optional rnet client tuning keys, forwarded verbatim:

+
network:
+  browser: Chrome131
+  http1_only: true            # force HTTP/1.1 instead of negotiated HTTP/2
+  # http2_only: true
+  # pool_max_size: 64         # max total connections in the pool
+  # pool_max_idle_per_host: 32
+  # tcp_nodelay: true
+
+
+

Renamed from curl_impersonate

+

This section was previously named curl_impersonate (after the removed backend). +The old spelling still works but is deprecated; rename it to network.

+
+
+

When to force HTTP/1.1

+

HTTP/2 is the right default on large CDNs. Force http1_only: true when a host +throttles per connection or has a slow origin behind HTTP/2 flow control — in +benchmarks this gained 30–50% on such hosts, while costing up to 27% on fast +CDNs. Set it only when measurements support it.

+
+
+ + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/OUTPUT_CONFIG/index.html b/OUTPUT_CONFIG/index.html new file mode 100644 index 00000000..cde01df6 --- /dev/null +++ b/OUTPUT_CONFIG/index.html @@ -0,0 +1,2343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Output & Naming - unshackle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+ +
+ + + + + + + + +

Output & Naming Configuration

+

This document covers output file organization and naming configuration options.

+

filenames (dict)

+

Override the default filenames used across unshackle. +The filenames use various variables that are replaced during runtime.

+

The following filenames are available and may be overridden:

+
    +
  • log - Log filenames. Uses {name} and {time} variables.
  • +
  • debug_log - Debug log filenames. Uses {service} and {time} variables.
  • +
  • config - Service configuration filenames.
  • +
  • root_config - Root configuration filename.
  • +
  • chapters - Chapter export filenames. Uses {title} and {random} variables.
  • +
  • subtitle - Subtitle export filenames. Uses {id} and {language} variables.
  • +
+

For example,

+
filenames:
+  log: "unshackle_{name}_{time}.log"
+  debug_log: "unshackle_debug_{service}_{time}.jsonl"
+  config: "config.yaml"
+  root_config: "unshackle.yaml"
+  chapters: "Chapters_{title}_{random}.txt"
+  subtitle: "Subtitle_{id}_{language}.srt"
+
+
+

output_template (dict)

+

Configure custom output filename templates for movies, series, songs, and albums. +This is required in your unshackle.yaml - a warning is shown if not configured.

+

Add ? suffix to make a variable conditional (omitted when empty): {year?}, {hdr?}, {repack?}

+

General variables

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VariableMeaning
titleTitle of the movie, episode, or track
yearRelease year
sourceService source tag (the uppercase service directory name)
tagGroup/release tag configured via tag:
editionEdition label (e.g. Extended, Theatrical)
repackREPACK when the release is a repack, otherwise empty
lang_tagLanguage tag produced by language_tags rules (e.g. NORDiC)
+

Series / episode variables

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VariableMeaning
seasonSeason number (e.g. 1)
episodeEpisode number (e.g. 1)
season_episodeCombined season/episode (e.g. S01E01)
episode_nameEpisode title
dateAir date in ISO form (e.g. 2024-06-30), only for daily/sports content
+
+

Daily / sports content

+

When a service supplies an air date (daily talk shows, news, sports events), +{season_episode} automatically renders the date using your template's separator +(e.g. 2024.06.30 for a dotted template, 2024 06 30 for a spaced one). The +standalone {year} is dropped from the filename — the air date is the sole date — +so every episode of a show names consistently. Folders still keep the year (e.g. +Show Name (2010)/2024/). No template change is needed.

+

Use the {date} variable instead if you want a fixed YYYY-MM-DD form regardless +of separator. Folders for dated episodes group by air year (e.g. Show Name/2024/).

+
+

Video / audio variables

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VariableMeaning
qualityResolution label (e.g. 1080p, 2160p)
resolutionRaw resolution string
videoVideo codec label (e.g. H.264, H.265)
hdrHDR format label (e.g. HDR, DV)
hfrHFR label when frame rate exceeds normal threshold
audioAudio codec label (e.g. AAC, EAC3)
audio_channelsChannel count (e.g. 2.0, 5.1)
audio_fullCombined codec + channels (e.g. DDP5.1)
atmosAtmos when Dolby Atmos is present, otherwise empty
dualDUAL when two audio languages are present, otherwise empty
multiMULTi when three or more audio languages are present, otherwise empty
+

Music variables

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VariableMeaning
track_numberTrack number within album
discDisc number
track_totalTotal tracks on disc
disc_totalTotal discs in release
artistTrack artist
album_artistAlbum-level artist (may differ from track artist)
albumAlbum title
release_typeRelease type (e.g. Album, Single, EP)
genreGenre string
explicitExplicit when track has explicit content flag, otherwise empty
isrcInternational Standard Recording Code
upcUniversal Product Code (album barcode)
labelRecord label name
+
output_template:
+  # Scene-style (dot-separated)
+  movies: '{title}.{year}.{repack?}.{edition?}.{quality}.{source}.WEB-DL.{dual?}.{multi?}.{audio_full}.{atmos?}.{hdr?}.{hfr?}.{video}-{tag}'
+  series: '{title}.{year?}.{season_episode}.{episode_name?}.{repack?}.{edition?}.{quality}.{source}.WEB-DL.{dual?}.{multi?}.{audio_full}.{atmos?}.{hdr?}.{hfr?}.{video}-{tag}'
+  songs: '{track_number}.{title}.{repack?}.{edition?}.{source?}.WEB-DL.{audio_full}.{atmos?}-{tag}'
+
+  # Plex-friendly (space-separated)
+  # movies: '{title} ({year}) {quality}'
+  # series: '{title} {season_episode} {episode_name?}'
+  # songs: '{track_number}. {title}'
+
+

Example outputs: +- Scene movies: Example.Movie.2024.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG +- Scene movies (REPACK): Example.Movie.2024.REPACK.2160p.EXAMPLE.WEB-DL.DDP5.1.H.265-TAG +- Scene series: Example.Show.2024.S01E01.Pilot.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG +- Plex movies: Example Movie (2024) 1080p

+

folder (optional)

+

Controls the folder name for downloaded content. Uses the same template variables as the file templates above.

+

If not configured, the default folder naming is used: +- Movies: Title (Year) +- Series: Derived from the series template with episode-specific variables removed +- Songs: Artist - Album (Year)

+

folder accepts either a single string (applies to all title kinds) or a mapping with per-kind +templates keyed by movies, series, songs, and/or albums. Unknown keys are warned about and ignored.

+

Use / in a folder template to create nested directories - each segment is sanitized +independently and joined as real path separators. For example '{source}/Series/{title}.{year?}' +produces EXAMPLE/Series/The.Show.2024/. Note {source} is the service tag (e.g. EXAMPLE, +EXAMPLE2), not the display name; it is blank when --no-source is used.

+

Movies only get their own folder when a movies folder template is set; without one the movie file +is written directly into the downloads directory (unchanged default behaviour). Series and songs +always get a folder unless --no-folder is passed.

+

Useful music variables for album folders: album_artist, album, artist, year, genre, label, release_type, track_total, disc_total.

+
output_template:
+  movies: '{title}.{year}.{repack?}.{edition?}.{quality}.{source}.WEB-DL.{dual?}.{multi?}.{audio_full}.{atmos?}.{hdr?}.{hfr?}.{video}-{tag}'
+  series: '{title}.{year?}.{season_episode}.{episode_name?}.{repack?}.{edition?}.{quality}.{source}.WEB-DL.{dual?}.{multi?}.{audio_full}.{atmos?}.{hdr?}.{hfr?}.{video}-{tag}'
+  songs: '{track_number}.{title}.{repack?}.{edition?}.{source?}.WEB-DL.{audio_full}.{atmos?}-{tag}'
+
+  # Scene-style folder (single template, applies to all kinds)
+  folder: '{title}.{year?}.{repack?}.{edition?}.{lang_tag?}.{quality}.{source}.WEB-DL.{dual?}.{multi?}.{audio_full}.{atmos?}.{hdr?}.{hfr?}.{video}-{tag}'
+
+  # Plex-friendly folder
+  # folder: '{title} ({year?})'
+
+  # Per-kind folder templates
+  # folder:
+  #   movies: '{title} ({year})'
+  #   series: '{title} ({year?})'
+  #   songs: '{artist} - {album} ({year?})'
+  #   albums: '{album_artist} - {album} ({year?})'
+
+  # Nested per-service layout (EXAMPLE/Series/Title.Year/...)
+  # folder:
+  #   series: '{source}/Series/{title}.{year?}'
+  #   movies: '{source}/Movies/{title}.{year?}'
+  #   songs:  '{source}/Music/{album_artist}/{album}.{year?}'
+
+

Example outputs: +- Scene folder: Example.Show.2024.S01.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG/ +- Plex folder: Example Show (2024)/ +- Nested folder: EXAMPLE/Series/Example.Show.2024/

+
+
+

language_tags (dict)

+

Automatically adds language-based identifiers (e.g., DANiSH, NORDiC, DKsubs) to output filenames +based on audio and subtitle track languages. Use {lang_tag?} in your output_template to place the tag.

+

Rules are evaluated in order; the first matching rule wins. All conditions within a single rule +must match (AND logic). If no rules match, {lang_tag?} is cleanly removed from the filename.

+

Conditions

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ConditionTypeDescription
audiostringMatches if any selected audio track has this language
subs_containstringMatches if any selected subtitle has this language
subs_contain_alllistMatches if subtitles include ALL listed languages
+

Language matching uses fuzzy matching (e.g., en matches en-US, en-GB).

+

Example: Nordic tagging

+
language_tags:
+  rules:
+    - audio: da
+      tag: DANiSH
+    - audio: sv
+      tag: SWEDiSH
+    - audio: nb
+      tag: NORWEGiAN
+    - audio: en
+      subs_contain_all: [da, sv, nb]
+      tag: NORDiC
+    - audio: en
+      subs_contain: da
+      tag: DKsubs
+
+output_template:
+  movies: '{title}.{year?}.{lang_tag?}.{quality}.{source}.WEB-DL.{audio_full}.{video}-{tag}'
+
+

Example outputs: +- Danish audio: Example.Show.S01E01.DANiSH.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG +- English audio + multiple Nordic subs: Example.Show.S01E01.NORDiC.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG +- English audio + Danish subs only: Example.Show.S01E01.DKsubs.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG +- No matching languages: Example.Show.S01E01.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG

+

Example: Other regional tags

+
language_tags:
+  rules:
+    - audio: nl
+      tag: DUTCH
+    - audio: de
+      tag: GERMAN
+    - audio: fr
+      subs_contain: en
+      tag: ENGFR
+    - audio: fr
+      tag: FRENCH
+
+
+

unicode_filenames (bool)

+

Allow Unicode characters in output filenames. When false, Unicode characters are transliterated +to ASCII equivalents. Default: false.

+
+

tag (str)

+

Group or Username to postfix to the end of download filenames following a dash. +Use {tag} in your output template to include it. +For example, tag: "J0HN" will have -J0HN at the end of all download filenames.

+
+

tag_group_name (bool)

+

Enable/disable tagging downloads with your group name when tag is set. Default: true.

+
+

tag_imdb_tmdb (bool)

+

Enable/disable tagging downloaded files with IMDB/TMDB/TVDB identifiers (when available). Default: true.

+
+

muxing (dict)

+
    +
  • set_title + Set the container title to Show SXXEXX Episode Name or Movie (Year). Default: true
  • +
  • merge_audio + Merge all audio tracks into each output file. Default: true
  • +
  • true: All selected audio tracks are muxed into one MKV per quality.
  • +
  • false: Separate MKV per (quality, audio_codec) combination. + For example: Title.1080p.AAC.mkv, Title.1080p.EC3.mkv.
  • +
+

Note: The --split-audio CLI flag overrides this setting. When --split-audio is passed, + merge_audio is effectively set to false for that run.

+
    +
  • merge_video + Merge video language variants into one file. Default: false
  • +
  • false: One MKV per video track (the default behaviour).
  • +
  • true: Group the selected video tracks by (resolution, range, codec) and merge + each group into one MKV, so only language varies within a file. The player switches + between the language tracks. No re-encode, no concatenation.
  • +
+

Only the language dimension is collapsed. Different resolutions, ranges + (SDR/HDR10/HDR10+/DV/HYBRID) and codecs (H264/H265) always stay in separate files. + For example, -r HYBRID,DV,HDR10,SDR --merge-video produces one file per range (never a + single combined file), while a title offering English + French video of the same + resolution/range/codec produces one file containing both video tracks.

+

Note: The --merge-video CLI flag overrides this setting. Can be set per service under + services.<TAG>.muxing.merge_video. Change group_videos_by_variant in + unshackle/commands/dl.py to adjust the grouping.

+
    +
  • +

    default_language (dict) + Override which track is flagged as the default in the muxed MKV, regardless + of the title's original language. Useful when you always want your player to + open on a specific language (e.g. always default to Polish audio even on + English originals). Only affects the MKV --default-track flag - track + selection (-l, --alang, etc.) is unchanged. All keys are optional; each + track type falls back to its previous default rule when the configured + language isn't present in the manifest.

    +
  • +
  • +

    audio: BCP-47 tag (e.g. pl, en, pt-BR). Wins over is_original_lang. + The --original-flag continues to mark the true original-audio track.

    +
  • +
  • video: BCP-47 tag. Wins over the title-language / first-track rule.
  • +
  • subtitle: BCP-47 tag. Wins over the "forced sub matching audio" rule.
  • +
+

Languages are matched with the same close-match logic used elsewhere + (pt matches pt-BR, etc.). Supports per-service overrides like the rest + of muxing.

+
muxing:
+  default_language:
+    audio: pl
+    video: pl
+    subtitle: pl
+
+
+

chapter_fallback_name (str)

+

The Chapter Name to use when exporting a Chapter without a Name. +The default is no fallback name at all and no Chapter name will be set.

+

The fallback name can use the following variables in f-string style:

+
    +
  • {i}: The Chapter number starting at 1. + E.g., "Chapter {i}": "Chapter 1", "Intro", "Chapter 3".
  • +
  • {j}: A number starting at 1 that increments any time a Chapter has no title. + E.g., "Chapter {j}": "Chapter 1", "Intro", "Chapter 2".
  • +
+

These are formatted with f-strings, directives are supported. +For example, "Chapter {i:02}" will result in "Chapter 01".

+
+

directories (dict)

+

Override the default directories used across unshackle. +The directories are set to common values by default.

+

The following directories are available and may be overridden,

+
    +
  • commands - CLI Command Classes.
  • +
  • services - Service Classes.
  • +
  • vaults - Vault Classes.
  • +
  • fonts - Font files (ttf or otf).
  • +
  • downloads - Downloads.
  • +
  • temp - Temporary files or conversions during download.
  • +
  • cache - Expiring data like Authorization tokens, or other misc data.
  • +
  • cookies - Expiring Cookie data.
  • +
  • logs - Logs.
  • +
  • exports - JSON sidecar exports written when --export is used on dl.
  • +
  • wvds - Widevine Devices.
  • +
  • prds - PlayReady Devices.
  • +
  • dcsl - Device Certificate Status List.
  • +
+

Notes:

+
    +
  • services accepts either a single directory or a list of directories to search for service modules. + Entries may also be git repo specs to load service packs hosted in a repo (see below).
  • +
+

For example,

+
directories:
+  downloads: "D:/Downloads/unshackle"
+  temp: "D:/Temp/unshackle"
+
+

There are directories not listed that cannot be modified as they are crucial to the operation of unshackle.

+

Loading services from a git repo

+

A services entry may be a git repo instead of a local path, letting you host service packs on +GitHub or any git host (GitLab, Gitea, self-hosted). Local paths and repo specs can be mixed:

+
directories:
+  services:
+    - https://github.com/you/your-services         # https repo (highest priority - listed first)
+    - git@gitlab.com:you/your-services.git         # ssh repo (private, via your git auth)
+    - you/your-services@main                        # owner/repo shorthand + optional @branch
+    - ~/my-local-services                          # local path (fallback - listed last)
+
+

How it works:

+
    +
  • On first use the repo is cloned (shallow) to <your-services-dir>/_repos/<repo-name>/ - the first + local services entry, or the bundled unshackle/services if you configured none. Nothing is + written to the cache directory.
  • +
  • After that, unshackle does not hit the network on every run. It re-pulls at most once every 24h, + or immediately when you run unshackle util refresh-services.
  • +
  • Requires git on your PATH. Private repos use your existing git credential helper - unshackle + stores no tokens. Git use is read-only on the remote - only clone, fetch, pull, and a + local reset are run; nothing is ever pushed.
  • +
  • Local edits and refresh. You can edit a clone under _repos/ directly. The two refresh paths + treat your edits differently:
  • +
  • Automatic (the 24h TTL during a normal dl/search run): if the clone has uncommitted + changes to tracked files or unpushed local commits, unshackle refuses to refresh and exits, + naming the clone, so a background pull never clobbers work in progress. Commit and push it + upstream (or revert), then it refreshes normally.
  • +
  • Manual (unshackle util refresh-services): an explicit "get upstream's latest" - it + hard-resets the clone to upstream, discarding local changes. Run it only when you want to + throw away local edits.
  • +
  • Untracked files (new service folders, __pycache__) never block the automatic path - a + fast-forward pull doesn't touch them.
  • +
  • The repo's top level must contain <TAG>/__init__.py service dirs (same layout as + unshackle/services/).
  • +
  • Priority is list order. The first source to define a tag is the one that loads; if a later + source (repo or local) has the same tag, that copy is treated as a duplicate and ignored. So list + the sources you trust most first - e.g. repos first and local last to make local a fallback, or + local first to let your local tweaks override a repo.
  • +
  • What you see on load. A one-line summary is logged each run, e.g.:
  • +
+
Loaded 103 services (36 duplicate(s) ignored)
+
+

The full per-duplicate detail is logged only at debug verbosity (unshackle -d ...), one line per + duplicate, naming the path that loaded and the path that was ignored:

+
EXAMPLE: using <unshackle>/services/EXAMPLE/__init__.py, ignoring duplicate <unshackle>/services/_repos/your-repo/EXAMPLE/__init__.py
+
+

Paths are shortened to <unshackle>/<venv>/~ tokens; set redact_paths: false to show full + absolute paths (see Debug Logging).

+

If <your-services-dir> is inside the installed package, a reinstall may remove the clones; they are +simply re-cloned on next use. On read-only installs, point services at a writable path.

+
+ + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/SERVICE_CONFIG/index.html b/SERVICE_CONFIG/index.html new file mode 100644 index 00000000..2caf2ab1 --- /dev/null +++ b/SERVICE_CONFIG/index.html @@ -0,0 +1,1916 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Service Integration & Auth - unshackle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+ +
+ + + + + + + + +

Service Integration & Authentication Configuration

+

This document covers service-specific configuration, authentication, and metadata integration options.

+

services (dict)

+

Configuration data for each Service. The Service will have the data within this section merged into the per-service +config.yaml (located in the service's directory) before being provided to the Service class.

+

Think of this config to be used for more sensitive configuration data, like user or device-specific API keys, IDs, +device attributes, and so on. A per-service config.yaml file is typically shared and not meant to be modified, +so use this for any sensitive configuration data.

+

The Key is the Service Tag, but can take any arbitrary form for its value. It's expected to begin as either a list or +a dictionary.

+

For example,

+
EXAMPLE:
+  client:
+    auth_scheme: MESSO
+    # ... more sensitive data
+
+

Per-Service Configuration Overrides

+

You can override many global configuration options on a per-service basis by nesting them under the +service tag in the services section. Supported override keys include: dl, subtitle, muxing, +headers, proxy_map, title_map, and more.

+

Overrides are merged with global config (not replaced) -- only specified keys are overridden, others +use global defaults.

+

Any dl command option can be overridden per service. Use the option name with dashes as underscores +(--v-lang -> v_lang). range and list work as-is; their internal Python names (range_, +list_, suffixed to avoid the builtins) are also accepted.

+

Precedence (highest first):

+
    +
  1. Explicit CLI arguments / environment variables (e.g. --v-lang en)
  2. +
  3. Per-service config (services.<TAG>.dl)
  4. +
  5. Global dl: config
  6. +
  7. Built-in option defaults
  8. +
+

For example,

+
services:
+  RATE_LIMITED_SERVICE:
+    dl:
+      downloads: 2       # Limit concurrent track downloads
+      workers: 4         # Reduce workers to avoid rate limits
+    headers:
+      User-Agent: "..."  # Service-specific UA override
+
+

Note: unshackle uses a single unified requests-based downloader. The legacy aria2c, +n_m3u8dl_re, and curl_impersonate override sections have been removed.

+

title_map (dict)

+

Rewrites service-provided titles before naming and output. Some services name a title differently +from how you want it stored, which can break library matching (e.g. a regional variant reusing the +international name). Keys are the exact title string the service returns; values are the desired +output title.

+
services:
+  EXAMPLE:
+    title_map:
+      Service Title: Desired Title
+
+

Episodes are matched on their show title, Movies and Songs on their name. The remap is applied +after the title cache (so edits take effect without a cache reset) and before any --enrich +override (so an explicit enrich still wins).

+

It applies on the local dl path, the import command, and the remote client (dl --remote). +For remote services the client's title_map is applied to the titles returned by the server, +so you can rename titles for services you don't have installed locally. The server sends raw +titles and does not remap, leaving the final name fully under the client's control.

+

Service Class Conventions

+

Each service directory under unshackle/services/ exports a class extending +unshackle.core.service.Service. The class name must match the directory name (the service tag).

+

Key class variables (defined on Service or by service-level idiom):

+
    +
  • ALIASES: tuple[str, ...] — alternative tags accepted on the CLI. Empty by default.
  • +
  • GEOFENCE: tuple[str, ...] — ISO country codes the service is available in. Empty == no geofence.
  • +
  • TITLE_RE: str — regex (with named groups, e.g. (?P<id>...), (?P<type>...)) used by the + service to parse the CLI title argument. Service-level idiom, not declared on the base class.
  • +
  • NO_SUBTITLES: bool — service-level idiom indicating the service has no subtitle tracks.
  • +
+

self.* helpers available after super().__init__(ctx):

+
    +
  • self.session — pre-configured HTTP session (requests.Session, or RnetSession when TLS + impersonation is active). Cookies, headers, proxies pre-applied.
  • +
  • self.config — merged service config (per-service config.yaml plus the services.<TAG> block + from unshackle.yaml).
  • +
  • self.log — logging.Logger named for the service class.
  • +
  • self.cache — generic Cacher for arbitrary key/value persistence.
  • +
  • self.title_cache — specialized TitleCacher for title metadata.
  • +
  • self.track_request — TrackRequest built from CLI flags. Fields: codecs: list[Video.Codec], + ranges: list[Video.Range] (defaults to [SDR]), best_available: bool. Services may + read or rewrite these (e.g. force HEVC for HDR ranges).
  • +
  • self.credential — set during authenticate(); None if cookies-only.
  • +
  • self.current_region — lowercase ISO country code from proxy/geolocation, or None.
  • +
  • self.request_input(prompt: str) -> str — interactive prompt. Falls through to input() + locally; under serve, the attached InputBridge relays the prompt to the remote client.
  • +
+

Driving CLI flags (parsed into self.track_request):

+
    +
  • -v / --vcodec — comma-separated Video.Codec list (e.g. H264,H265).
  • +
  • -a / --acodec — comma-separated audio codec list.
  • +
  • -r / --range — comma-separated Video.Range list (SDR, HDR10, HDR10+, DV, + HYBRID). Defaults to [SDR].
  • +
  • -q / --quality — resolution list.
  • +
  • --vbitrate-range / --abitrate-range — MIN-MAX kbps windows.
  • +
+
+

credentials (dict[str, str|list|dict])

+

Specify login credentials to use for each Service, and optionally per-profile.

+

For example,

+
EXAMPLE: jane@example.tld:LoremIpsum100 # directly
+EXAMPLE2: # or per-profile, optionally with a default
+  default: jane@example.tld:LoremIpsum99 # <-- used by default if -p/--profile is not used
+  james: james@example.tld:TheFriend97
+  john: john@example.tld:LoremIpsum98
+EXAMPLE3: # the `default` key is not necessary, but no credential will be used by default
+  john: john@example.tld:SecretPassword123
+
+

The value should be in string form, i.e. john@example.tld:password123 or john:password123. +Any arbitrary values can be used on the left (username/password/phone) and right (password/secret). +You can also specify these in list form, i.e., ["john@example.tld", ":PasswordWithAColon"].

+

If you specify multiple credentials with keys like the EXAMPLE2 and EXAMPLE3 example above, then you should +use a default key or no credential will be loaded automatically unless you use -p/--profile. You +do not have to use a default key at all.

+

Please be aware that this information is sensitive and to keep it safe. Do not share your config.

+
+

firefox_cookies (dict)

+

Automatically extract cookies from your running Firefox installation instead of maintaining a +cookies/<SERVICE>.txt file. Configured as a top-level dict keyed by service tag; each value is +a settings dict for that service.

+
firefox_cookies:
+  EXAMPLE:
+    hosts:
+      - example.com
+      - api.example.com
+    local_storage: false   # optional, default false
+
+

Fields:

+
    +
  • hosts — list of host patterns to match against Firefox's cookie store. Each pattern must be at + least 3 characters. A pattern matches the exact host and any dot-suffix subdomain + (e.g. example.com also matches sub.example.com and .example.com).
  • +
  • local_storage — when true, also harvest localStorage entries for the listed hosts from + Firefox's webappsstore.sqlite. Default: false.
  • +
+

At download time unshackle picks the most recently modified valid Firefox profile (the one +whose cookies.sqlite has the latest mtime), copies the database to a restricted temporary +directory (0700), and reads cookies from it without touching the live profile. If extraction +returns no cookies or fails for any reason, it falls back silently to the normal file-based cookie +(cookies/<SERVICE>.txt or cookies/<SERVICE>/<profile>.txt).

+

Notes:

+
    +
  • Firefox must not have an exclusive write lock on the database at the moment of extraction (i.e. + Firefox may be open — the extractor copies both cookies.sqlite and its WAL file to capture any + unflushed writes).
  • +
  • local_storage: true is only needed for services that store auth tokens in the browser's + localStorage rather than in HTTP cookies.
  • +
  • The feature is implemented in core/utils/firefox_cookie_extractor.py; the fallback logic lives + in dl.get_cookie_jar (commands/dl.py).
  • +
+
+

tmdb_api_key (str)

+

API key for The Movie Database (TMDB). This is used for tagging downloaded files with TMDB, +IMDB and TVDB identifiers. Leave empty to disable automatic lookups.

+

To obtain a TMDB API key:

+
    +
  1. Create an account at https://www.themoviedb.org/
  2. +
  3. Go to https://www.themoviedb.org/settings/api to register for API access
  4. +
  5. Fill out the API application form with your project details
  6. +
  7. Once approved, you'll receive your API key
  8. +
+

For example,

+
tmdb_api_key: cf66bf18956kca5311ada3bebb84eb9a # Not a real key
+
+
+

Note

+

Keep your API key secure and do not share it publicly. This key is used by the core/providers/tmdb.py metadata provider to fetch metadata from TMDB for proper file tagging and ID enrichment.

+
+
+

simkl_client_id (str)

+

Client ID for SIMKL API integration. SIMKL is used as a metadata source for improved title matching and tagging, +especially when a TMDB API key is not configured.

+

To obtain a SIMKL Client ID:

+
    +
  1. Create an account at https://simkl.com/
  2. +
  3. Go to https://simkl.com/settings/developer/
  4. +
  5. Register a new application to receive your Client ID
  6. +
+

For example,

+
simkl_client_id: "your_client_id_here"
+
+
+

Note

+

While optional, having a SIMKL Client ID improves metadata lookup reliability. SIMKL serves as an alternative or fallback metadata source to TMDB. This is used by the core/providers/simkl.py metadata provider.

+
+
+

ipinfo_api_key (str)

+

Optional API token for ipinfo.io. When set, unshackle uses the free authenticated Lite endpoint (https://api.ipinfo.io/lite/me), which has substantially higher rate limits than the anonymous endpoint and returns richer fields (ASN, organization name, continent). Leave empty to use the anonymous ipinfo.io endpoint, with ip-api.in as a final fallback.

+

To obtain an ipinfo.io token:

+
    +
  1. Sign up for a free account at https://ipinfo.io/signup
  2. +
  3. Copy the token from your dashboard
  4. +
+

For example,

+
ipinfo_api_key: "12a3b45cd678ef" # Not a real key
+
+
+

Note

+

The token is only ever sent to api.ipinfo.io as a per-request Authorization header — it is never attached to your session for service requests. Used by core/utils/ip_info.py for region detection and proxy verification.

+
+
+

title_cache_enabled (bool)

+

Enable/disable caching of title metadata to reduce redundant API calls. Default: true.

+
+

title_cache_time (int)

+

Cache duration in seconds for title metadata. Default: 1800 (30 minutes).

+
+

title_cache_max_retention (int)

+

Maximum retention time in seconds for serving slightly stale cached title metadata when API calls fail. +Default: 86400 (24 hours). Effective retention is min(title_cache_time + grace, title_cache_max_retention).

+
+

debug (bool)

+

Enable structured JSON debug logging for troubleshooting and service development. Default: false.

+

When enabled (via config or the --debug CLI flag): +- Creates JSON Lines (.jsonl) log files with complete debugging context +- Logs: session info, CLI params, service config, CDM details, authentication, titles, tracks metadata, + DRM operations, vault queries, errors with stack traces +- File location: logs/unshackle_debug_{service}_{timestamp}.jsonl

+
+

debug_keys (bool)

+

Log decryption keys in debug logs. Default: false.

+

When true, actual content encryption keys (CEKs) are included in debug log output. Useful for +debugging key retrieval and decryption issues.

+
+

Security note

+

Passwords, tokens, cookies, and session tokens are always redacted regardless +of this setting. Only content keys (content_key, key fields) are affected. Key IDs (kid), +key counts, and other metadata are always logged.

+
+
+ + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/SUBTITLE_CONFIG/index.html b/SUBTITLE_CONFIG/index.html new file mode 100644 index 00000000..8ec9c93c --- /dev/null +++ b/SUBTITLE_CONFIG/index.html @@ -0,0 +1,1529 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Subtitles - unshackle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ +
+ + + + + + + + +

Subtitle Processing Configuration

+

This document covers subtitle processing and formatting options under the top-level subtitle: key in unshackle.yaml.

+

This document is the canonical reference for subtitle configuration. unshackle/unshackle-example.yaml is a minimal starter only.

+

subtitle (dict)

+

Control subtitle conversion, SDH (hearing-impaired) stripping, formatting preservation, and output behavior.

+
    +
  • conversion_method: Which backend to convert subtitles with. Default: auto.
  • +
+

Routing is data-driven (unshackle/core/tracks/subtitle_convert.py): a registry of backends each + declares the source→target codec pairs it supports plus a preference rank. For a conversion, the + available backends that support the pair are tried in rank order — a real fallback chain. A + non-auto value pins that backend first, then still falls back through the chain if it can't + handle the pair or errors (pin-then-fallback). A service may also set preferred_conversion_method + on its tracks; an explicit conversion_method in config always wins.

+
    +
  • auto: Best available backend by rank — SubtitleEdit (if installed) for highest fidelity; + otherwise subby for WebVTT/fVTT/SAMI→SRT (adds CommonIssuesFixer cleanup), pysubs2 for SSA/ASS + and the broad format set, pycaption as last resort.
  • +
  • subby: Prefer subby (CommonIssuesFixer); reads WebVTT/fVTT/SAMI, writes SRT (and TTML/VTT via + an SRT intermediate).
  • +
  • subtitleedit: Prefer SubtitleEdit / seconv. Highest fidelity — preserves positioning/italics.
  • +
  • pycaption: Prefer pycaption. Flattens positioning/italics, writes only SRT/TTML/WebVTT.
  • +
  • pysubs2: Prefer pysubs2 (SRT, SSA, ASS, WebVTT, TTML, SAMI, MicroDVD, MPL2, TMP). The only + pure-Python backend that reads ASS/SSA, so it is the default for styled SubStation sources.
  • +
+

Styled-subtitle protection: ASS/SSA are never automatically downconverted to SRT (the + conversion is skipped and the original kept) — SRT cannot carry their positioning/colours/styling. + This applies to the default muxed track only; explicit requests still convert: a per-download + --sub-format srt for the muxed track, or sidecar_format: srt for sidecars. To keep raw styled + sidecars, set sidecar_format: original.

+

Segmented subtitles (fVTT/WVTT and fTTML/STPP from DASH/HLS) are read directly + from the fragmented MP4: fVTT via subby's WVTTConverter, fTTML via pycaption's box parsing. They + can be converted from but not to.

+

SubtitleEdit on Linux/macOS: install the SubtitleEdit 5+ CLI (SeConv / seconv, the + self-contained cross-platform build from the SubtitleEdit releases) onto PATH or into + unshackle/binaries/. unshackle targets the SubtitleEdit 5+ command syntax. The Windows + SubtitleEdit.exe is the GUI app — use the SeConv CLI binary for headless conversion.

+
    +
  • sdh_method: How to strip SDH cues. Default: auto.
  • +
  • auto: Try subby for SRT first, then SubtitleEdit (when conversion_method is auto/subtitleedit and the binary is available), then subtitle-filter as the final fallback.
  • +
  • subby: Use subby's SDHStripper. Only operates on SRT; for other codecs the call returns without stripping.
  • +
  • subtitleedit: Use SubtitleEdit's --remove-text-for-hi (SE5 CLI) when the binary is available; otherwise falls through to subtitle-filter.
  • +
  • +

    filter-subs: Use the subtitle-filter library directly (rm_fonts, rm_ast, rm_music, rm_effects, rm_names, rm_author).

    +
  • +
  • +

    strip_sdh: Enable/disable automatic SDH stripping for tracks flagged as SDH. Default: true.

    +
  • +
  • +

    convert_before_strip: When falling through to the subtitle-filter path, auto-convert non-SRT subtitles to SRT first for better compatibility. Default: true. Has no effect when SubtitleEdit handles stripping directly.

    +
  • +
  • +

    preserve_formatting: Keep original subtitle tags and positioning during WebVTT processing. When true, sanitized WebVTT is written back without round-tripping through pycaption, preserving tags like <i>, <b>, and line: positioning. Default: true.

    +
  • +
  • +

    output_mode: Controls how subtitles are included in the output. Default: mux.

    +
  • +
  • mux: Embed subtitles in the MKV container only.
  • +
  • sidecar: Save subtitles as separate files only (not muxed).
  • +
  • +

    both: Embed in the MKV container and save as sidecar files.

    +
  • +
  • +

    sidecar_format: Format for sidecar subtitle files (used when output_mode is sidecar or both). Default: srt.

    +
  • +
  • srt: SubRip.
  • +
  • vtt: WebVTT.
  • +
  • ass: Advanced SubStation Alpha.
  • +
  • original: Keep the subtitle in its current format without conversion.
  • +
+

Example:

+
subtitle:
+  conversion_method: auto
+  sdh_method: auto
+  strip_sdh: true
+  convert_before_strip: true
+  preserve_formatting: true
+  output_mode: mux
+  sidecar_format: srt
+
+

WebVTT Sanitization (automatic, not configurable)

+

After download, WebVTT and segmented WebVTT (fVTT/WVTT) tracks pass through a fixed sanitization pipeline before any conversion or muxing:

+
    +
  1. Segment merge — segmented DASH/HLS WebVTT is stitched via merge_segmented_webvtt (uses pysubs2 for lenient parsing when conversion_method is auto or pysubs2, otherwise pycaption directly).
  2. +
  3. Negative timestamps — sanitize_webvtt_timestamps rewrites -HH:MM:SS.mmm cues to 00:00:00.000.
  4. +
  5. Cue identifiers — sanitize_webvtt_cue_identifiers strips letter+digit IDs (e.g. Q0, S12) on their own line before a timing line, which otherwise confuse parsers like pysubs2.
  6. +
  7. Overlapping cues — merge_overlapping_webvtt_cues collapses cues with start times within 50 ms and matching end times into a single multi-line cue, ordered by line: percentage (lower % = higher on screen = first line).
  8. +
  9. Fallback hardening — when preserve_formatting is false and the first pycaption parse fails, sanitize_webvtt retries with a WEBVTT header guard, hour-padded timings, and another negative-timestamp pass; if that still fails, the sanitized text is written as-is.
  10. +
+

sanitize_broken_webvtt and space_webvtt_headers additionally run inside Subtitle.parse() to drop malformed --> lines and reflow merged-segment headers. merge_same_cues and filter_unwanted_cues (drops &nbsp;/whitespace-only cues) run only on the pycaption path.

+

These behaviors are intentional and have no config knobs — they apply to every WebVTT track regardless of conversion_method.

+ +
    +
  • Filename sanitization (e.g. parenthesis handling, unidecode bracket artifacts from PR #105) lives in unshackle/core/utilities.py::sanitize_filename and is governed by output_template, not the subtitle: config block.
  • +
  • Subtitle codec support is defined in unshackle/core/tracks/subtitle.py; the conversion backend + registry, capability matrix, and ranks live in unshackle/core/tracks/subtitle_convert.py.
  • +
+
+ + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/api/authentication/index.html b/api/authentication/index.html new file mode 100644 index 00000000..e4c18fe9 --- /dev/null +++ b/api/authentication/index.html @@ -0,0 +1,1507 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Authentication - unshackle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+ +
+ + + + + + + + +

Authentication

+

When api_secret is set in unshackle.yaml, all API requests require the X-Secret-Key header. There is no query-parameter fallback. /api/health is always reachable without authentication. --no-key disables auth entirely (not recommended for public-facing servers).

+
# unshackle.yaml
+serve:
+  api_secret: "your-master-secret"          # falls back to global users map below
+  remote_only: false                         # also toggleable via --remote-only
+  services: ["EXAMPLE1", "EXAMPLE2"]         # optional global service allowlist
+  users:
+    user-secret-1:
+      username: alice
+      devices: ["my_widevine_l3"]            # Widevine WVD names this user may use
+      playready_devices: ["my_pr_sl2000"]    # PlayReady PRD names; defaults to [] (no access)
+      services: ["EXAMPLE1"]                  # optional per-user allowlist (intersected with global)
+    user-secret-2:
+      username: bob
+      devices: []
+      playready_devices: []
+
+

Service allowlists

+

config.serve.services is the global allowlist; users.<key>.services further narrows it per key. The effective set is the intersection. Endpoints affected: /api/services, /api/search, /api/list-titles, /api/list-tracks, /api/download, and all /api/session/* routes.

+

CDM access (server-side decryption)

+

There is no separate "tier" flag. Whether the server can return KID:KEY for a session-mode download depends solely on the device lists configured for the calling user key:

+
    +
  • Empty devices and playready_devices -> server can only proxy CDM challenges; the client must run its own CDM and parse the license.
  • +
  • Populated lists -> the client may set mode: "server_cdm" on /api/session/{id}/license and receive { "keys": { "<track_id>": { "<KID>": "<KEY>" } } } instead of raw license bytes.
  • +
+

Per-service CDM type can be pinned via config.cdm (widevine/playready) or per-service cdm_type; otherwise the server picks the type the user has devices for.

+

Server-side dl defaults

+

Any flag accepted by /api/download (see the table below) can be declared under serve: in unshackle.yaml and the API will apply it as a default. Request-body values still win. Useful for raising concurrency without changing every client call:

+
serve:
+  api_secret: "..."
+  users: { ... }
+  downloads: 4        # parallel tracks per download job
+  workers: 16         # threads per track segment fetch
+  best_available: true
+  no_proxy_download: false
+
+

Layering order: built-in defaults < serve.* overrides < service-specific click defaults < request body.

+

Per-request override gates

+

Two security gates under serve: guard capabilities that /api/download clients can request. Both default OFF; an unset or false value causes the server to reject the request with 403 FORBIDDEN.

+

cdm_overrides — a /api/download job body may include a cdm field to select a specific server-side device. When this gate is off, every such request is rejected. Set to true to allow any device the server has, or to a list of device names to restrict to an explicit subset:

+
serve:
+  # Allow any server-side device:
+  cdm_overrides: true
+
+  # Or restrict to specific devices:
+  cdm_overrides:
+    - generic_nexus_4464_l3
+
+

allow_job_credentials — a /api/download job body may include a credential (single) or credentials (map) field to authenticate the job with client-supplied secrets instead of the server's configured credentials. When this gate is off, any job containing either field is rejected. Each distinct credential gets its own isolated token cache on the server. Set to true to allow:

+
serve:
+  allow_job_credentials: true
+
+

Both gates are independent and can be combined. A typical locked-down deployment leaves both unset; a trusted single-client deployment might enable both.

+ + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/api/client-configuration/index.html b/api/client-configuration/index.html new file mode 100644 index 00000000..702da27e --- /dev/null +++ b/api/client-configuration/index.html @@ -0,0 +1,1514 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Client Configuration - unshackle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ +
+ + + + + + + + +

Client Configuration: remote_services

+

remote_services is a top-level block in unshackle.yaml on the client machine. It tells dl how to reach a remote unshackle serve instance instead of running service code locally. The client handles track selection, download, decrypt, and mux; the server handles auth, manifest parsing, and (optionally) DRM licensing.

+

CLI flags

+ + + + + + + + + + + + + + + + + +
FlagDescription
--remoteUse a remote server instead of local service code. Reads remote_services from config.
--server <name>Select a named server when multiple are configured. If only one entry exists it is picked automatically.
+
unshackle dl --remote EXAMPLE1 "https://example.com/show/abc123"
+unshackle dl --remote --server us-server EXAMPLE1 "https://example.com/show/abc123"
+
+

Config structure

+
remote_services:
+  my-server:
+    url: "http://192.168.1.100:8786"   # base URL of the remote serve instance
+    api_key: "your-secret-key-here"    # sent as X-Secret-Key; omit or set "" for --no-key servers
+
+    # server_cdm: the server runs its own CDM and returns KID:KEY pairs (mode: server_cdm).
+    # false (default): client runs its own CDM; license challenges are proxied through the server.
+    server_cdm: false
+
+    # Per-service overrides applied on the client side after the remote session is set up.
+    # Supported keys: decryption, downloader, cdm (and any free-form service config keys).
+    services:
+      EXAMPLE1:
+        decryption: mp4decrypt   # override decryption tool for this service (shaka or mp4decrypt)
+
+

server_cdm — when false (default) the client builds CDM challenges locally and calls POST /api/session/{id}/license with mode: "proxy"; the server forwards the challenge to the service's license endpoint and returns raw license bytes. When true the client skips local CDM setup and calls with mode: "server_cdm"; the server performs the full CDM flow and returns { "keys": { "<KID>": "<KEY>" } }. The server-side user key must have a matching device configured (devices / playready_devices) for server_cdm to work.

+

Multiple servers

+

When more than one server is configured, --server <name> is required:

+
remote_services:
+  us-server:
+    url: "https://us.example.com:8786"
+    api_key: "us-key"
+    server_cdm: true
+    services:
+      EXAMPLE1:
+        decryption: mp4decrypt
+
+  eu-server:
+    url: "https://eu.example.com:8786"
+    api_key: "eu-key"
+    server_cdm: false
+
+
unshackle dl --remote --server us-server EXAMPLE1 "https://example.com/show/abc123"
+
+

Cache forwarding

+

On session open the client sends any local *.json cache files for the service (tokens, etc.) to the server so it can skip interactive re-auth. On session close the server returns updated cache files; the client writes them back locally so the next remote session can forward them again.

+ + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/api/download-job-lifecycle/index.html b/api/download-job-lifecycle/index.html new file mode 100644 index 00000000..6154fed9 --- /dev/null +++ b/api/download-job-lifecycle/index.html @@ -0,0 +1,1397 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Download Job Lifecycle - unshackle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ +
+ + + + + + + + +

Download Job Lifecycle

+
queued -> downloading -> completed
+                     \-> failed
+queued -> cancelled
+downloading -> cancelled
+
+

Progress reporting

+

A multi-episode request (wanted: ["S01E01", ...]) is a single job that processes episodes sequentially. During a run the job status updates live: current_title names the episode downloading now, output_files grows as each episode finishes muxing, and track_progress lists per-track progress when downloads > 1 runs tracks concurrently. progress and track counts reset as each episode starts; use output_files against parameters.wanted to derive overall completion.

+

Jobs are retained for 24 hours after completion (override via top-level download_job_retention_hours in unshackle.yaml). The server runs up to 2 concurrent download jobs by default; override via top-level max_concurrent_downloads. This is independent of serve.downloads, which controls parallel tracks within a single job.

+

Remote sessions are managed by SessionStore (unshackle/core/api/session_store.py); idle sessions and their InputBridge instances are cleaned up by a background loop started/stopped with the app lifecycle.

+ + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/api/endpoint-map/index.html b/api/endpoint-map/index.html new file mode 100644 index 00000000..4bd8119a --- /dev/null +++ b/api/endpoint-map/index.html @@ -0,0 +1,1424 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Endpoint Map - unshackle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ +
+ + + + + + + + +

Endpoint Map

+

Standard endpoints (suppressed in --remote-only mode are marked R):

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodPathR
GET/api/healthok
GET/api/servicesok
POST/api/searchok
POST/api/list-titleshidden
POST/api/list-trackshidden
POST/api/downloadhidden
GET/api/download/jobshidden
GET/api/download/jobs/{job_id}hidden
DELETE/api/download/jobs/{job_id}hidden
POST/api/session/createok
GET/api/session/{session_id}ok
DELETE/api/session/{session_id}ok
GET/api/session/{session_id}/titlesok
POST/api/session/{session_id}/tracksok
POST/api/session/{session_id}/segmentsok
POST/api/session/{session_id}/licenseok
GET/api/session/{session_id}/promptok
POST/api/session/{session_id}/promptok
+

CDM endpoints (/{wvd}/..., /playready/{prd}/...) are exposed unless --api-only / --remote-only / --no-widevine / --no-playready is set, and use pywidevine / pyplayready's own auth scheme.

+ + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/api/endpoints/index.html b/api/endpoints/index.html new file mode 100644 index 00000000..1dcb7db4 --- /dev/null +++ b/api/endpoints/index.html @@ -0,0 +1,2256 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Endpoints - unshackle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+ +
+ + + + + + + + +

Endpoints

+

GET /api/health

+

Health check with version and update information. Always reachable without auth.

+
curl http://localhost:8786/api/health
+
+
{
+  "status": "ok",
+  "version": "4.0.0",
+  "update_check": {
+    "update_available": false,
+    "current_version": "4.0.0",
+    "latest_version": null
+  }
+}
+
+
+

GET /api/services

+

List all available streaming services (filtered by the effective allowlist for the caller).

+
curl -H "X-Secret-Key: $KEY" http://localhost:8786/api/services
+
+

Returns {"services": [...]}. Each entry has tag, aliases, geofence, title_regex, url (from cli.short_help), help (full docstring), and cli_params describing the service-level Click parameters.

+
+

POST /api/search

+

Search for titles from a streaming service.

+

Required parameters: +| Parameter | Type | Description | +| --- | --- | --- | +| service | string | Service tag | +| query | string | Search query |

+

Optional parameters: +| Parameter | Type | Default | Description | +| --- | --- | --- | --- | +| profile | string | null | Profile for credentials/cookies | +| proxy | string | null | Proxy URI or country code | +| no_proxy | boolean | false | Disable all proxy use |

+
curl -X POST http://localhost:8786/api/search \
+  -H "X-Secret-Key: $KEY" \
+  -H "Content-Type: application/json" \
+  -d '{"service": "EXAMPLE1", "query": "example show"}'
+
+
{
+  "results": [
+    {
+      "id": "abc123def456",
+      "title": "Example Show",
+      "description": null,
+      "label": "TV Show",
+      "url": "https://example.com/show/abc123def456"
+    }
+  ],
+  "count": 1
+}
+
+
+

POST /api/list-titles

+

Get available titles (seasons/episodes/movies) for a service and title ID. Disabled in --remote-only mode.

+

Required parameters: +| Parameter | Type | Description | +| --- | --- | --- | +| service | string | Service tag | +| title_id | string | Title ID or URL |

+
curl -X POST http://localhost:8786/api/list-titles \
+  -H "X-Secret-Key: $KEY" \
+  -H "Content-Type: application/json" \
+  -d '{"service": "EXAMPLE1", "title_id": "abc123def456"}'
+
+
+

POST /api/list-tracks

+

Get video, audio, and subtitle tracks for a title. Disabled in --remote-only mode.

+

Required parameters: +| Parameter | Type | Description | +| --- | --- | --- | +| service | string | Service tag | +| title_id | string | Title ID or URL |

+

Optional parameters: +| Parameter | Type | Default | Description | +| --- | --- | --- | --- | +| wanted | array | all | Episode filter (e.g., ["S01E01"]) | +| profile | string | null | Profile for credentials/cookies | +| proxy | string | null | Proxy URI or country code | +| no_proxy | boolean | false | Disable all proxy use |

+

Returns video, audio, and subtitle tracks with codec, bitrate, resolution, language, and DRM information.

+
+

POST /api/download

+

Start a download job. Returns immediately with a job ID (HTTP 202). Disabled in --remote-only mode.

+

Required parameters: +| Parameter | Type | Description | +| --- | --- | --- | +| service | string | Service tag | +| title_id | string | Title ID or URL |

+

Quality and codec parameters: +| Parameter | Type | Default | Description | +| --- | --- | --- | --- | +| quality | array[int] | best | Resolution(s) (e.g., [1080, 2160]) | +| vcodec | string or array | any | Video codec(s): H264, H265/HEVC, VP9, AV1, VC1, VP8 | +| acodec | string or array | any | Audio codec(s): AAC, AC3, EC3, AC4, OPUS, FLAC, ALAC, DTS, OGG | +| vbitrate | int | highest | Video bitrate in kbps | +| abitrate | int | highest | Audio bitrate in kbps | +| range | array[string] | ["SDR"] | Color range(s): SDR, HDR10, HDR10+, HLG, DV, HYBRID | +| channels | float | any | Audio channels (e.g., 5.1, 7.1) | +| no_atmos | boolean | false | Exclude Dolby Atmos tracks | +| split_audio | boolean | null | Create separate output per audio codec | +| sub_format | string | null | Output subtitle format: SRT, VTT, ASS, SSA, TTML |

+

Episode selection: +| Parameter | Type | Default | Description | +| --- | --- | --- | --- | +| wanted | array[string] | all | Episodes (e.g., ["S01E01", "S01E02-S01E05"]) | +| latest_episode | boolean | false | Download only the most recent episode |

+

Language parameters: +| Parameter | Type | Default | Description | +| --- | --- | --- | --- | +| lang | array[string] | ["orig"] | Language for video and audio (orig = original) | +| v_lang | array[string] | [] | Language override for video tracks only | +| a_lang | array[string] | [] | Language override for audio tracks only | +| s_lang | array[string] | ["all"] | Language for subtitles | +| require_subs | array[string] | [] | Required subtitle languages (skip if missing) | +| forced_subs | boolean | false | Include forced subtitle tracks | +| exact_lang | boolean | false | Exact language matching (no variants) |

+

Track selection: +| Parameter | Type | Default | Description | +| --- | --- | --- | --- | +| video_only | boolean | false | Only download video tracks | +| audio_only | boolean | false | Only download audio tracks | +| subs_only | boolean | false | Only download subtitle tracks | +| chapters_only | boolean | false | Only download chapters | +| no_video | boolean | false | Skip video tracks | +| no_audio | boolean | false | Skip audio tracks | +| no_subs | boolean | false | Skip subtitle tracks | +| no_chapters | boolean | false | Skip chapters | +| audio_description | boolean | false | Include audio description tracks |

+

Output and tagging: +| Parameter | Type | Default | Description | +| --- | --- | --- | --- | +| tag | string | null | Override group tag | +| repack | boolean | false | Add REPACK tag to filename | +| tmdb_id | int | null | Use specific TMDB ID for tagging | +| imdb_id | string | null | Use specific IMDB ID (e.g., tt1375666) | +| animeapi_id | string | null | Anime database ID via AnimeAPI (e.g., mal:12345) | +| enrich | boolean | false | Override show title and year from external source | +| no_folder | boolean | false | Disable folder creation for TV shows | +| no_source | boolean | false | Remove source tag from filename | +| no_mux | boolean | false | Do not mux tracks into container | +| output_dir | string | null | Override output directory |

+

Download behavior: +| Parameter | Type | Default | Description | +| --- | --- | --- | --- | +| profile | string | null | Profile for credentials/cookies | +| proxy | string | null | Proxy URI or country code | +| no_proxy | boolean | false | Disable all proxy use | +| no_proxy_download | boolean | false | Bypass proxy for segment downloads only. Manifest, license, and auth still use proxy | +| workers | int | null | Max threads per track download | +| downloads | int | 1 | Concurrent track downloads | +| slow | boolean or string | null | Add randomized delay between titles. true = 60-120s, or "MIN-MAX" string (e.g., "20-40"). Min must be >= 20 | +| best_available | boolean | false | Continue if requested quality unavailable | +| worst | boolean | false | Select the lowest bitrate track within the specified quality. Requires quality | +| skip_dl | boolean | false | Skip download, only get decryption keys | +| export | boolean | false | Export manifest, track URLs, keys, and subtitles to JSON in the exports directory | +| cdm_only | boolean | null | Only use CDM (true) or only vaults (false) | +| no_cache | boolean | false | Bypass title cache | +| reset_cache | boolean | false | Clear title cache before fetching |

+

Example:

+
curl -X POST http://localhost:8786/api/download \
+  -H "X-Secret-Key: $KEY" \
+  -H "Content-Type: application/json" \
+  -d '{
+    "service": "EXAMPLE1",
+    "title_id": "abc123def456",
+    "wanted": ["S01E01"],
+    "quality": [1080, 2160],
+    "vcodec": ["H265"],
+    "acodec": ["AAC", "EC3"],
+    "range": ["HDR10", "SDR"],
+    "split_audio": true,
+    "lang": ["en"]
+  }'
+
+
{
+  "job_id": "504db959-80b0-446c-a764-7924b761d613",
+  "status": "queued",
+  "created_time": "2026-02-27T18:00:00.000000"
+}
+
+
+

GET /api/download/jobs

+

List all download jobs with optional filtering and sorting. Disabled in --remote-only mode.

+

Query parameters: +| Parameter | Type | Default | Description | +| --- | --- | --- | --- | +| status | string | all | Filter by status: queued, downloading, completed, failed, cancelled | +| service | string | all | Filter by service tag | +| sort_by | string | created_time | Sort field: created_time, started_time, completed_time, progress, status, service | +| sort_order | string | desc | Sort order: asc, desc | +| full | string | false | true to include full job details per job (parameters, timestamps, output_files, error fields) |

+
curl -H "X-Secret-Key: $KEY" "http://localhost:8786/api/download/jobs?status=completed"
+
+
+

GET /api/download/jobs/{job_id}

+

Get detailed information about a specific download job including progress, parameters, and error details.

+
{
+  "job_id": "504db959-80b0-446c-a764-7924b761d613",
+  "status": "downloading",
+  "created_time": "2026-02-27T18:00:00.000000",
+  "service": "EXAMPLE1",
+  "title_id": "abc123def456",
+  "title": "Example Show",
+  "progress": 74.9,
+  "phase": "downloading video 1080p SDR",
+  "current_title": "S01E04",
+  "completed_tracks": 4,
+  "total_tracks": 13,
+  "active_tracks": ["video 1080p SDR"],
+  "track_progress": [
+    { "label": "video 1080p SDR", "progress": 74.9, "speed": "DASH 265.5 MB/s" }
+  ],
+  "segments_done": 75.0,
+  "segments_total": 100.0,
+  "speed": "DASH 265.5 MB/s",
+  "parameters": { },
+  "started_time": "2026-02-27T18:00:01.000000",
+  "completed_time": null,
+  "output_files": [
+    "/downloads/Example.Show.S01E01.mkv",
+    "/downloads/Example.Show.S01E02.mkv",
+    "/downloads/Example.Show.S01E03.mkv"
+  ],
+  "error_message": null,
+  "error_details": null
+}
+
+

Progress fields:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
progressnumberOverall progress for the title currently downloading, 0–100. Resets per episode on multi-episode jobs
phasestringHuman-readable current step, e.g. downloading video 1080p SDR, muxing
titlestringHuman-readable show/movie name, available once the download starts
current_titlestringSxxEyy code of the episode downloading now (episodic jobs only)
completed_tracks / total_tracksintTrack counts for the current title
active_tracksarray[string]Labels of tracks downloading now
track_progressarrayPer-track {label, progress, speed} for each concurrently active track
segments_done / segments_totalnumberSegment counts of the track downloading now
speedstringTransfer speed of the track downloading now
output_filesarray[string]Final file paths, appended as each title finishes muxing
+

The list endpoint (GET /api/download/jobs) returns the same progress fields per job but omits parameters, timestamps, output_files, and error details.

+
+

DELETE /api/download/jobs/{job_id}

+

Cancel a queued or running download job, or remove a finished one.

+
    +
  • queued / downloading job: cancelled, returns 200 with {"status": "success", "message": "Job cancelled"}.
  • +
  • completed / failed / cancelled job: removed from the manager entirely, returns 204 (no body).
  • +
  • Unknown job: 404.
  • +
+
+

POST /api/download/jobs/clear-finished

+

Remove all completed, failed, and cancelled jobs from the manager. Disabled in --remote-only mode.

+
{ "removed": 3 }
+
+
+

POST /api/download/jobs/{job_id}/retry

+

Enqueue a new job reusing a finished job's service, title, and original (unredacted) parameters. Only allowed on completed, failed, or cancelled jobs — returns 409 otherwise, 404 if unknown. The original job is left in place.

+

Creation-time authorization is re-applied at retry time: the job's service must pass the caller's service allowlist (otherwise 400 invalid service), and jobs whose original parameters carried a cdm override or per-job credentials return 403 if serve.cdm_overrides / serve.allow_job_credentials no longer permit them.

+

Returns 202 with the same shape as POST /api/download:

+
{
+  "job_id": "b7e9c1f2-...",
+  "status": "queued",
+  "created_time": "2026-02-27T18:00:00.000000"
+}
+
+
+

POST /api/download/jobs/{job_id}/priority

+

Move a queued job to the front of the download queue so a worker picks it up next. Returns 409 if the job is not queued, 404 if unknown.

+
{ "job_id": "504db959-...", "position": "front" }
+
+
+

GET /api/profiles

+

List named credential profiles per service, usable as the profile parameter on search/list/download. Only services whose credentials are configured as a profile-name: credential mapping appear (a default key is included if present); a service with a single plain (unnamed) credential is omitted entirely. Filtered by the caller's service allowlist. Disabled in --remote-only mode.

+
curl -H "X-Secret-Key: $KEY" http://localhost:8786/api/profiles
+
+
{ "profiles": { "EXAMPLE1": ["default", "uk", "us"], "EXAMPLE2": ["alice"] } }
+
+
+

GET /api/config

+

Read-only, redacted view of the effective server configuration for a UI settings page. Secrets (api_secret, users, credentials, tokens) are never included; secret-looking keys inside dl are masked with ***. Disabled in --remote-only mode.

+
curl -H "X-Secret-Key: $KEY" http://localhost:8786/api/config
+
+
{
+  "config": {
+    "dl": { "lang": "orig", "sub_format": "srt", "downloads": 12 },
+    "serve": {
+      "max_concurrent_downloads": 2,
+      "job_retention_hours": 24,
+      "history_limit": 100,
+      "services": null,
+      "remote_only": false,
+      "cdm_overrides": null,
+      "allow_job_credentials": false
+    },
+    "directories": {
+      "downloads": "/srv/downloads",
+      "temp": "/srv/temp",
+      "cache": "/srv/cache"
+    },
+    "services": ["EXAMPLE1", "EXAMPLE2"]
+  }
+}
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
dlDefault dl parameters from the config file (redacted)
serve.servicesGlobal service allowlist, or null when all services are allowed
serve.cdm_overridesPermitted per-request CDM device names (list), true, or null
servicesAvailable service tags, filtered by the caller's allowlist
+
+

GET /api/history

+

Persistent download history. Every job that reaches a terminal state (completed, failed, cancelled) is appended as one JSON line to api_history.jsonl in the cache directory, surviving restarts and job cleanup. Only the newest serve.history_limit entries are retained (default 100, 0 for unlimited; see Advanced Config). Newest first. Corrupt lines are skipped; a missing file yields an empty list. Entries are filtered by the caller's service allowlist. Disabled in --remote-only mode.

+

Query parameters: +| Parameter | Type | Default | Description | +| --- | --- | --- | --- | +| limit | int | 100 | Maximum entries returned (must be >= 1) | +| service | string | all | Filter by service tag (case-insensitive) |

+
curl -H "X-Secret-Key: $KEY" "http://localhost:8786/api/history?limit=50&service=EXAMPLE1"
+
+
{
+  "history": [
+    {
+      "job_id": "504db959-80b0-446c-a764-7924b761d613",
+      "service": "EXAMPLE1",
+      "title_id": "abc123def456",
+      "title": "Example Show",
+      "status": "completed",
+      "created_time": "2026-02-27T18:00:00.000000",
+      "completed_time": "2026-02-27T18:12:34.000000",
+      "output_files": ["/downloads/Example.Show.S01E01.mkv"],
+      "parameters": { "wanted": ["S01E01"], "quality": [1080], "skip_dl": false },
+      "error_message": null
+    }
+  ],
+  "count": 1
+}
+
+

error_message is redacted with the same secret-scrubbing applied to job responses. parameters is the redacted download selection the job ran with (quality/vcodec/range/wanted/skip_dl, etc.). completed_time is null for jobs cancelled while still queued.

+
+

DELETE /api/history/{job_id}

+

Remove a single history entry by job_id (the file is rewritten without it). Returns 204 on success, 404 if no matching entry exists or it falls outside the caller's service allowlist. Corrupt lines in the history file are preserved.

+
curl -X DELETE -H "X-Secret-Key: $KEY" "http://localhost:8786/api/history/504db959-80b0-446c-a764-7924b761d613"
+
+
+

POST /api/maintenance/clear-cache

+

Delete the contents of the cache directory (the directory is recreated empty). freed_bytes is best effort. This also removes api_history.jsonl, so persistent download history is cleared. Returns 409 with the active job ids if any job is currently downloading, since in-flight jobs may be reading the cache. Disabled in --remote-only mode.

+
curl -X POST -H "X-Secret-Key: $KEY" http://localhost:8786/api/maintenance/clear-cache
+
+
{ "cleared": true, "freed_bytes": 1234567 }
+
+
+

POST /api/maintenance/clear-temp

+

Same as clear-cache, for the temp directory. Also refuses with 409 while any job is downloading — active workers write segment files under temp.

+
{ "cleared": true, "freed_bytes": 0 }
+
+
+

POST /api/maintenance/refresh-services

+

Force-sync (git pull, hard reset to upstream) every service repo configured in directories.services. refreshed is true when all repos synced (or none are configured). Disabled in --remote-only mode.

+
curl -X POST -H "X-Secret-Key: $KEY" http://localhost:8786/api/maintenance/refresh-services
+
+
{
+  "refreshed": true,
+  "repos": [
+    { "spec": "https://github.com/example/services", "updated": true, "changes": ["~ EXAMPLE1 (modified)"] }
+  ]
+}
+
+

An empty changes array means the repo was already up to date. A repo that failed to sync has updated: false (and flips refreshed to false).

+
+

GET /api/env/check

+

Install status of the binaries unshackle env check inspects (FFmpeg, MKVToolNix, shaka-packager, etc.). version is a best-effort probe and may be null even when installed. Disabled in --remote-only mode.

+
curl -H "X-Secret-Key: $KEY" http://localhost:8786/api/env/check
+
+
{
+  "checks": [
+    { "name": "FFmpeg", "installed": true, "version": "7.1.0", "required": true },
+    { "name": "mp4decrypt", "installed": false, "version": null, "required": false }
+  ]
+}
+
+ + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/api/error-responses/index.html b/api/error-responses/index.html new file mode 100644 index 00000000..4baca3e7 --- /dev/null +++ b/api/error-responses/index.html @@ -0,0 +1,1343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Error Responses - unshackle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ +
+ + + + + + + + +

Error Responses

+

All endpoints return consistent error responses:

+
{
+  "status": "error",
+  "error_code": "INVALID_PARAMETERS",
+  "message": "Invalid vcodec: XYZ. Must be one of: H264, H265, VP9, AV1, VC1, VP8",
+  "timestamp": "2026-02-27T18:00:00.000000+00:00",
+  "details": { }
+}
+
+

Common error codes:

+
    +
  • INVALID_INPUT -- malformed request body
  • +
  • INVALID_PARAMETERS -- invalid parameter values
  • +
  • MISSING_SERVICE -- service tag not provided
  • +
  • INVALID_SERVICE -- service not found or not in the caller's allowlist
  • +
  • SERVICE_ERROR -- service initialization or runtime error
  • +
  • AUTH_FAILED -- authentication failure
  • +
  • NOT_FOUND / TRACK_NOT_FOUND / session not found -- job/session/track/title missing
  • +
  • INTERNAL_ERROR -- unexpected server error
  • +
+

When --debug-api is enabled, error responses include additional debug_info with tracebacks and stderr output.

+

Authentication errors from the auth middleware are returned as {"status": 401, "message": "..."} (not the standard error envelope).

+ + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/api/quick-start/index.html b/api/quick-start/index.html new file mode 100644 index 00000000..5b8f430f --- /dev/null +++ b/api/quick-start/index.html @@ -0,0 +1,1391 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Quick Start - unshackle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ +
+ + + + + + + + +

Quick Start

+
# Start the server (no authentication)
+unshackle serve --no-key
+
+# Start with authentication (api_secret in unshackle.yaml)
+unshackle serve
+
+# Serve only the REST API (no pywidevine/pyplayready CDM)
+unshackle serve --api-only
+
+# Serve only the remote-dl session endpoints (CORS/Cloudflare friendly)
+unshackle serve --remote-only
+
+# Disable just one CDM
+unshackle serve --no-widevine
+unshackle serve --no-playready
+
+# Verbose error responses (tracebacks/stderr in JSON)
+unshackle serve --debug-api
+
+

serve flags:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FlagDescription
-h, --hostBind host (default 127.0.0.1)
-p, --portBind port (default 8786)
--caddyAlso launch Caddy using Caddyfile next to the unshackle config
--api-onlyREST API only; skip the bundled pywidevine/pyplayready CDM endpoints
--no-widevineDisable Widevine CDM endpoints
--no-playreadyDisable PlayReady CDM endpoints
--no-keyDisable API key authentication entirely
--debug-apiInclude tracebacks/stderr in error responses
--debugEnable DEBUG-level logging for API operations
--remote-onlyExpose only /api/health, /api/services, /api/search, and /api/session/* (implies --api-only)
+ + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/api/remote-service-sessions/index.html b/api/remote-service-sessions/index.html new file mode 100644 index 00000000..8683d7aa --- /dev/null +++ b/api/remote-service-sessions/index.html @@ -0,0 +1,1860 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Remote Service Sessions - unshackle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+ +
+ + + + + + + + +

Remote Service Sessions

+

These endpoints back the RemoteService adapter in unshackle/core/remote_service.py. They let a thin dl client (or any consumer) authenticate against a service on the server, fetch titles/tracks/manifests, and either proxy CDM challenges or have the server resolve KID:KEY directly. The dl command's RemoteService adapter replaces the old remote_dl command. These endpoints are the only /api/* routes available in --remote-only mode (in addition to health, services, and search).

+

POST /api/session/create

+

Authenticate against a service and open a session. Body fields:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
servicestringService tag (required)
title_idstringTitle ID/URL (required)
credentialsobjectAuth credentials forwarded to Service.authenticate
cookiesstringCookie blob (Netscape or JSON)
proxystringProxy URI or country code
no_proxyboolForce-disable proxies
profilestringProfile name
cacheobjectOptional pre-warmed title cache payload
+

If the service requires interactive input during authentication, poll GET /api/session/{id}/prompt and submit responses via POST /api/session/{id}/prompt until status is authenticated.

+

Request:

+
{
+  "service": "EXAMPLE1",
+  "title_id": "abc123def456",
+  "credentials": {"username": "alice", "password": "hunter2"},
+  "cookies": "# Netscape HTTP Cookie File\n...",
+  "proxy": "us",
+  "no_proxy": false,
+  "profile": "default",
+  "cache": {}
+}
+
+

Response (202-style; auth runs asynchronously):

+
{
+  "session_id": "f1c4a8b2-9c7e-4d2a-bf91-2d3e4f5a6b7c",
+  "service": "EXAMPLE1",
+  "status": "authenticating"
+}
+
+

GET /api/session/{session_id}

+

Returns session metadata. 404 if expired or unknown.

+
{
+  "session_id": "f1c4a8b2-9c7e-4d2a-bf91-2d3e4f5a6b7c",
+  "service": "EXAMPLE1",
+  "valid": true,
+  "expires_in": 3600,
+  "track_count": 0,
+  "title_count": 0
+}
+
+

DELETE /api/session/{session_id}

+

Tears down the session, cancels any pending prompts, and returns any updated per-session cache files (base64-encoded, zlib-compressed) so the client can re-warm next time.

+
{
+  "status": "ok",
+  "cache": {
+    "tokens": "eJzLSM3JyVcozy/KSVGo5AIAGgQEvQ=="
+  }
+}
+
+

GET /api/session/{session_id}/titles

+

Returns the resolved titles list.

+
{
+  "session_id": "f1c4a8b2-9c7e-4d2a-bf91-2d3e4f5a6b7c",
+  "titles": [
+    {
+      "type": "episode",
+      "name": "Pilot",
+      "series_title": "Example Show",
+      "season": 1,
+      "number": 1,
+      "year": 2024,
+      "id": "ep-0001",
+      "language": "en"
+    },
+    {
+      "type": "movie",
+      "name": "Example Movie",
+      "year": 2024,
+      "id": "mov-0001",
+      "language": "en"
+    }
+  ]
+}
+
+

POST /api/session/{session_id}/tracks

+

Request:

+
{"title_id": "ep-0001"}
+
+

Response:

+
{
+  "title": {
+    "type": "episode",
+    "name": "Pilot",
+    "series_title": "Example Show",
+    "season": 1,
+    "number": 1,
+    "year": 2024,
+    "id": "ep-0001",
+    "language": "en"
+  },
+  "video": [
+    {
+      "id": "v-1080p-h264",
+      "codec": "H264",
+      "codec_display": "H.264",
+      "bitrate": 6000,
+      "width": 1920,
+      "height": 1080,
+      "resolution": "1920x1080",
+      "fps": "23.976",
+      "range": "SDR",
+      "range_display": "SDR",
+      "language": "en",
+      "drm": [
+        {
+          "type": "widevine",
+          "pssh": "AAAAW3Bzc2gAAAAA7e+...",
+          "kids": ["abcdef0123456789abcdef0123456789"],
+          "license_url": "https://license.example.com/widevine"
+        }
+      ],
+      "descriptor": "DASH",
+      "url": "https://cdn.example.com/manifest.mpd"
+    }
+  ],
+  "audio": [
+    {
+      "id": "a-en-eac3",
+      "codec": "EC3",
+      "codec_display": "Dolby Digital Plus",
+      "bitrate": 640,
+      "channels": "5.1",
+      "language": "en",
+      "atmos": false,
+      "descriptive": false,
+      "drm": null,
+      "descriptor": "DASH",
+      "url": "https://cdn.example.com/manifest.mpd"
+    }
+  ],
+  "subtitles": [
+    {
+      "id": "s-en-vtt",
+      "codec": "WebVTT",
+      "language": "en",
+      "forced": false,
+      "sdh": false,
+      "cc": false,
+      "descriptor": "DASH",
+      "url": "https://cdn.example.com/subs/en.vtt"
+    }
+  ],
+  "chapters": [
+    {"timestamp": "00:00:00.000", "name": "Chapter 1"}
+  ],
+  "attachments": [],
+  "manifests": [
+    {
+      "type": "dash",
+      "url": "https://cdn.example.com/manifest.mpd",
+      "data": "eJzNVk1v2zAM/Ss..."
+    }
+  ],
+  "session_headers": {
+    "User-Agent": "Mozilla/5.0 ..."
+  },
+  "session_cookies": {
+    "session": "abc123"
+  },
+  "server_cdm_type": "widevine"
+}
+
+

POST /api/session/{session_id}/segments

+

Request:

+
{"track_ids": ["v-1080p-h264", "a-en-eac3"]}
+
+

Response:

+
{
+  "tracks": {
+    "v-1080p-h264": {
+      "descriptor": "DASH",
+      "url": "https://cdn.example.com/manifest.mpd",
+      "drm": [
+        {
+          "type": "widevine",
+          "pssh": "AAAAW3Bzc2gAAAAA7e+...",
+          "kids": ["abcdef0123456789abcdef0123456789"],
+          "license_url": "https://license.example.com/widevine"
+        }
+      ],
+      "headers": {"User-Agent": "Mozilla/5.0 ..."},
+      "cookies": {"session": "abc123"},
+      "data": {}
+    },
+    "a-en-eac3": {
+      "descriptor": "DASH",
+      "url": "https://cdn.example.com/manifest.mpd",
+      "drm": null,
+      "headers": {"User-Agent": "Mozilla/5.0 ..."},
+      "cookies": {"session": "abc123"},
+      "data": {}
+    }
+  }
+}
+
+

POST /api/session/{session_id}/license

+

Two modes, selected by the mode field.

+

mode: "proxy" (default) -- forward a client-built CDM challenge to the service's license endpoint.

+

Request:

+
{
+  "mode": "proxy",
+  "track_id": "v-1080p-h264",
+  "challenge": "CAESxQEK...",
+  "drm_type": "widevine",
+  "pssh": "AAAAW3Bzc2gAAAAA7e+..."
+}
+
+

Response:

+
{"license": "CAIS3wIK..."}
+
+

mode: "server_cdm" -- the server uses its own CDM to license the track and extract keys. Single-track form takes track_id; batch form takes track_ids. Requires the calling user key to have a matching device (devices for Widevine, playready_devices for PlayReady) in unshackle.yaml.

+

Request (batch):

+
{
+  "mode": "server_cdm",
+  "track_ids": ["v-1080p-h264", "a-en-eac3"],
+  "drm_type": "widevine"
+}
+
+

Response:

+
{
+  "keys": {
+    "v-1080p-h264": {
+      "abcdef0123456789abcdef0123456789": "00112233445566778899aabbccddeeff"
+    },
+    "a-en-eac3": {
+      "abcdef0123456789abcdef0123456789": "00112233445566778899aabbccddeeff"
+    }
+  },
+  "drm_type": "widevine"
+}
+
+

GET /api/session/{session_id}/prompt

+

Polled by the client during interactive authentication (OTP, PIN, device codes). Backed by the InputBridge in unshackle/core/api/input_bridge.py; Service.request_input() blocks server-side until the client posts a response.

+

Pending input:

+
{"status": "pending_input", "prompt": "Enter OTP code: "}
+
+

Other states:

+
{"status": "authenticating"}
+
+
{"status": "authenticated"}
+
+
{"status": "failed", "error": "Invalid credentials"}
+
+

POST /api/session/{session_id}/prompt

+

Unblocks the server-side request_input() call.

+

Request:

+
{"response": "123456"}
+
+

Response:

+
{"status": "accepted"}
+
+ + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/favicon.svg b/assets/favicon.svg new file mode 100644 index 00000000..b2834eab --- /dev/null +++ b/assets/favicon.svg @@ -0,0 +1 @@ + diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..1cf13b9f9d978896599290a74f77d5dbe7d1655c GIT binary patch literal 1870 zcmV-U2eJ5xP)Gc)JR9QMau)O=X#!i9;T z37kk-upj^(fsR36MHs_+1RCI)NNu9}lD0S{B^g8PN?Ww(5|~L#Ng*g{WsqleV}|#l zz8@ri&cTzw_h33bHI+12+kK6WN$h#n5cD8OQt`5kw6p~9H3()bUQ8OS4Q4HTQ=1Ol z_JAocz`fLbT2^{`8n~UAo=#AUOf=SOq4pYkt;XbC&f#7lb$*7=$na!mWCQ`dBQsO0 zLFBSPj*N?#u5&pf2t4XjEGH|=pPQ8xh7tpx;US5Cx_Ju;!O`ya-yF`)b%TEt5>eP1ZX~}sjjA%FJF?h7cX8=b!DZl<6%Cv z*G0uvvU+vmnpLZ2paivG-(cd*y3$hCIcsZcYOGh{$&)A6*XX&kXZd3G8m)G$Zz-LV z^GF3VAW^Mdv!)4OM8EgqRiz~*Cji;uzl2uC9^=8I84vNp;ltJ|q-*uQwGp2ma6cY7 z;`%`!9UXO@fr&Ebapfs34OmS9^u6$)bJxrucutf>`dKPKT%%*d3XlFVKunp9 zasduxjrjs>f8V=D|J=XNZp;_Zy^WgQ$9WDjgY=z@stwiEBm9u5*|34&1Na8BMjjgf3+SHcr`5~>oz1Y?SW^=K z^bTyO6>Gar#P_W2gEMwq)ot3; zREHn~U&Dp0l6YT0&k-wLwYjb?5zGK`W6S2v+K>AM(95m2C20L|3m~rN8dprPr@t)5lsk9Hu*W z?pS990s;Ez=+Rj{x7p``4>+c0G5^pYnB1^!TL=(?HLHZ+HicG{~4F1d^5Awl_2!1jICM-!9eoLhbbT^;yHcefyTAaqRcY zmuctDopPT!%k+}x%lZRKnzykr2}}XfG_ne?nRQO~?%hkzo;@RN{P6o`&mMUWBYMTe z6i8ChtjX&gXl`nvrU>jah)2iNM%JdjqoaeaU%yVn!^70x-flljp6Q5tK}5}&X8&&G zX3fpb3E(!rH=zVI_9Gjl45w@{(ITqngWFe7@9{mX;tO25Z_8 zQHEpI+FkTU#4xu>RkN>b3Tnc3UpWzPXWm#o55GKF09j^Mh~)K7{QqbO_~(@CVq! zS<8954|P8mXN2MRs86xZ&Q4EfM@JB94b=(YGuk)s&^jiSF=t3*oNK3`rD{H`yQ?d; ztE=laAUoZx5?RC8*WKOj`%LXEkgDd>&^Q4M^z`%u0rg-It=hLCVsq!Z%^6eB-OvOT zFZ28TN&cRmgU}Elrnk43)!>Z1FCPL2K$7}gwzIc48NX}#!A1BpJP?#v5wkNprhV** z?Cpalt1oH&{r!o3eSKc&ap)iz2BTn_VV`4>9M^b3;(YY}4>#ML6{~(4mH+?%07*qo IM6N<$f(jP3KmY&$ literal 0 HcmV?d00001 diff --git a/assets/javascripts/bundle.79ae519e.min.js b/assets/javascripts/bundle.79ae519e.min.js new file mode 100644 index 00000000..3df3e5e6 --- /dev/null +++ b/assets/javascripts/bundle.79ae519e.min.js @@ -0,0 +1,16 @@ +"use strict";(()=>{var Zi=Object.create;var _r=Object.defineProperty;var ea=Object.getOwnPropertyDescriptor;var ta=Object.getOwnPropertyNames,Bt=Object.getOwnPropertySymbols,ra=Object.getPrototypeOf,Ar=Object.prototype.hasOwnProperty,bo=Object.prototype.propertyIsEnumerable;var ho=(e,t,r)=>t in e?_r(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,P=(e,t)=>{for(var r in t||(t={}))Ar.call(t,r)&&ho(e,r,t[r]);if(Bt)for(var r of Bt(t))bo.call(t,r)&&ho(e,r,t[r]);return e};var vo=(e,t)=>{var r={};for(var o in e)Ar.call(e,o)&&t.indexOf(o)<0&&(r[o]=e[o]);if(e!=null&&Bt)for(var o of Bt(e))t.indexOf(o)<0&&bo.call(e,o)&&(r[o]=e[o]);return r};var Cr=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var oa=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of ta(t))!Ar.call(e,n)&&n!==r&&_r(e,n,{get:()=>t[n],enumerable:!(o=ea(t,n))||o.enumerable});return e};var $t=(e,t,r)=>(r=e!=null?Zi(ra(e)):{},oa(t||!e||!e.__esModule?_r(r,"default",{value:e,enumerable:!0}):r,e));var go=(e,t,r)=>new Promise((o,n)=>{var i=c=>{try{a(r.next(c))}catch(p){n(p)}},s=c=>{try{a(r.throw(c))}catch(p){n(p)}},a=c=>c.done?o(c.value):Promise.resolve(c.value).then(i,s);a((r=r.apply(e,t)).next())});var xo=Cr((kr,yo)=>{(function(e,t){typeof kr=="object"&&typeof yo!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(kr,(function(){"use strict";function e(r){var o=!0,n=!1,i=null,s={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function a(k){return!!(k&&k!==document&&k.nodeName!=="HTML"&&k.nodeName!=="BODY"&&"classList"in k&&"contains"in k.classList)}function c(k){var ut=k.type,je=k.tagName;return!!(je==="INPUT"&&s[ut]&&!k.readOnly||je==="TEXTAREA"&&!k.readOnly||k.isContentEditable)}function p(k){k.classList.contains("focus-visible")||(k.classList.add("focus-visible"),k.setAttribute("data-focus-visible-added",""))}function l(k){k.hasAttribute("data-focus-visible-added")&&(k.classList.remove("focus-visible"),k.removeAttribute("data-focus-visible-added"))}function f(k){k.metaKey||k.altKey||k.ctrlKey||(a(r.activeElement)&&p(r.activeElement),o=!0)}function u(k){o=!1}function d(k){a(k.target)&&(o||c(k.target))&&p(k.target)}function v(k){a(k.target)&&(k.target.classList.contains("focus-visible")||k.target.hasAttribute("data-focus-visible-added"))&&(n=!0,window.clearTimeout(i),i=window.setTimeout(function(){n=!1},100),l(k.target))}function S(k){document.visibilityState==="hidden"&&(n&&(o=!0),X())}function X(){document.addEventListener("mousemove",ee),document.addEventListener("mousedown",ee),document.addEventListener("mouseup",ee),document.addEventListener("pointermove",ee),document.addEventListener("pointerdown",ee),document.addEventListener("pointerup",ee),document.addEventListener("touchmove",ee),document.addEventListener("touchstart",ee),document.addEventListener("touchend",ee)}function re(){document.removeEventListener("mousemove",ee),document.removeEventListener("mousedown",ee),document.removeEventListener("mouseup",ee),document.removeEventListener("pointermove",ee),document.removeEventListener("pointerdown",ee),document.removeEventListener("pointerup",ee),document.removeEventListener("touchmove",ee),document.removeEventListener("touchstart",ee),document.removeEventListener("touchend",ee)}function ee(k){k.target.nodeName&&k.target.nodeName.toLowerCase()==="html"||(o=!1,re())}document.addEventListener("keydown",f,!0),document.addEventListener("mousedown",u,!0),document.addEventListener("pointerdown",u,!0),document.addEventListener("touchstart",u,!0),document.addEventListener("visibilitychange",S,!0),X(),r.addEventListener("focus",d,!0),r.addEventListener("blur",v,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)}))});var ro=Cr((jy,Rn)=>{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var qa=/["'&<>]/;Rn.exports=Ka;function Ka(e){var t=""+e,r=qa.exec(t);if(!r)return t;var o,n="",i=0,s=0;for(i=r.index;i{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof Nt=="object"&&typeof io=="object"?io.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Nt=="object"?Nt.ClipboardJS=r():t.ClipboardJS=r()})(Nt,function(){return(function(){var e={686:(function(o,n,i){"use strict";i.d(n,{default:function(){return Xi}});var s=i(279),a=i.n(s),c=i(370),p=i.n(c),l=i(817),f=i.n(l);function u(q){try{return document.execCommand(q)}catch(C){return!1}}var d=function(C){var _=f()(C);return u("cut"),_},v=d;function S(q){var C=document.documentElement.getAttribute("dir")==="rtl",_=document.createElement("textarea");_.style.fontSize="12pt",_.style.border="0",_.style.padding="0",_.style.margin="0",_.style.position="absolute",_.style[C?"right":"left"]="-9999px";var D=window.pageYOffset||document.documentElement.scrollTop;return _.style.top="".concat(D,"px"),_.setAttribute("readonly",""),_.value=q,_}var X=function(C,_){var D=S(C);_.container.appendChild(D);var N=f()(D);return u("copy"),D.remove(),N},re=function(C){var _=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},D="";return typeof C=="string"?D=X(C,_):C instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(C==null?void 0:C.type)?D=X(C.value,_):(D=f()(C),u("copy")),D},ee=re;function k(q){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?k=function(_){return typeof _}:k=function(_){return _&&typeof Symbol=="function"&&_.constructor===Symbol&&_!==Symbol.prototype?"symbol":typeof _},k(q)}var ut=function(){var C=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},_=C.action,D=_===void 0?"copy":_,N=C.container,G=C.target,We=C.text;if(D!=="copy"&&D!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(G!==void 0)if(G&&k(G)==="object"&&G.nodeType===1){if(D==="copy"&&G.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(D==="cut"&&(G.hasAttribute("readonly")||G.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(We)return ee(We,{container:N});if(G)return D==="cut"?v(G):ee(G,{container:N})},je=ut;function R(q){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?R=function(_){return typeof _}:R=function(_){return _&&typeof Symbol=="function"&&_.constructor===Symbol&&_!==Symbol.prototype?"symbol":typeof _},R(q)}function se(q,C){if(!(q instanceof C))throw new TypeError("Cannot call a class as a function")}function ce(q,C){for(var _=0;_0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof N.action=="function"?N.action:this.defaultAction,this.target=typeof N.target=="function"?N.target:this.defaultTarget,this.text=typeof N.text=="function"?N.text:this.defaultText,this.container=R(N.container)==="object"?N.container:document.body}},{key:"listenClick",value:function(N){var G=this;this.listener=p()(N,"click",function(We){return G.onClick(We)})}},{key:"onClick",value:function(N){var G=N.delegateTarget||N.currentTarget,We=this.action(G)||"copy",Yt=je({action:We,container:this.container,target:this.target(G),text:this.text(G)});this.emit(Yt?"success":"error",{action:We,text:Yt,trigger:G,clearSelection:function(){G&&G.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(N){return Mr("action",N)}},{key:"defaultTarget",value:function(N){var G=Mr("target",N);if(G)return document.querySelector(G)}},{key:"defaultText",value:function(N){return Mr("text",N)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(N){var G=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return ee(N,G)}},{key:"cut",value:function(N){return v(N)}},{key:"isSupported",value:function(){var N=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],G=typeof N=="string"?[N]:N,We=!!document.queryCommandSupported;return G.forEach(function(Yt){We=We&&!!document.queryCommandSupported(Yt)}),We}}]),_})(a()),Xi=Ji}),828:(function(o){var n=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function s(a,c){for(;a&&a.nodeType!==n;){if(typeof a.matches=="function"&&a.matches(c))return a;a=a.parentNode}}o.exports=s}),438:(function(o,n,i){var s=i(828);function a(l,f,u,d,v){var S=p.apply(this,arguments);return l.addEventListener(u,S,v),{destroy:function(){l.removeEventListener(u,S,v)}}}function c(l,f,u,d,v){return typeof l.addEventListener=="function"?a.apply(null,arguments):typeof u=="function"?a.bind(null,document).apply(null,arguments):(typeof l=="string"&&(l=document.querySelectorAll(l)),Array.prototype.map.call(l,function(S){return a(S,f,u,d,v)}))}function p(l,f,u,d){return function(v){v.delegateTarget=s(v.target,f),v.delegateTarget&&d.call(l,v)}}o.exports=c}),879:(function(o,n){n.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},n.nodeList=function(i){var s=Object.prototype.toString.call(i);return i!==void 0&&(s==="[object NodeList]"||s==="[object HTMLCollection]")&&"length"in i&&(i.length===0||n.node(i[0]))},n.string=function(i){return typeof i=="string"||i instanceof String},n.fn=function(i){var s=Object.prototype.toString.call(i);return s==="[object Function]"}}),370:(function(o,n,i){var s=i(879),a=i(438);function c(u,d,v){if(!u&&!d&&!v)throw new Error("Missing required arguments");if(!s.string(d))throw new TypeError("Second argument must be a String");if(!s.fn(v))throw new TypeError("Third argument must be a Function");if(s.node(u))return p(u,d,v);if(s.nodeList(u))return l(u,d,v);if(s.string(u))return f(u,d,v);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function p(u,d,v){return u.addEventListener(d,v),{destroy:function(){u.removeEventListener(d,v)}}}function l(u,d,v){return Array.prototype.forEach.call(u,function(S){S.addEventListener(d,v)}),{destroy:function(){Array.prototype.forEach.call(u,function(S){S.removeEventListener(d,v)})}}}function f(u,d,v){return a(document.body,u,d,v)}o.exports=c}),817:(function(o){function n(i){var s;if(i.nodeName==="SELECT")i.focus(),s=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var a=i.hasAttribute("readonly");a||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),a||i.removeAttribute("readonly"),s=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var c=window.getSelection(),p=document.createRange();p.selectNodeContents(i),c.removeAllRanges(),c.addRange(p),s=c.toString()}return s}o.exports=n}),279:(function(o){function n(){}n.prototype={on:function(i,s,a){var c=this.e||(this.e={});return(c[i]||(c[i]=[])).push({fn:s,ctx:a}),this},once:function(i,s,a){var c=this;function p(){c.off(i,p),s.apply(a,arguments)}return p._=s,this.on(i,p,a)},emit:function(i){var s=[].slice.call(arguments,1),a=((this.e||(this.e={}))[i]||[]).slice(),c=0,p=a.length;for(c;c0&&i[i.length-1])&&(p[0]===6||p[0]===2)){r=0;continue}if(p[0]===3&&(!i||p[1]>i[0]&&p[1]=e.length&&(e=void 0),{value:e&&e[o++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function K(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],s;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(a){s={error:a}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(s)throw s.error}}return i}function B(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o1||c(d,S)})},v&&(n[d]=v(n[d])))}function c(d,v){try{p(o[d](v))}catch(S){u(i[0][3],S)}}function p(d){d.value instanceof dt?Promise.resolve(d.value.v).then(l,f):u(i[0][2],d)}function l(d){c("next",d)}function f(d){c("throw",d)}function u(d,v){d(v),i.shift(),i.length&&c(i[0][0],i[0][1])}}function To(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof Oe=="function"?Oe(e):e[Symbol.iterator](),r={},o("next"),o("throw"),o("return"),r[Symbol.asyncIterator]=function(){return this},r);function o(i){r[i]=e[i]&&function(s){return new Promise(function(a,c){s=e[i](s),n(a,c,s.done,s.value)})}}function n(i,s,a,c){Promise.resolve(c).then(function(p){i({value:p,done:a})},s)}}function I(e){return typeof e=="function"}function yt(e){var t=function(o){Error.call(o),o.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var Jt=yt(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(o,n){return n+1+") "+o.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function Ze(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var qe=(function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,o,n,i;if(!this.closed){this.closed=!0;var s=this._parentage;if(s)if(this._parentage=null,Array.isArray(s))try{for(var a=Oe(s),c=a.next();!c.done;c=a.next()){var p=c.value;p.remove(this)}}catch(S){t={error:S}}finally{try{c&&!c.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}else s.remove(this);var l=this.initialTeardown;if(I(l))try{l()}catch(S){i=S instanceof Jt?S.errors:[S]}var f=this._finalizers;if(f){this._finalizers=null;try{for(var u=Oe(f),d=u.next();!d.done;d=u.next()){var v=d.value;try{So(v)}catch(S){i=i!=null?i:[],S instanceof Jt?i=B(B([],K(i)),K(S.errors)):i.push(S)}}}catch(S){o={error:S}}finally{try{d&&!d.done&&(n=u.return)&&n.call(u)}finally{if(o)throw o.error}}}if(i)throw new Jt(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)So(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&Ze(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Ze(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=(function(){var t=new e;return t.closed=!0,t})(),e})();var $r=qe.EMPTY;function Xt(e){return e instanceof qe||e&&"closed"in e&&I(e.remove)&&I(e.add)&&I(e.unsubscribe)}function So(e){I(e)?e():e.unsubscribe()}var De={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var xt={setTimeout:function(e,t){for(var r=[],o=2;o0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var o=this,n=this,i=n.hasError,s=n.isStopped,a=n.observers;return i||s?$r:(this.currentObservers=null,a.push(r),new qe(function(){o.currentObservers=null,Ze(a,r)}))},t.prototype._checkFinalizedStatuses=function(r){var o=this,n=o.hasError,i=o.thrownError,s=o.isStopped;n?r.error(i):s&&r.complete()},t.prototype.asObservable=function(){var r=new F;return r.source=this,r},t.create=function(r,o){return new Ho(r,o)},t})(F);var Ho=(function(e){ie(t,e);function t(r,o){var n=e.call(this)||this;return n.destination=r,n.source=o,n}return t.prototype.next=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.next)===null||n===void 0||n.call(o,r)},t.prototype.error=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.error)===null||n===void 0||n.call(o,r)},t.prototype.complete=function(){var r,o;(o=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||o===void 0||o.call(r)},t.prototype._subscribe=function(r){var o,n;return(n=(o=this.source)===null||o===void 0?void 0:o.subscribe(r))!==null&&n!==void 0?n:$r},t})(T);var jr=(function(e){ie(t,e);function t(r){var o=e.call(this)||this;return o._value=r,o}return Object.defineProperty(t.prototype,"value",{get:function(){return this.getValue()},enumerable:!1,configurable:!0}),t.prototype._subscribe=function(r){var o=e.prototype._subscribe.call(this,r);return!o.closed&&r.next(this._value),o},t.prototype.getValue=function(){var r=this,o=r.hasError,n=r.thrownError,i=r._value;if(o)throw n;return this._throwIfClosed(),i},t.prototype.next=function(r){e.prototype.next.call(this,this._value=r)},t})(T);var Rt={now:function(){return(Rt.delegate||Date).now()},delegate:void 0};var It=(function(e){ie(t,e);function t(r,o,n){r===void 0&&(r=1/0),o===void 0&&(o=1/0),n===void 0&&(n=Rt);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=o,i._timestampProvider=n,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=o===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,o),i}return t.prototype.next=function(r){var o=this,n=o.isStopped,i=o._buffer,s=o._infiniteTimeWindow,a=o._timestampProvider,c=o._windowTime;n||(i.push(r),!s&&i.push(a.now()+c)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var o=this._innerSubscribe(r),n=this,i=n._infiniteTimeWindow,s=n._buffer,a=s.slice(),c=0;c0?e.prototype.schedule.call(this,r,o):(this.delay=o,this.state=r,this.scheduler.flush(this),this)},t.prototype.execute=function(r,o){return o>0||this.closed?e.prototype.execute.call(this,r,o):this._execute(r,o)},t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!=null&&n>0||n==null&&this.delay>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.flush(this),0)},t})(St);var Ro=(function(e){ie(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t})(Ot);var Dr=new Ro(Po);var Io=(function(e){ie(t,e);function t(r,o){var n=e.call(this,r,o)||this;return n.scheduler=r,n.work=o,n}return t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!==null&&n>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.actions.push(this),r._scheduled||(r._scheduled=Tt.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,o,n){var i;if(n===void 0&&(n=0),n!=null?n>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,o,n);var s=r.actions;o!=null&&o===r._scheduled&&((i=s[s.length-1])===null||i===void 0?void 0:i.id)!==o&&(Tt.cancelAnimationFrame(o),r._scheduled=void 0)},t})(St);var Fo=(function(e){ie(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var o;r?o=r.id:(o=this._scheduled,this._scheduled=void 0);var n=this.actions,i;r=r||n.shift();do if(i=r.execute(r.state,r.delay))break;while((r=n[0])&&r.id===o&&n.shift());if(this._active=!1,i){for(;(r=n[0])&&r.id===o&&n.shift();)r.unsubscribe();throw i}},t})(Ot);var ye=new Fo(Io);var y=new F(function(e){return e.complete()});function tr(e){return e&&I(e.schedule)}function Vr(e){return e[e.length-1]}function pt(e){return I(Vr(e))?e.pop():void 0}function Fe(e){return tr(Vr(e))?e.pop():void 0}function rr(e,t){return typeof Vr(e)=="number"?e.pop():t}var Lt=(function(e){return e&&typeof e.length=="number"&&typeof e!="function"});function or(e){return I(e==null?void 0:e.then)}function nr(e){return I(e[wt])}function ir(e){return Symbol.asyncIterator&&I(e==null?void 0:e[Symbol.asyncIterator])}function ar(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function fa(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var sr=fa();function cr(e){return I(e==null?void 0:e[sr])}function pr(e){return wo(this,arguments,function(){var r,o,n,i;return Gt(this,function(s){switch(s.label){case 0:r=e.getReader(),s.label=1;case 1:s.trys.push([1,,9,10]),s.label=2;case 2:return[4,dt(r.read())];case 3:return o=s.sent(),n=o.value,i=o.done,i?[4,dt(void 0)]:[3,5];case 4:return[2,s.sent()];case 5:return[4,dt(n)];case 6:return[4,s.sent()];case 7:return s.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function lr(e){return I(e==null?void 0:e.getReader)}function U(e){if(e instanceof F)return e;if(e!=null){if(nr(e))return ua(e);if(Lt(e))return da(e);if(or(e))return ha(e);if(ir(e))return jo(e);if(cr(e))return ba(e);if(lr(e))return va(e)}throw ar(e)}function ua(e){return new F(function(t){var r=e[wt]();if(I(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function da(e){return new F(function(t){for(var r=0;r=2;return function(o){return o.pipe(e?g(function(n,i){return e(n,i,o)}):be,Ee(1),r?Qe(t):tn(function(){return new fr}))}}function Yr(e){return e<=0?function(){return y}:E(function(t,r){var o=[];t.subscribe(w(r,function(n){o.push(n),e=2,!0))}function le(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new T}:t,o=e.resetOnError,n=o===void 0?!0:o,i=e.resetOnComplete,s=i===void 0?!0:i,a=e.resetOnRefCountZero,c=a===void 0?!0:a;return function(p){var l,f,u,d=0,v=!1,S=!1,X=function(){f==null||f.unsubscribe(),f=void 0},re=function(){X(),l=u=void 0,v=S=!1},ee=function(){var k=l;re(),k==null||k.unsubscribe()};return E(function(k,ut){d++,!S&&!v&&X();var je=u=u!=null?u:r();ut.add(function(){d--,d===0&&!S&&!v&&(f=Br(ee,c))}),je.subscribe(ut),!l&&d>0&&(l=new bt({next:function(R){return je.next(R)},error:function(R){S=!0,X(),f=Br(re,n,R),je.error(R)},complete:function(){v=!0,X(),f=Br(re,s),je.complete()}}),U(k).subscribe(l))})(p)}}function Br(e,t){for(var r=[],o=2;oe.next(document)),e}function M(e,t=document){return Array.from(t.querySelectorAll(e))}function j(e,t=document){let r=ue(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function ue(e,t=document){return t.querySelector(e)||void 0}function Ne(){var e,t,r,o;return(o=(r=(t=(e=document.activeElement)==null?void 0:e.shadowRoot)==null?void 0:t.activeElement)!=null?r:document.activeElement)!=null?o:void 0}var Ra=L(h(document.body,"focusin"),h(document.body,"focusout")).pipe(Ae(1),Q(void 0),m(()=>Ne()||document.body),Z(1));function Ye(e){return Ra.pipe(m(t=>e.contains(t)),Y())}function it(e,t){return H(()=>L(h(e,"mouseenter").pipe(m(()=>!0)),h(e,"mouseleave").pipe(m(()=>!1))).pipe(t?jt(r=>He(+!r*t)):be,Q(e.matches(":hover"))))}function sn(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)sn(e,r)}function x(e,t,...r){let o=document.createElement(e);if(t)for(let n of Object.keys(t))typeof t[n]!="undefined"&&(typeof t[n]!="boolean"?o.setAttribute(n,t[n]):o.setAttribute(n,""));for(let n of r)sn(o,n);return o}function br(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function _t(e){let t=x("script",{src:e});return H(()=>(document.head.appendChild(t),L(h(t,"load"),h(t,"error").pipe(b(()=>Nr(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(m(()=>{}),A(()=>document.head.removeChild(t)),Ee(1))))}var cn=new T,Ia=H(()=>typeof ResizeObserver=="undefined"?_t("https://unpkg.com/resize-observer-polyfill"):$(void 0)).pipe(m(()=>new ResizeObserver(e=>e.forEach(t=>cn.next(t)))),b(e=>L(tt,$(e)).pipe(A(()=>e.disconnect()))),Z(1));function de(e){return{width:e.offsetWidth,height:e.offsetHeight}}function Le(e){let t=e;for(;t.clientWidth===0&&t.parentElement;)t=t.parentElement;return Ia.pipe(O(r=>r.observe(t)),b(r=>cn.pipe(g(o=>o.target===t),A(()=>r.unobserve(t)))),m(()=>de(e)),Q(de(e)))}function At(e){return{width:e.scrollWidth,height:e.scrollHeight}}function vr(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}function pn(e){let t=[],r=e.parentElement;for(;r;)(e.clientWidth>r.clientWidth||e.clientHeight>r.clientHeight)&&t.push(r),r=(e=r).parentElement;return t.length===0&&t.push(document.documentElement),t}function Be(e){return{x:e.offsetLeft,y:e.offsetTop}}function ln(e){let t=e.getBoundingClientRect();return{x:t.x+window.scrollX,y:t.y+window.scrollY}}function mn(e){return L(h(window,"load"),h(window,"resize")).pipe($e(0,ye),m(()=>Be(e)),Q(Be(e)))}function gr(e){return{x:e.scrollLeft,y:e.scrollTop}}function Ge(e){return L(h(e,"scroll"),h(window,"scroll"),h(window,"resize")).pipe($e(0,ye),m(()=>gr(e)),Q(gr(e)))}var fn=new T,Fa=H(()=>$(new IntersectionObserver(e=>{for(let t of e)fn.next(t)},{threshold:0}))).pipe(b(e=>L(tt,$(e)).pipe(A(()=>e.disconnect()))),Z(1));function mt(e){return Fa.pipe(O(t=>t.observe(e)),b(t=>fn.pipe(g(({target:r})=>r===e),A(()=>t.unobserve(e)),m(({isIntersecting:r})=>r))))}function un(e,t=16){return Ge(e).pipe(m(({y:r})=>{let o=de(e),n=At(e);return r>=n.height-o.height-t}),Y())}var yr={drawer:j("[data-md-toggle=drawer]"),search:j("[data-md-toggle=search]")};function dn(e){return yr[e].checked}function at(e,t){yr[e].checked!==t&&yr[e].click()}function Je(e){let t=yr[e];return h(t,"change").pipe(m(()=>t.checked),Q(t.checked))}function ja(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function Ua(){return L(h(window,"compositionstart").pipe(m(()=>!0)),h(window,"compositionend").pipe(m(()=>!1))).pipe(Q(!1))}function hn(){let e=h(window,"keydown").pipe(g(t=>!(t.metaKey||t.ctrlKey)),m(t=>({mode:dn("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),g(({mode:t,type:r})=>{if(t==="global"){let o=Ne();if(typeof o!="undefined")return!ja(o,r)}return!0}),le());return Ua().pipe(b(t=>t?y:e))}function we(){return new URL(location.href)}function st(e,t=!1){if(V("navigation.instant")&&!t){let r=x("a",{href:e.href});document.body.appendChild(r),r.click(),r.remove()}else location.href=e.href}function bn(){return new T}function vn(){return location.hash.slice(1)}function gn(e){let t=x("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Zr(e){return L(h(window,"hashchange"),e).pipe(m(vn),Q(vn()),g(t=>t.length>0),Z(1))}function yn(e){return Zr(e).pipe(m(t=>ue(`[id="${t}"]`)),g(t=>typeof t!="undefined"))}function Wt(e){let t=matchMedia(e);return ur(r=>t.addListener(()=>r(t.matches))).pipe(Q(t.matches))}function xn(){let e=matchMedia("print");return L(h(window,"beforeprint").pipe(m(()=>!0)),h(window,"afterprint").pipe(m(()=>!1))).pipe(Q(e.matches))}function eo(e,t){return e.pipe(b(r=>r?t():y))}function to(e,t){return new F(r=>{let o=new XMLHttpRequest;return o.open("GET",`${e}`),o.responseType="blob",o.addEventListener("load",()=>{o.status>=200&&o.status<300?(r.next(o.response),r.complete()):r.error(new Error(o.statusText))}),o.addEventListener("error",()=>{r.error(new Error("Network error"))}),o.addEventListener("abort",()=>{r.complete()}),typeof(t==null?void 0:t.progress$)!="undefined"&&(o.addEventListener("progress",n=>{var i;if(n.lengthComputable)t.progress$.next(n.loaded/n.total*100);else{let s=(i=o.getResponseHeader("Content-Length"))!=null?i:0;t.progress$.next(n.loaded/+s*100)}}),t.progress$.next(5)),o.send(),()=>o.abort()})}function ze(e,t){return to(e,t).pipe(b(r=>r.text()),m(r=>JSON.parse(r)),Z(1))}function xr(e,t){let r=new DOMParser;return to(e,t).pipe(b(o=>o.text()),m(o=>r.parseFromString(o,"text/html")),Z(1))}function En(e,t){let r=new DOMParser;return to(e,t).pipe(b(o=>o.text()),m(o=>r.parseFromString(o,"text/xml")),Z(1))}function wn(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function Tn(){return L(h(window,"scroll",{passive:!0}),h(window,"resize",{passive:!0})).pipe(m(wn),Q(wn()))}function Sn(){return{width:innerWidth,height:innerHeight}}function On(){return h(window,"resize",{passive:!0}).pipe(m(Sn),Q(Sn()))}function Ln(){return z([Tn(),On()]).pipe(m(([e,t])=>({offset:e,size:t})),Z(1))}function Er(e,{viewport$:t,header$:r}){let o=t.pipe(ne("size")),n=z([o,r]).pipe(m(()=>Be(e)));return z([r,t,n]).pipe(m(([{height:i},{offset:s,size:a},{x:c,y:p}])=>({offset:{x:s.x-c,y:s.y-p+i},size:a})))}function Wa(e){return h(e,"message",t=>t.data)}function Da(e){let t=new T;return t.subscribe(r=>e.postMessage(r)),t}function Mn(e,t=new Worker(e)){let r=Wa(t),o=Da(t),n=new T;n.subscribe(o);let i=o.pipe(oe(),ae(!0));return n.pipe(oe(),Ve(r.pipe(W(i))),le())}var Va=j("#__config"),Ct=JSON.parse(Va.textContent);Ct.base=`${new URL(Ct.base,we())}`;function Te(){return Ct}function V(e){return Ct.features.includes(e)}function Me(e,t){return typeof t!="undefined"?Ct.translations[e].replace("#",t.toString()):Ct.translations[e]}function Ce(e,t=document){return j(`[data-md-component=${e}]`,t)}function me(e,t=document){return M(`[data-md-component=${e}]`,t)}function Na(e){let t=j(".md-typeset > :first-child",e);return h(t,"click",{once:!0}).pipe(m(()=>j(".md-typeset",e)),m(r=>({hash:__md_hash(r.innerHTML)})))}function _n(e){if(!V("announce.dismiss")||!e.childElementCount)return y;if(!e.hidden){let t=j(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return H(()=>{let t=new T;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),Na(e).pipe(O(r=>t.next(r)),A(()=>t.complete()),m(r=>P({ref:e},r)))})}function za(e,{target$:t}){return t.pipe(m(r=>({hidden:r!==e})))}function An(e,t){let r=new T;return r.subscribe(({hidden:o})=>{e.hidden=o}),za(e,t).pipe(O(o=>r.next(o)),A(()=>r.complete()),m(o=>P({ref:e},o)))}function Dt(e,t){return t==="inline"?x("div",{class:"md-tooltip md-tooltip--inline",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"})):x("div",{class:"md-tooltip",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"}))}function wr(...e){return x("div",{class:"md-tooltip2",role:"dialog"},x("div",{class:"md-tooltip2__inner md-typeset"},e))}function Cn(...e){return x("div",{class:"md-tooltip2",role:"tooltip"},x("div",{class:"md-tooltip2__inner md-typeset"},e))}function kn(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return x("aside",{class:"md-annotation",tabIndex:0},Dt(t),x("a",{href:r,class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}else return x("aside",{class:"md-annotation",tabIndex:0},Dt(t),x("span",{class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}function Hn(e){return x("button",{class:"md-code__button",title:Me("clipboard.copy"),"data-clipboard-target":`#${e} > code`,"data-md-type":"copy"})}function $n(){return x("button",{class:"md-code__button",title:"Toggle line selection","data-md-type":"select"})}function Pn(){return x("nav",{class:"md-code__nav"})}var In=$t(ro());function oo(e,t){let r=t&2,o=t&1,n=Object.keys(e.terms).filter(c=>!e.terms[c]).reduce((c,p)=>[...c,x("del",null,(0,In.default)(p))," "],[]).slice(0,-1),i=Te(),s=new URL(e.location,i.base);V("search.highlight")&&s.searchParams.set("h",Object.entries(e.terms).filter(([,c])=>c).reduce((c,[p])=>`${c} ${p}`.trim(),""));let{tags:a}=Te();return x("a",{href:`${s}`,class:"md-search-result__link",tabIndex:-1},x("article",{class:"md-search-result__article md-typeset","data-md-score":e.score.toFixed(2)},r>0&&x("div",{class:"md-search-result__icon md-icon"}),r>0&&x("h1",null,e.title),r<=0&&x("h2",null,e.title),o>0&&e.text.length>0&&e.text,e.tags&&x("nav",{class:"md-tags"},e.tags.map(c=>{let p=a?c in a?`md-tag-icon md-tag--${a[c]}`:"md-tag-icon":"";return x("span",{class:`md-tag ${p}`},c)})),o>0&&n.length>0&&x("p",{class:"md-search-result__terms"},Me("search.result.term.missing"),": ",...n)))}function Fn(e){let t=e[0].score,r=[...e],o=Te(),n=r.findIndex(l=>!`${new URL(l.location,o.base)}`.includes("#")),[i]=r.splice(n,1),s=r.findIndex(l=>l.scoreoo(l,1)),...c.length?[x("details",{class:"md-search-result__more"},x("summary",{tabIndex:-1},x("div",null,c.length>0&&c.length===1?Me("search.result.more.one"):Me("search.result.more.other",c.length))),...c.map(l=>oo(l,1)))]:[]];return x("li",{class:"md-search-result__item"},p)}function jn(e){return x("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>x("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?br(r):r)))}function no(e){let t=`tabbed-control tabbed-control--${e}`;return x("div",{class:t,hidden:!0},x("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function Un(e){return x("div",{class:"md-typeset__scrollwrap"},x("div",{class:"md-typeset__table"},e))}function Qa(e){var o;let t=Te(),r=new URL(`../${e.version}/`,t.base);return x("li",{class:"md-version__item"},x("a",{href:`${r}`,class:"md-version__link"},e.title,((o=t.version)==null?void 0:o.alias)&&e.aliases.length>0&&x("span",{class:"md-version__alias"},e.aliases[0])))}function Wn(e,t){var o;let r=Te();return e=e.filter(n=>{var i;return!((i=n.properties)!=null&&i.hidden)}),x("div",{class:"md-version"},x("button",{class:"md-version__current","aria-label":Me("select.version")},t.title,((o=r.version)==null?void 0:o.alias)&&t.aliases.length>0&&x("span",{class:"md-version__alias"},t.aliases[0])),x("ul",{class:"md-version__list"},e.map(Qa)))}var Ya=0;function Ba(e,t=250){let r=z([Ye(e),it(e,t)]).pipe(m(([n,i])=>n||i),Y()),o=H(()=>pn(e)).pipe(J(Ge),gt(1),Pe(r),m(()=>ln(e)));return r.pipe(Re(n=>n),b(()=>z([r,o])),m(([n,i])=>({active:n,offset:i})),le())}function Vt(e,t,r=250){let{content$:o,viewport$:n}=t,i=`__tooltip2_${Ya++}`;return H(()=>{let s=new T,a=new jr(!1);s.pipe(oe(),ae(!1)).subscribe(a);let c=a.pipe(jt(l=>He(+!l*250,Dr)),Y(),b(l=>l?o:y),O(l=>l.id=i),le());z([s.pipe(m(({active:l})=>l)),c.pipe(b(l=>it(l,250)),Q(!1))]).pipe(m(l=>l.some(f=>f))).subscribe(a);let p=a.pipe(g(l=>l),te(c,n),m(([l,f,{size:u}])=>{let d=e.getBoundingClientRect(),v=d.width/2;if(f.role==="tooltip")return{x:v,y:8+d.height};if(d.y>=u.height/2){let{height:S}=de(f);return{x:v,y:-16-S}}else return{x:v,y:16+d.height}}));return z([c,s,p]).subscribe(([l,{offset:f},u])=>{l.style.setProperty("--md-tooltip-host-x",`${f.x}px`),l.style.setProperty("--md-tooltip-host-y",`${f.y}px`),l.style.setProperty("--md-tooltip-x",`${u.x}px`),l.style.setProperty("--md-tooltip-y",`${u.y}px`),l.classList.toggle("md-tooltip2--top",u.y<0),l.classList.toggle("md-tooltip2--bottom",u.y>=0)}),a.pipe(g(l=>l),te(c,(l,f)=>f),g(l=>l.role==="tooltip")).subscribe(l=>{let f=de(j(":scope > *",l));l.style.setProperty("--md-tooltip-width",`${f.width}px`),l.style.setProperty("--md-tooltip-tail","0px")}),a.pipe(Y(),xe(ye),te(c)).subscribe(([l,f])=>{f.classList.toggle("md-tooltip2--active",l)}),z([a.pipe(g(l=>l)),c]).subscribe(([l,f])=>{f.role==="dialog"?(e.setAttribute("aria-controls",i),e.setAttribute("aria-haspopup","dialog")):e.setAttribute("aria-describedby",i)}),a.pipe(g(l=>!l)).subscribe(()=>{e.removeAttribute("aria-controls"),e.removeAttribute("aria-describedby"),e.removeAttribute("aria-haspopup")}),Ba(e,r).pipe(O(l=>s.next(l)),A(()=>s.complete()),m(l=>P({ref:e},l)))})}function Xe(e,{viewport$:t},r=document.body){return Vt(e,{content$:new F(o=>{let n=e.title,i=Cn(n);return o.next(i),e.removeAttribute("title"),r.append(i),()=>{i.remove(),e.setAttribute("title",n)}}),viewport$:t},0)}function Ga(e,t){let r=H(()=>z([mn(e),Ge(t)])).pipe(m(([{x:o,y:n},i])=>{let{width:s,height:a}=de(e);return{x:o-i.x+s/2,y:n-i.y+a/2}}));return Ye(e).pipe(b(o=>r.pipe(m(n=>({active:o,offset:n})),Ee(+!o||1/0))))}function Dn(e,t,{target$:r}){let[o,n]=Array.from(e.children);return H(()=>{let i=new T,s=i.pipe(oe(),ae(!0));return i.subscribe({next({offset:a}){e.style.setProperty("--md-tooltip-x",`${a.x}px`),e.style.setProperty("--md-tooltip-y",`${a.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),mt(e).pipe(W(s)).subscribe(a=>{e.toggleAttribute("data-md-visible",a)}),L(i.pipe(g(({active:a})=>a)),i.pipe(Ae(250),g(({active:a})=>!a))).subscribe({next({active:a}){a?e.prepend(o):o.remove()},complete(){e.prepend(o)}}),i.pipe($e(16,ye)).subscribe(({active:a})=>{o.classList.toggle("md-tooltip--active",a)}),i.pipe(gt(125,ye),g(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:a})=>a)).subscribe({next(a){a?e.style.setProperty("--md-tooltip-0",`${-a}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),h(n,"click").pipe(W(s),g(a=>!(a.metaKey||a.ctrlKey))).subscribe(a=>{a.stopPropagation(),a.preventDefault()}),h(n,"mousedown").pipe(W(s),te(i)).subscribe(([a,{active:c}])=>{var p;if(a.button!==0||a.metaKey||a.ctrlKey)a.preventDefault();else if(c){a.preventDefault();let l=e.parentElement.closest(".md-annotation");l instanceof HTMLElement?l.focus():(p=Ne())==null||p.blur()}}),r.pipe(W(s),g(a=>a===o),nt(125)).subscribe(()=>e.focus()),Ga(e,t).pipe(O(a=>i.next(a)),A(()=>i.complete()),m(a=>P({ref:e},a)))})}function Ja(e){let t=Te();if(e.tagName!=="CODE")return[e];let r=[".c",".c1",".cm"];if(t.annotate&&typeof t.annotate=="object"){let o=e.closest("[class|=language]");if(o)for(let n of Array.from(o.classList)){if(!n.startsWith("language-"))continue;let[,i]=n.split("-");i in t.annotate&&r.push(...t.annotate[i])}}return M(r.join(", "),e)}function Xa(e){let t=[];for(let r of Ja(e)){let o=[],n=document.createNodeIterator(r,NodeFilter.SHOW_TEXT);for(let i=n.nextNode();i;i=n.nextNode())o.push(i);for(let i of o){let s;for(;s=/(\(\d+\))(!)?/.exec(i.textContent);){let[,a,c]=s;if(typeof c=="undefined"){let p=i.splitText(s.index);i=p.splitText(a.length),t.push(p)}else{i.textContent=a,t.push(i);break}}}}return t}function Vn(e,t){t.append(...Array.from(e.childNodes))}function Tr(e,t,{target$:r,print$:o}){let n=t.closest("[id]"),i=n==null?void 0:n.id,s=new Map;for(let a of Xa(t)){let[,c]=a.textContent.match(/\((\d+)\)/);ue(`:scope > li:nth-child(${c})`,e)&&(s.set(c,kn(c,i)),a.replaceWith(s.get(c)))}return s.size===0?y:H(()=>{let a=new T,c=a.pipe(oe(),ae(!0)),p=[];for(let[l,f]of s)p.push([j(".md-typeset",f),j(`:scope > li:nth-child(${l})`,e)]);return o.pipe(W(c)).subscribe(l=>{e.hidden=!l,e.classList.toggle("md-annotation-list",l);for(let[f,u]of p)l?Vn(f,u):Vn(u,f)}),L(...[...s].map(([,l])=>Dn(l,t,{target$:r}))).pipe(A(()=>a.complete()),le())})}function Nn(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return Nn(t)}}function zn(e,t){return H(()=>{let r=Nn(e);return typeof r!="undefined"?Tr(r,e,t):y})}var Kn=$t(ao());var Za=0,qn=L(h(window,"keydown").pipe(m(()=>!0)),L(h(window,"keyup"),h(window,"contextmenu")).pipe(m(()=>!1))).pipe(Q(!1),Z(1));function Qn(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return Qn(t)}}function es(e){return Le(e).pipe(m(({width:t})=>({scrollable:At(e).width>t})),ne("scrollable"))}function Yn(e,t){let{matches:r}=matchMedia("(hover)"),o=H(()=>{let n=new T,i=n.pipe(Yr(1));n.subscribe(({scrollable:d})=>{d&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")});let s=[],a=e.closest("pre"),c=a.closest("[id]"),p=c?c.id:Za++;a.id=`__code_${p}`;let l=[],f=e.closest(".highlight");if(f instanceof HTMLElement){let d=Qn(f);if(typeof d!="undefined"&&(f.classList.contains("annotate")||V("content.code.annotate"))){let v=Tr(d,e,t);l.push(Le(f).pipe(W(i),m(({width:S,height:X})=>S&&X),Y(),b(S=>S?v:y)))}}let u=M(":scope > span[id]",e);if(u.length&&(e.classList.add("md-code__content"),e.closest(".select")||V("content.code.select")&&!e.closest(".no-select"))){let d=+u[0].id.split("-").pop(),v=$n();s.push(v),V("content.tooltips")&&l.push(Xe(v,{viewport$}));let S=h(v,"click").pipe(Ut(R=>!R,!1),O(()=>v.blur()),le());S.subscribe(R=>{v.classList.toggle("md-code__button--active",R)});let X=fe(u).pipe(J(R=>it(R).pipe(m(se=>[R,se]))));S.pipe(b(R=>R?X:y)).subscribe(([R,se])=>{let ce=ue(".hll.select",R);if(ce&&!se)ce.replaceWith(...Array.from(ce.childNodes));else if(!ce&&se){let he=document.createElement("span");he.className="hll select",he.append(...Array.from(R.childNodes).slice(1)),R.append(he)}});let re=fe(u).pipe(J(R=>h(R,"mousedown").pipe(O(se=>se.preventDefault()),m(()=>R)))),ee=S.pipe(b(R=>R?re:y),te(qn),m(([R,se])=>{var he;let ce=u.indexOf(R)+d;if(se===!1)return[ce,ce];{let Se=M(".hll",e).map(Ue=>u.indexOf(Ue.parentElement)+d);return(he=window.getSelection())==null||he.removeAllRanges(),[Math.min(ce,...Se),Math.max(ce,...Se)]}})),k=Zr(y).pipe(g(R=>R.startsWith(`__codelineno-${p}-`)));k.subscribe(R=>{let[,,se]=R.split("-"),ce=se.split(":").map(Se=>+Se-d+1);ce.length===1&&ce.push(ce[0]);for(let Se of M(".hll:not(.select)",e))Se.replaceWith(...Array.from(Se.childNodes));let he=u.slice(ce[0]-1,ce[1]);for(let Se of he){let Ue=document.createElement("span");Ue.className="hll",Ue.append(...Array.from(Se.childNodes).slice(1)),Se.append(Ue)}}),k.pipe(Ee(1),xe(pe)).subscribe(R=>{if(R.includes(":")){let se=document.getElementById(R.split(":")[0]);se&&setTimeout(()=>{let ce=se,he=-64;for(;ce!==document.body;)he+=ce.offsetTop,ce=ce.offsetParent;window.scrollTo({top:he})},1)}});let je=fe(M('a[href^="#__codelineno"]',f)).pipe(J(R=>h(R,"click").pipe(O(se=>se.preventDefault()),m(()=>R)))).pipe(W(i),te(qn),m(([R,se])=>{let he=+j(`[id="${R.hash.slice(1)}"]`).parentElement.id.split("-").pop();if(se===!1)return[he,he];{let Se=M(".hll",e).map(Ue=>+Ue.parentElement.id.split("-").pop());return[Math.min(he,...Se),Math.max(he,...Se)]}}));L(ee,je).subscribe(R=>{let se=`#__codelineno-${p}-`;R[0]===R[1]?se+=R[0]:se+=`${R[0]}:${R[1]}`,history.replaceState({},"",se),window.dispatchEvent(new HashChangeEvent("hashchange",{newURL:window.location.origin+window.location.pathname+se,oldURL:window.location.href}))})}if(Kn.default.isSupported()&&(e.closest(".copy")||V("content.code.copy")&&!e.closest(".no-copy"))){let d=Hn(a.id);s.push(d),V("content.tooltips")&&l.push(Xe(d,{viewport$}))}if(s.length){let d=Pn();d.append(...s),a.insertBefore(d,e)}return es(e).pipe(O(d=>n.next(d)),A(()=>n.complete()),m(d=>P({ref:e},d)),Ve(L(...l).pipe(W(i))))});return V("content.lazy")?mt(e).pipe(g(n=>n),Ee(1),b(()=>o)):o}function ts(e,{target$:t,print$:r}){let o=!0;return L(t.pipe(m(n=>n.closest("details:not([open])")),g(n=>e===n),m(()=>({action:"open",reveal:!0}))),r.pipe(g(n=>n||!o),O(()=>o=e.open),m(n=>({action:n?"open":"close"}))))}function Bn(e,t){return H(()=>{let r=new T;return r.subscribe(({action:o,reveal:n})=>{e.toggleAttribute("open",o==="open"),n&&e.scrollIntoView()}),ts(e,t).pipe(O(o=>r.next(o)),A(()=>r.complete()),m(o=>P({ref:e},o)))})}var Gn=0;function rs(e){let t=document.createElement("h3");t.innerHTML=e.innerHTML;let r=[t],o=e.nextElementSibling;for(;o&&!(o instanceof HTMLHeadingElement);)r.push(o),o=o.nextElementSibling;return r}function os(e,t){for(let r of M("[href], [src]",e))for(let o of["href","src"]){let n=r.getAttribute(o);if(n&&!/^(?:[a-z]+:)?\/\//i.test(n)){r[o]=new URL(r.getAttribute(o),t).toString();break}}for(let r of M("[name^=__], [for]",e))for(let o of["id","for","name"]){let n=r.getAttribute(o);n&&r.setAttribute(o,`${n}$preview_${Gn}`)}return Gn++,$(e)}function Jn(e,t){let{sitemap$:r}=t;if(!(e instanceof HTMLAnchorElement))return y;if(!(V("navigation.instant.preview")||e.hasAttribute("data-preview")))return y;e.removeAttribute("title");let o=z([Ye(e),it(e)]).pipe(m(([i,s])=>i||s),Y(),g(i=>i));return rt([r,o]).pipe(b(([i])=>{let s=new URL(e.href);return s.search=s.hash="",i.has(`${s}`)?$(s):y}),b(i=>xr(i).pipe(b(s=>os(s,i)))),b(i=>{let s=e.hash?`article [id="${e.hash.slice(1)}"]`:"article h1",a=ue(s,i);return typeof a=="undefined"?y:$(rs(a))})).pipe(b(i=>{let s=new F(a=>{let c=wr(...i);return a.next(c),document.body.append(c),()=>c.remove()});return Vt(e,P({content$:s},t))}))}var Xn=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.flowchartTitleText{fill:var(--md-mermaid-label-fg-color)}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel p,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel p{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color)}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}g #flowchart-circleEnd,g #flowchart-circleStart,g #flowchart-crossEnd,g #flowchart-crossStart,g #flowchart-pointEnd,g #flowchart-pointStart{stroke:none}.classDiagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs marker.marker.composition.class path,defs marker.marker.dependency.class path,defs marker.marker.extension.class path{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs marker.marker.aggregation.class path{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}.statediagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel,.nodeLabel p{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}a .nodeLabel{text-decoration:underline}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}[id^=entity] path,[id^=entity] rect{fill:var(--md-default-bg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs .marker.oneOrMore.er *,defs .marker.onlyOne.er *,defs .marker.zeroOrMore.er *,defs .marker.zeroOrOne.er *{stroke:var(--md-mermaid-edge-color)!important}text:not([class]):last-child{fill:var(--md-mermaid-label-fg-color)}.actor{fill:var(--md-mermaid-sequence-actor-bg-color);stroke:var(--md-mermaid-sequence-actor-border-color)}text.actor>tspan{fill:var(--md-mermaid-sequence-actor-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-mermaid-sequence-actor-line-color)}.actor-man circle,.actor-man line{fill:var(--md-mermaid-sequence-actorman-bg-color);stroke:var(--md-mermaid-sequence-actorman-line-color)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-sequence-message-line-color)}.note{fill:var(--md-mermaid-sequence-note-bg-color);stroke:var(--md-mermaid-sequence-note-border-color)}.loopText,.loopText>tspan,.messageText,.noteText>tspan{stroke:none;font-family:var(--md-mermaid-font-family)!important}.messageText{fill:var(--md-mermaid-sequence-message-fg-color)}.loopText,.loopText>tspan{fill:var(--md-mermaid-sequence-loop-fg-color)}.noteText>tspan{fill:var(--md-mermaid-sequence-note-fg-color)}#arrowhead path{fill:var(--md-mermaid-sequence-message-line-color);stroke:none}.loopLine{fill:var(--md-mermaid-sequence-loop-bg-color);stroke:var(--md-mermaid-sequence-loop-border-color)}.labelBox{fill:var(--md-mermaid-sequence-label-bg-color);stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-sequence-label-fg-color);font-family:var(--md-mermaid-font-family)}.sequenceNumber{fill:var(--md-mermaid-sequence-number-fg-color)}rect.rect{fill:var(--md-mermaid-sequence-box-bg-color);stroke:none}rect.rect+text.text{fill:var(--md-mermaid-sequence-box-fg-color)}defs #sequencenumber{fill:var(--md-mermaid-sequence-number-bg-color)!important}";var so,is=0;function as(){return typeof mermaid=="undefined"||mermaid instanceof Element?_t("https://unpkg.com/mermaid@11/dist/mermaid.min.js"):$(void 0)}function Zn(e){return e.classList.remove("mermaid"),so||(so=as().pipe(O(()=>mermaid.initialize({startOnLoad:!1,themeCSS:Xn,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),m(()=>{}),Z(1))),so.subscribe(()=>go(null,null,function*(){e.classList.add("mermaid");let t=`__mermaid_${is++}`,r=x("div",{class:"mermaid"}),o=e.textContent,{svg:n,fn:i}=yield mermaid.render(t,o),s=r.attachShadow({mode:"closed"});s.innerHTML=n,e.replaceWith(r),i==null||i(s)})),so.pipe(m(()=>({ref:e})))}var ei=x("table");function ti(e){return e.replaceWith(ei),ei.replaceWith(Un(e)),$({ref:e})}function ss(e){let t=e.find(r=>r.checked)||e[0];return L(...e.map(r=>h(r,"change").pipe(m(()=>j(`label[for="${r.id}"]`))))).pipe(Q(j(`label[for="${t.id}"]`)),m(r=>({active:r})))}function ri(e,{viewport$:t,target$:r}){let o=j(".tabbed-labels",e),n=M(":scope > input",e),i=no("prev");e.append(i);let s=no("next");return e.append(s),H(()=>{let a=new T,c=a.pipe(oe(),ae(!0));z([a,Le(e),mt(e)]).pipe(W(c),$e(1,ye)).subscribe({next([{active:p},l]){let f=Be(p),{width:u}=de(p);e.style.setProperty("--md-indicator-x",`${f.x}px`),e.style.setProperty("--md-indicator-width",`${u}px`);let d=gr(o);(f.xd.x+l.width)&&o.scrollTo({left:Math.max(0,f.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),z([Ge(o),Le(o)]).pipe(W(c)).subscribe(([p,l])=>{let f=At(o);i.hidden=p.x<16,s.hidden=p.x>f.width-l.width-16}),L(h(i,"click").pipe(m(()=>-1)),h(s,"click").pipe(m(()=>1))).pipe(W(c)).subscribe(p=>{let{width:l}=de(o);o.scrollBy({left:l*p,behavior:"smooth"})}),r.pipe(W(c),g(p=>n.includes(p))).subscribe(p=>p.click()),o.classList.add("tabbed-labels--linked");for(let p of n){let l=j(`label[for="${p.id}"]`);l.replaceChildren(x("a",{href:`#${l.htmlFor}`,tabIndex:-1},...Array.from(l.childNodes))),h(l.firstElementChild,"click").pipe(W(c),g(f=>!(f.metaKey||f.ctrlKey)),O(f=>{f.preventDefault(),f.stopPropagation()})).subscribe(()=>{history.replaceState({},"",`#${l.htmlFor}`),l.click()})}return V("content.tabs.link")&&a.pipe(Ie(1),te(t)).subscribe(([{active:p},{offset:l}])=>{let f=p.innerText.trim();if(p.hasAttribute("data-md-switching"))p.removeAttribute("data-md-switching");else{let u=e.offsetTop-l.y;for(let v of M("[data-tabs]"))for(let S of M(":scope > input",v)){let X=j(`label[for="${S.id}"]`);if(X!==p&&X.innerText.trim()===f){X.setAttribute("data-md-switching",""),S.click();break}}window.scrollTo({top:e.offsetTop-u});let d=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([f,...d])])}}),a.pipe(W(c)).subscribe(()=>{for(let p of M("audio, video",e))p.offsetWidth&&p.autoplay?p.play().catch(()=>{}):p.pause()}),ss(n).pipe(O(p=>a.next(p)),A(()=>a.complete()),m(p=>P({ref:e},p)))}).pipe(et(pe))}function oi(e,t){let{viewport$:r,target$:o,print$:n}=t;return L(...M(".annotate:not(.highlight)",e).map(i=>zn(i,{target$:o,print$:n})),...M("pre:not(.mermaid) > code",e).map(i=>Yn(i,{target$:o,print$:n})),...M("a",e).map(i=>Jn(i,t)),...M("pre.mermaid",e).map(i=>Zn(i)),...M("table:not([class])",e).map(i=>ti(i)),...M("details",e).map(i=>Bn(i,{target$:o,print$:n})),...M("[data-tabs]",e).map(i=>ri(i,{viewport$:r,target$:o})),...M("[title]:not([data-preview])",e).filter(()=>V("content.tooltips")).map(i=>Xe(i,{viewport$:r})),...M(".footnote-ref",e).filter(()=>V("content.footnote.tooltips")).map(i=>Vt(i,{content$:new F(s=>{let a=new URL(i.href).hash.slice(1),c=Array.from(document.getElementById(a).cloneNode(!0).children),p=wr(...c);return s.next(p),document.body.append(p),()=>p.remove()}),viewport$:r})))}function cs(e,{alert$:t}){return t.pipe(b(r=>L($(!0),$(!1).pipe(nt(2e3))).pipe(m(o=>({message:r,active:o})))))}function ni(e,t){let r=j(".md-typeset",e);return H(()=>{let o=new T;return o.subscribe(({message:n,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=n}),cs(e,t).pipe(O(n=>o.next(n)),A(()=>o.complete()),m(n=>P({ref:e},n)))})}var ps=0;function ls(e,t){document.body.append(e);let{width:r}=de(e);e.style.setProperty("--md-tooltip-width",`${r}px`),e.remove();let o=vr(t),n=typeof o!="undefined"?Ge(o):$({x:0,y:0}),i=L(Ye(t),it(t)).pipe(Y());return z([i,n]).pipe(m(([s,a])=>{let{x:c,y:p}=Be(t),l=de(t),f=t.closest("table");return f&&t.parentElement&&(c+=f.offsetLeft+t.parentElement.offsetLeft,p+=f.offsetTop+t.parentElement.offsetTop),{active:s,offset:{x:c-a.x+l.width/2-r/2,y:p-a.y+l.height+8}}}))}function ii(e){let t=e.title;if(!t.length)return y;let r=`__tooltip_${ps++}`,o=Dt(r,"inline"),n=j(".md-typeset",o);return n.innerHTML=t,H(()=>{let i=new T;return i.subscribe({next({offset:s}){o.style.setProperty("--md-tooltip-x",`${s.x}px`),o.style.setProperty("--md-tooltip-y",`${s.y}px`)},complete(){o.style.removeProperty("--md-tooltip-x"),o.style.removeProperty("--md-tooltip-y")}}),L(i.pipe(g(({active:s})=>s)),i.pipe(Ae(250),g(({active:s})=>!s))).subscribe({next({active:s}){s?(e.insertAdjacentElement("afterend",o),e.setAttribute("aria-describedby",r),e.removeAttribute("title")):(o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t))},complete(){o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t)}}),i.pipe($e(16,ye)).subscribe(({active:s})=>{o.classList.toggle("md-tooltip--active",s)}),i.pipe(gt(125,ye),g(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:s})=>s)).subscribe({next(s){s?o.style.setProperty("--md-tooltip-0",`${-s}px`):o.style.removeProperty("--md-tooltip-0")},complete(){o.style.removeProperty("--md-tooltip-0")}}),ls(o,e).pipe(O(s=>i.next(s)),A(()=>i.complete()),m(s=>P({ref:e},s)))}).pipe(et(pe))}function ms({viewport$:e}){if(!V("header.autohide"))return $(!1);let t=e.pipe(m(({offset:{y:n}})=>n),ot(2,1),m(([n,i])=>[nMath.abs(i-n.y)>100),m(([,[n]])=>n),Y()),o=Je("search");return z([e,o]).pipe(m(([{offset:n},i])=>n.y>400&&!i),Y(),b(n=>n?r:$(!1)),Q(!1))}function ai(e,t){return H(()=>z([Le(e),ms(t)])).pipe(m(([{height:r},o])=>({height:r,hidden:o})),Y((r,o)=>r.height===o.height&&r.hidden===o.hidden),Z(1))}function si(e,{header$:t,main$:r}){return H(()=>{let o=new T,n=o.pipe(oe(),ae(!0));o.pipe(ne("active"),Pe(t)).subscribe(([{active:s},{hidden:a}])=>{e.classList.toggle("md-header--shadow",s&&!a),e.hidden=a});let i=fe(M("[title]",e)).pipe(g(()=>V("content.tooltips")),J(s=>ii(s)));return r.subscribe(o),t.pipe(W(n),m(s=>P({ref:e},s)),Ve(i.pipe(W(n))))})}function fs(e,{viewport$:t,header$:r}){return Er(e,{viewport$:t,header$:r}).pipe(m(({offset:{y:o}})=>{let{height:n}=de(e);return{active:n>0&&o>=n}}),ne("active"))}function ci(e,t){return H(()=>{let r=new T;r.subscribe({next({active:n}){e.classList.toggle("md-header__title--active",n)},complete(){e.classList.remove("md-header__title--active")}});let o=ue(".md-content h1");return typeof o=="undefined"?y:fs(o,t).pipe(O(n=>r.next(n)),A(()=>r.complete()),m(n=>P({ref:e},n)))})}function pi(e,{viewport$:t,header$:r}){let o=r.pipe(m(({height:i})=>i),Y()),n=o.pipe(b(()=>Le(e).pipe(m(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),ne("bottom"))));return z([o,n,t]).pipe(m(([i,{top:s,bottom:a},{offset:{y:c},size:{height:p}}])=>(p=Math.max(0,p-Math.max(0,s-c,i)-Math.max(0,p+c-a)),{offset:s-i,height:p,active:s-i<=c})),Y((i,s)=>i.offset===s.offset&&i.height===s.height&&i.active===s.active))}function us(e){let t=__md_get("__palette")||{index:e.findIndex(o=>matchMedia(o.getAttribute("data-md-color-media")).matches)},r=Math.max(0,Math.min(t.index,e.length-1));return $(...e).pipe(J(o=>h(o,"change").pipe(m(()=>o))),Q(e[r]),m(o=>({index:e.indexOf(o),color:{media:o.getAttribute("data-md-color-media"),scheme:o.getAttribute("data-md-color-scheme"),primary:o.getAttribute("data-md-color-primary"),accent:o.getAttribute("data-md-color-accent")}})),Z(1))}function li(e){let t=M("input",e),r=x("meta",{name:"theme-color"});document.head.appendChild(r);let o=x("meta",{name:"color-scheme"});document.head.appendChild(o);let n=Wt("(prefers-color-scheme: light)");return H(()=>{let i=new T;return i.subscribe(s=>{if(document.body.setAttribute("data-md-color-switching",""),s.color.media==="(prefers-color-scheme)"){let a=matchMedia("(prefers-color-scheme: light)"),c=document.querySelector(a.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");s.color.scheme=c.getAttribute("data-md-color-scheme"),s.color.primary=c.getAttribute("data-md-color-primary"),s.color.accent=c.getAttribute("data-md-color-accent")}for(let[a,c]of Object.entries(s.color))document.body.setAttribute(`data-md-color-${a}`,c);for(let a=0;as.key==="Enter"),te(i,(s,a)=>a)).subscribe(({index:s})=>{s=(s+1)%t.length,t[s].click(),t[s].focus()}),i.pipe(m(()=>{let s=Ce("header"),a=window.getComputedStyle(s);return o.content=a.colorScheme,a.backgroundColor.match(/\d+/g).map(c=>(+c).toString(16).padStart(2,"0")).join("")})).subscribe(s=>r.content=`#${s}`),i.pipe(xe(pe)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")}),us(t).pipe(W(n.pipe(Ie(1))),vt(),O(s=>i.next(s)),A(()=>i.complete()),m(s=>P({ref:e},s)))})}function mi(e,{progress$:t}){return H(()=>{let r=new T;return r.subscribe(({value:o})=>{e.style.setProperty("--md-progress-value",`${o}`)}),t.pipe(O(o=>r.next({value:o})),A(()=>r.complete()),m(o=>({ref:e,value:o})))})}function fi(e,t){return e.protocol=t.protocol,e.hostname=t.hostname,e}function ds(e,t){let r=new Map;for(let o of M("url",e)){let n=j("loc",o),i=[fi(new URL(n.textContent),t)];r.set(`${i[0]}`,i);for(let s of M("[rel=alternate]",o)){let a=s.getAttribute("href");a!=null&&i.push(fi(new URL(a),t))}}return r}function kt(e){return En(new URL("sitemap.xml",e)).pipe(m(t=>ds(t,new URL(e))),ve(()=>$(new Map)),le())}function ui({document$:e}){let t=new Map;e.pipe(b(()=>M("link[rel=alternate]")),m(r=>new URL(r.href)),g(r=>!t.has(r.toString())),J(r=>kt(r).pipe(m(o=>[r,o]),ve(()=>y)))).subscribe(([r,o])=>{t.set(r.toString().replace(/\/$/,""),o)}),h(document.body,"click").pipe(g(r=>!r.metaKey&&!r.ctrlKey),b(r=>{if(r.target instanceof Element){let o=r.target.closest("a");if(o&&!o.target){let n=[...t].find(([f])=>o.href.startsWith(`${f}/`));if(typeof n=="undefined")return y;let[i,s]=n,a=we();if(a.href.startsWith(i))return y;let c=Te(),p=a.href.replace(c.base,"");p=`${i}/${p}`;let l=s.has(p.split("#")[0])?new URL(p,c.base):new URL(i);return r.preventDefault(),$(l)}}return y})).subscribe(r=>st(r,!0))}var co=$t(ao());function hs(e){e.setAttribute("data-md-copying","");let t=e.closest("[data-copy]"),r=t?t.getAttribute("data-copy"):e.innerText;return e.removeAttribute("data-md-copying"),r.trimEnd()}function di({alert$:e}){co.default.isSupported()&&new F(t=>{new co.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||hs(j(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe(O(t=>{t.trigger.focus()}),m(()=>Me("clipboard.copied"))).subscribe(e)}function hi(e,t){if(!(e.target instanceof Element))return y;let r=e.target.closest("a");if(r===null)return y;if(r.target||e.metaKey||e.ctrlKey)return y;let o=new URL(r.href);return o.search=o.hash="",t.has(`${o}`)?(e.preventDefault(),$(r)):y}function bi(e){let t=new Map;for(let r of M(":scope > *",e.head))t.set(r.outerHTML,r);return t}function vi(e){for(let t of M("[href], [src]",e))for(let r of["href","src"]){let o=t.getAttribute(r);if(o&&!/^(?:[a-z]+:)?\/\//i.test(o)){t[r]=t[r];break}}return $(e)}function bs(e){for(let o of["[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...V("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let n=ue(o),i=ue(o,e);typeof n!="undefined"&&typeof i!="undefined"&&n.replaceWith(i)}let t=bi(document);for(let[o,n]of bi(e))t.has(o)?t.delete(o):document.head.appendChild(n);for(let o of t.values()){let n=o.getAttribute("name");n!=="theme-color"&&n!=="color-scheme"&&o.remove()}let r=Ce("container");return Ke(M("script",r)).pipe(b(o=>{let n=e.createElement("script");if(o.src){for(let i of o.getAttributeNames())n.setAttribute(i,o.getAttribute(i));return o.replaceWith(n),new F(i=>{n.onload=()=>i.complete()})}else return n.textContent=o.textContent,o.replaceWith(n),y}),oe(),ae(document))}function gi({sitemap$:e,location$:t,viewport$:r,progress$:o}){if(location.protocol==="file:")return y;$(document).subscribe(vi);let n=h(document.body,"click").pipe(Pe(e),b(([a,c])=>hi(a,c)),m(({href:a})=>new URL(a)),le()),i=h(window,"popstate").pipe(m(we),le());n.pipe(te(r)).subscribe(([a,{offset:c}])=>{history.replaceState(c,""),history.pushState(null,"",a)}),L(n,i).subscribe(t);let s=t.pipe(ne("pathname"),b(a=>xr(a,{progress$:o}).pipe(ve(()=>(st(a,!0),y)))),b(vi),b(bs),le());return L(s.pipe(te(t,(a,c)=>c)),s.pipe(b(()=>t),ne("hash")),t.pipe(Y((a,c)=>a.pathname===c.pathname&&a.hash===c.hash),b(()=>n),O(()=>history.back()))).subscribe(a=>{var c,p;history.state!==null||!a.hash?window.scrollTo(0,(p=(c=history.state)==null?void 0:c.y)!=null?p:0):(history.scrollRestoration="auto",gn(a.hash),history.scrollRestoration="manual")}),t.subscribe(()=>{history.scrollRestoration="manual"}),h(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),r.pipe(ne("offset"),Ae(100)).subscribe(({offset:a})=>{history.replaceState(a,"")}),V("navigation.instant.prefetch")&&L(h(document.body,"mousemove"),h(document.body,"focusin")).pipe(Pe(e),b(([a,c])=>hi(a,c)),Ae(25),Qr(({href:a})=>a),hr(a=>{let c=document.createElement("link");return c.rel="prefetch",c.href=a.toString(),document.head.appendChild(c),h(c,"load").pipe(m(()=>c),Ee(1))})).subscribe(a=>a.remove()),s}var yi=$t(ro());function xi(e){let t=e.separator.split("|").map(n=>n.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":n).join("|"),r=new RegExp(t,"img"),o=(n,i,s)=>`${i}${s}`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").replace(/&/g,"&").trim();let i=new RegExp(`(^|${e.separator}|)(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return s=>(0,yi.default)(s).replace(i,o).replace(/<\/mark>(\s+)]*>/img,"$1")}}function zt(e){return e.type===1}function Sr(e){return e.type===3}function Ei(e,t){let r=Mn(e);return L($(location.protocol!=="file:"),Je("search")).pipe(Re(o=>o),b(()=>t)).subscribe(({config:o,docs:n})=>r.next({type:0,data:{config:o,docs:n,options:{suggest:V("search.suggest")}}})),r}function wi(e){var l;let{selectedVersionSitemap:t,selectedVersionBaseURL:r,currentLocation:o,currentBaseURL:n}=e,i=(l=po(n))==null?void 0:l.pathname;if(i===void 0)return;let s=ys(o.pathname,i);if(s===void 0)return;let a=Es(t.keys());if(!t.has(a))return;let c=po(s,a);if(!c||!t.has(c.href))return;let p=po(s,r);if(p)return p.hash=o.hash,p.search=o.search,p}function po(e,t){try{return new URL(e,t)}catch(r){return}}function ys(e,t){if(e.startsWith(t))return e.slice(t.length)}function xs(e,t){let r=Math.min(e.length,t.length),o;for(o=0;oy)),o=r.pipe(m(n=>{let[,i]=t.base.match(/([^/]+)\/?$/);return n.find(({version:s,aliases:a})=>s===i||a.includes(i))||n[0]}));r.pipe(m(n=>new Map(n.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),b(n=>h(document.body,"click").pipe(g(i=>!i.metaKey&&!i.ctrlKey),te(o),b(([i,s])=>{if(i.target instanceof Element){let a=i.target.closest("a");if(a&&!a.target&&n.has(a.href)){let c=a.href;return!i.target.closest(".md-version")&&n.get(c)===s?y:(i.preventDefault(),$(new URL(c)))}}return y}),b(i=>kt(i).pipe(m(s=>{var a;return(a=wi({selectedVersionSitemap:s,selectedVersionBaseURL:i,currentLocation:we(),currentBaseURL:t.base}))!=null?a:i})))))).subscribe(n=>st(n,!0)),z([r,o]).subscribe(([n,i])=>{j(".md-header__topic").appendChild(Wn(n,i))}),e.pipe(b(()=>o)).subscribe(n=>{var a;let i=new URL(t.base),s=__md_get("__outdated",sessionStorage,i);if(s===null){s=!0;let c=((a=t.version)==null?void 0:a.default)||"latest";Array.isArray(c)||(c=[c]);e:for(let p of c)for(let l of n.aliases.concat(n.version))if(new RegExp(p,"i").test(l)){s=!1;break e}__md_set("__outdated",s,sessionStorage,i)}if(s)for(let c of me("outdated"))c.hidden=!1})}function ws(e,{worker$:t}){let{searchParams:r}=we();r.has("q")&&(at("search",!0),e.value=r.get("q"),e.focus(),Je("search").pipe(Re(i=>!i)).subscribe(()=>{let i=we();i.searchParams.delete("q"),history.replaceState({},"",`${i}`)}));let o=Ye(e),n=L(t.pipe(Re(zt)),h(e,"keyup"),o).pipe(m(()=>e.value),Y());return z([n,o]).pipe(m(([i,s])=>({value:i,focus:s})),Z(1))}function Si(e,{worker$:t}){let r=new T,o=r.pipe(oe(),ae(!0));z([t.pipe(Re(zt)),r],(i,s)=>s).pipe(ne("value")).subscribe(({value:i})=>t.next({type:2,data:i})),r.pipe(ne("focus")).subscribe(({focus:i})=>{i&&at("search",i)}),h(e.form,"reset").pipe(W(o)).subscribe(()=>e.focus());let n=j("header [for=__search]");return h(n,"click").subscribe(()=>e.focus()),ws(e,{worker$:t}).pipe(O(i=>r.next(i)),A(()=>r.complete()),m(i=>P({ref:e},i)),Z(1))}function Oi(e,{worker$:t,query$:r}){let o=new T,n=un(e.parentElement).pipe(g(Boolean)),i=e.parentElement,s=j(":scope > :first-child",e),a=j(":scope > :last-child",e);Je("search").subscribe(l=>{a.setAttribute("role",l?"list":"presentation"),a.hidden=!l}),o.pipe(te(r),Gr(t.pipe(Re(zt)))).subscribe(([{items:l},{value:f}])=>{switch(l.length){case 0:s.textContent=f.length?Me("search.result.none"):Me("search.result.placeholder");break;case 1:s.textContent=Me("search.result.one");break;default:let u=br(l.length);s.textContent=Me("search.result.other",u)}});let c=o.pipe(O(()=>a.innerHTML=""),b(({items:l})=>L($(...l.slice(0,10)),$(...l.slice(10)).pipe(ot(4),Xr(n),b(([f])=>f)))),m(Fn),le());return c.subscribe(l=>a.appendChild(l)),c.pipe(J(l=>{let f=ue("details",l);return typeof f=="undefined"?y:h(f,"toggle").pipe(W(o),m(()=>f))})).subscribe(l=>{l.open===!1&&l.offsetTop<=i.scrollTop&&i.scrollTo({top:l.offsetTop})}),t.pipe(g(Sr),m(({data:l})=>l)).pipe(O(l=>o.next(l)),A(()=>o.complete()),m(l=>P({ref:e},l)))}function Ts(e,{query$:t}){return t.pipe(m(({value:r})=>{let o=we();return o.hash="",r=r.replace(/\s+/g,"+").replace(/&/g,"%26").replace(/=/g,"%3D"),o.search=`q=${r}`,{url:o}}))}function Li(e,t){let r=new T,o=r.pipe(oe(),ae(!0));return r.subscribe(({url:n})=>{e.setAttribute("data-clipboard-text",e.href),e.href=`${n}`}),h(e,"click").pipe(W(o)).subscribe(n=>n.preventDefault()),Ts(e,t).pipe(O(n=>r.next(n)),A(()=>r.complete()),m(n=>P({ref:e},n)))}function Mi(e,{worker$:t,keyboard$:r}){let o=new T,n=Ce("search-query"),i=L(h(n,"keydown"),h(n,"focus")).pipe(xe(pe),m(()=>n.value),Y());return o.pipe(Pe(i),m(([{suggest:a},c])=>{let p=c.split(/([\s-]+)/);if(a!=null&&a.length&&p[p.length-1]){let l=a[a.length-1];l.startsWith(p[p.length-1])&&(p[p.length-1]=l)}else p.length=0;return p})).subscribe(a=>e.innerHTML=a.join("").replace(/\s/g," ")),r.pipe(g(({mode:a})=>a==="search")).subscribe(a=>{a.type==="ArrowRight"&&e.innerText.length&&n.selectionStart===n.value.length&&(n.value=e.innerText)}),t.pipe(g(Sr),m(({data:a})=>a)).pipe(O(a=>o.next(a)),A(()=>o.complete()),m(()=>({ref:e})))}function _i(e,{index$:t,keyboard$:r}){let o=Te();try{let n=Ei(o.search,t),i=Ce("search-query",e),s=Ce("search-result",e);h(e,"click").pipe(g(({target:c})=>c instanceof Element&&!!c.closest("a"))).subscribe(()=>at("search",!1)),r.pipe(g(({mode:c})=>c==="search")).subscribe(c=>{let p=Ne();switch(c.type){case"Enter":if(p===i){let l=new Map;for(let f of M(":first-child [href]",s)){let u=f.firstElementChild;l.set(f,parseFloat(u.getAttribute("data-md-score")))}if(l.size){let[[f]]=[...l].sort(([,u],[,d])=>d-u);f.click()}c.claim()}break;case"Escape":case"Tab":at("search",!1),i.blur();break;case"ArrowUp":case"ArrowDown":if(typeof p=="undefined")i.focus();else{let l=[i,...M(":not(details) > [href], summary, details[open] [href]",s)],f=Math.max(0,(Math.max(0,l.indexOf(p))+l.length+(c.type==="ArrowUp"?-1:1))%l.length);l[f].focus()}c.claim();break;default:i!==Ne()&&i.focus()}}),r.pipe(g(({mode:c})=>c==="global")).subscribe(c=>{switch(c.type){case"f":case"s":case"/":i.focus(),i.select(),c.claim();break}});let a=Si(i,{worker$:n});return L(a,Oi(s,{worker$:n,query$:a})).pipe(Ve(...me("search-share",e).map(c=>Li(c,{query$:a})),...me("search-suggest",e).map(c=>Mi(c,{worker$:n,keyboard$:r}))))}catch(n){return e.hidden=!0,tt}}function Ai(e,{index$:t,location$:r}){return z([t,r.pipe(Q(we()),g(o=>!!o.searchParams.get("h")))]).pipe(m(([o,n])=>xi(o.config)(n.searchParams.get("h"))),m(o=>{var s;let n=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let a=i.nextNode();a;a=i.nextNode())if((s=a.parentElement)!=null&&s.offsetHeight){let c=a.textContent,p=o(c);p.length>c.length&&n.set(a,p)}for(let[a,c]of n){let{childNodes:p}=x("span",null,c);a.replaceWith(...Array.from(p))}return{ref:e,nodes:n}}))}function Ss(e,{viewport$:t,main$:r}){let o=e.closest(".md-grid"),n=o.offsetTop-o.parentElement.offsetTop;return z([r,t]).pipe(m(([{offset:i,height:s},{offset:{y:a}}])=>(s=s+Math.min(n,Math.max(0,a-i))-n,{height:s,locked:a>=i+n})),Y((i,s)=>i.height===s.height&&i.locked===s.locked))}function lo(e,o){var n=o,{header$:t}=n,r=vo(n,["header$"]);let i=j(".md-sidebar__scrollwrap",e),{y:s}=Be(i);return H(()=>{let a=new T,c=a.pipe(oe(),ae(!0)),p=a.pipe($e(0,ye));return p.pipe(te(t)).subscribe({next([{height:l},{height:f}]){i.style.height=`${l-2*s}px`,e.style.top=`${f}px`},complete(){i.style.height="",e.style.top=""}}),p.pipe(Re()).subscribe(()=>{for(let l of M(".md-nav__link--active[href]",e)){if(!l.clientHeight)continue;let f=l.closest(".md-sidebar__scrollwrap");if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=de(f);f.scrollTo({top:u-d/2})}}}),fe(M("label[tabindex]",e)).pipe(J(l=>h(l,"click").pipe(xe(pe),m(()=>l),W(c)))).subscribe(l=>{let f=j(`[id="${l.htmlFor}"]`);j(`[aria-labelledby="${l.id}"]`).setAttribute("aria-expanded",`${f.checked}`)}),V("content.tooltips")&&fe(M("abbr[title]",e)).pipe(J(l=>Xe(l,{viewport$})),W(c)).subscribe(),Ss(e,r).pipe(O(l=>a.next(l)),A(()=>a.complete()),m(l=>P({ref:e},l)))})}function Ci(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return rt(ze(`${r}/releases/latest`).pipe(ve(()=>y),m(o=>({version:o.tag_name})),Qe({})),ze(r).pipe(ve(()=>y),m(o=>({stars:o.stargazers_count,forks:o.forks_count})),Qe({}))).pipe(m(([o,n])=>P(P({},o),n)))}else{let r=`https://api.github.com/users/${e}`;return ze(r).pipe(m(o=>({repositories:o.public_repos})),Qe({}))}}function ki(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return rt(ze(`${r}/releases/permalink/latest`).pipe(ve(()=>y),m(({tag_name:o})=>({version:o})),Qe({})),ze(r).pipe(ve(()=>y),m(({star_count:o,forks_count:n})=>({stars:o,forks:n})),Qe({}))).pipe(m(([o,n])=>P(P({},o),n)))}function Hi(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,o]=t;return Ci(r,o)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,o]=t;return ki(r,o)}return y}var Os;function Ls(e){return Os||(Os=H(()=>{let t=__md_get("__source",sessionStorage);if(t)return $(t);if(me("consent").length){let o=__md_get("__consent");if(!(o&&o.github))return y}return Hi(e.href).pipe(O(o=>__md_set("__source",o,sessionStorage)))}).pipe(ve(()=>y),g(t=>Object.keys(t).length>0),m(t=>({facts:t})),Z(1)))}function $i(e){let t=j(":scope > :last-child",e);return H(()=>{let r=new T;return r.subscribe(({facts:o})=>{t.appendChild(jn(o)),t.classList.add("md-source__repository--active")}),Ls(e).pipe(O(o=>r.next(o)),A(()=>r.complete()),m(o=>P({ref:e},o)))})}function Ms(e,{viewport$:t,header$:r}){return Le(document.body).pipe(b(()=>Er(e,{header$:r,viewport$:t})),m(({offset:{y:o}})=>({hidden:o>=10})),ne("hidden"))}function Pi(e,t){return H(()=>{let r=new T;return r.subscribe({next({hidden:o}){e.hidden=o},complete(){e.hidden=!1}}),(V("navigation.tabs.sticky")?$({hidden:!1}):Ms(e,t)).pipe(O(o=>r.next(o)),A(()=>r.complete()),m(o=>P({ref:e},o)))})}function _s(e,{viewport$:t,header$:r}){let o=new Map,n=M(".md-nav__link",e);for(let a of n){let c=decodeURIComponent(a.hash.substring(1)),p=ue(`[id="${c}"]`);typeof p!="undefined"&&o.set(a,p)}let i=r.pipe(ne("height"),m(({height:a})=>{let c=Ce("main"),p=j(":scope > :first-child",c);return a+.8*(p.offsetTop-c.offsetTop)}),le());return Le(document.body).pipe(ne("height"),b(a=>H(()=>{let c=[];return $([...o].reduce((p,[l,f])=>{for(;c.length&&o.get(c[c.length-1]).tagName>=f.tagName;)c.pop();let u=f.offsetTop;for(;!u&&f.parentElement;)f=f.parentElement,u=f.offsetTop;let d=f.offsetParent;for(;d;d=d.offsetParent)u+=d.offsetTop;return p.set([...c=[...c,l]].reverse(),u)},new Map))}).pipe(m(c=>new Map([...c].sort(([,p],[,l])=>p-l))),Pe(i),b(([c,p])=>t.pipe(Ut(([l,f],{offset:{y:u},size:d})=>{let v=u+d.height>=Math.floor(a.height);for(;f.length;){let[,S]=f[0];if(S-p=u&&!v)f=[l.pop(),...f];else break}return[l,f]},[[],[...c]]),Y((l,f)=>l[0]===f[0]&&l[1]===f[1])))))).pipe(m(([a,c])=>({prev:a.map(([p])=>p),next:c.map(([p])=>p)})),Q({prev:[],next:[]}),ot(2,1),m(([a,c])=>a.prev.length{let i=new T,s=i.pipe(oe(),ae(!0));if(i.subscribe(({prev:a,next:c})=>{for(let[p]of c)p.classList.remove("md-nav__link--passed"),p.classList.remove("md-nav__link--active");for(let[p,[l]]of a.entries())l.classList.add("md-nav__link--passed"),l.classList.toggle("md-nav__link--active",p===a.length-1)}),V("toc.follow")){let a=L(t.pipe(Ae(1),m(()=>{})),t.pipe(Ae(250),m(()=>"smooth")));i.pipe(g(({prev:c})=>c.length>0),Pe(o.pipe(xe(pe))),te(a)).subscribe(([[{prev:c}],p])=>{let[l]=c[c.length-1];if(l.offsetHeight){let f=vr(l);if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=de(f);f.scrollTo({top:u-d/2,behavior:p})}}})}return V("navigation.tracking")&&t.pipe(W(s),ne("offset"),Ae(250),Ie(1),W(n.pipe(Ie(1))),vt({delay:250}),te(i)).subscribe(([,{prev:a}])=>{let c=we(),p=a[a.length-1];if(p&&p.length){let[l]=p,{hash:f}=new URL(l.href);c.hash!==f&&(c.hash=f,history.replaceState({},"",`${c}`))}else c.hash="",history.replaceState({},"",`${c}`)}),_s(e,{viewport$:t,header$:r}).pipe(O(a=>i.next(a)),A(()=>i.complete()),m(a=>P({ref:e},a)))})}function As(e,{viewport$:t,main$:r,target$:o}){let n=t.pipe(m(({offset:{y:s}})=>s),ot(2,1),m(([s,a])=>s>a&&a>0),Y()),i=r.pipe(m(({active:s})=>s));return z([i,n]).pipe(m(([s,a])=>!(s&&a)),Y(),W(o.pipe(Ie(1))),ae(!0),vt({delay:250}),m(s=>({hidden:s})))}function Ii(e,{viewport$:t,header$:r,main$:o,target$:n}){let i=new T,s=i.pipe(oe(),ae(!0));return i.subscribe({next({hidden:a}){e.hidden=a,a?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(W(s),ne("height")).subscribe(({height:a})=>{e.style.top=`${a+16}px`}),h(e,"click").subscribe(a=>{a.preventDefault(),window.scrollTo({top:0})}),As(e,{viewport$:t,main$:o,target$:n}).pipe(O(a=>i.next(a)),A(()=>i.complete()),m(a=>P({ref:e},a)))}function Fi({document$:e,viewport$:t}){e.pipe(b(()=>M(".md-ellipsis")),J(r=>mt(r).pipe(W(e.pipe(Ie(1))),g(o=>o),m(()=>r),Ee(1))),g(r=>r.offsetWidth{let o=r.innerText,n=r.closest("a")||r;return n.title=o,V("content.tooltips")?Xe(n,{viewport$:t}).pipe(W(e.pipe(Ie(1))),A(()=>n.removeAttribute("title"))):y})).subscribe(),V("content.tooltips")&&e.pipe(b(()=>M(".md-status")),J(r=>Xe(r,{viewport$:t}))).subscribe()}function ji({document$:e,tablet$:t}){e.pipe(b(()=>M(".md-toggle--indeterminate")),O(r=>{r.indeterminate=!0,r.checked=!1}),J(r=>h(r,"change").pipe(Jr(()=>r.classList.contains("md-toggle--indeterminate")),m(()=>r))),te(t)).subscribe(([r,o])=>{r.classList.remove("md-toggle--indeterminate"),o&&(r.checked=!1)})}function Cs(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function Ui({document$:e}){e.pipe(b(()=>M("[data-md-scrollfix]")),O(t=>t.removeAttribute("data-md-scrollfix")),g(Cs),J(t=>h(t,"touchstart").pipe(m(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function Wi({viewport$:e,tablet$:t}){z([Je("search"),t]).pipe(m(([r,o])=>r&&!o),b(r=>$(r).pipe(nt(r?400:100))),te(e)).subscribe(([r,{offset:{y:o}}])=>{if(r)document.body.setAttribute("data-md-scrolllock",""),document.body.style.top=`-${o}px`;else{let n=-1*parseInt(document.body.style.top,10);document.body.removeAttribute("data-md-scrolllock"),document.body.style.top="",n&&window.scrollTo(0,n)}})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let o=e[r];typeof o=="string"?o=document.createTextNode(o):o.parentNode&&o.parentNode.removeChild(o),r?t.insertBefore(this.previousSibling,o):t.replaceChild(o,this)}}}));function ks(){return location.protocol==="file:"?_t(`${new URL("search/search_index.js",Or.base)}`).pipe(m(()=>__index),Z(1)):ze(new URL("search/search_index.json",Or.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var ct=an(),Kt=bn(),Ht=yn(Kt),mo=hn(),ke=Ln(),Lr=Wt("(min-width: 60em)"),Vi=Wt("(min-width: 76.25em)"),Ni=xn(),Or=Te(),zi=document.forms.namedItem("search")?ks():tt,fo=new T;di({alert$:fo});ui({document$:ct});var uo=new T,qi=kt(Or.base);V("navigation.instant")&&gi({sitemap$:qi,location$:Kt,viewport$:ke,progress$:uo}).subscribe(ct);var Di;((Di=Or.version)==null?void 0:Di.provider)==="mike"&&Ti({document$:ct});L(Kt,Ht).pipe(nt(125)).subscribe(()=>{at("drawer",!1),at("search",!1)});mo.pipe(g(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=ue("link[rel=prev]");typeof t!="undefined"&&st(t);break;case"n":case".":let r=ue("link[rel=next]");typeof r!="undefined"&&st(r);break;case"Enter":let o=Ne();o instanceof HTMLLabelElement&&o.click()}});Fi({viewport$:ke,document$:ct});ji({document$:ct,tablet$:Lr});Ui({document$:ct});Wi({viewport$:ke,tablet$:Lr});var ft=ai(Ce("header"),{viewport$:ke}),qt=ct.pipe(m(()=>Ce("main")),b(e=>pi(e,{viewport$:ke,header$:ft})),Z(1)),Hs=L(...me("consent").map(e=>An(e,{target$:Ht})),...me("dialog").map(e=>ni(e,{alert$:fo})),...me("palette").map(e=>li(e)),...me("progress").map(e=>mi(e,{progress$:uo})),...me("search").map(e=>_i(e,{index$:zi,keyboard$:mo})),...me("source").map(e=>$i(e))),$s=H(()=>L(...me("announce").map(e=>_n(e)),...me("content").map(e=>oi(e,{sitemap$:qi,viewport$:ke,target$:Ht,print$:Ni})),...me("content").map(e=>V("search.highlight")?Ai(e,{index$:zi,location$:Kt}):y),...me("header").map(e=>si(e,{viewport$:ke,header$:ft,main$:qt})),...me("header-title").map(e=>ci(e,{viewport$:ke,header$:ft})),...me("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?eo(Vi,()=>lo(e,{viewport$:ke,header$:ft,main$:qt})):eo(Lr,()=>lo(e,{viewport$:ke,header$:ft,main$:qt}))),...me("tabs").map(e=>Pi(e,{viewport$:ke,header$:ft})),...me("toc").map(e=>Ri(e,{viewport$:ke,header$:ft,main$:qt,target$:Ht})),...me("top").map(e=>Ii(e,{viewport$:ke,header$:ft,main$:qt,target$:Ht})))),Ki=ct.pipe(b(()=>$s),Ve(Hs),Z(1));Ki.subscribe();window.document$=ct;window.location$=Kt;window.target$=Ht;window.keyboard$=mo;window.viewport$=ke;window.tablet$=Lr;window.screen$=Vi;window.print$=Ni;window.alert$=fo;window.progress$=uo;window.component$=Ki;})(); +//# sourceMappingURL=bundle.79ae519e.min.js.map + diff --git a/assets/javascripts/bundle.79ae519e.min.js.map b/assets/javascripts/bundle.79ae519e.min.js.map new file mode 100644 index 00000000..5cf02892 --- /dev/null +++ b/assets/javascripts/bundle.79ae519e.min.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/escape-html/index.js", "node_modules/clipboard/dist/clipboard.js", "src/templates/assets/javascripts/bundle.ts", "node_modules/tslib/tslib.es6.mjs", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/util/errorContext.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/BehaviorSubject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/QueueAction.ts", "node_modules/rxjs/src/internal/scheduler/QueueScheduler.ts", "node_modules/rxjs/src/internal/scheduler/queue.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/util/isReadableStreamLike.ts", "node_modules/rxjs/src/internal/observable/innerFrom.ts", "node_modules/rxjs/src/internal/util/executeSchedule.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/operators/subscribeOn.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleReadableStreamLike.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/observable/throwError.ts", "node_modules/rxjs/src/internal/util/EmptyError.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/fromEventPattern.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/audit.ts", "node_modules/rxjs/src/internal/operators/auditTime.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatest.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/debounce.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinct.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/throwIfEmpty.ts", "node_modules/rxjs/src/internal/operators/endWith.ts", "node_modules/rxjs/src/internal/operators/exhaustMap.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/first.ts", "node_modules/rxjs/src/internal/operators/takeLast.ts", "node_modules/rxjs/src/internal/operators/merge.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/repeat.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/throttleTime.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zip.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/templates/assets/javascripts/browser/document/index.ts", "src/templates/assets/javascripts/browser/element/_/index.ts", "src/templates/assets/javascripts/browser/element/focus/index.ts", "src/templates/assets/javascripts/browser/element/hover/index.ts", "src/templates/assets/javascripts/utilities/h/index.ts", "src/templates/assets/javascripts/utilities/round/index.ts", "src/templates/assets/javascripts/browser/script/index.ts", "src/templates/assets/javascripts/browser/element/size/_/index.ts", "src/templates/assets/javascripts/browser/element/size/content/index.ts", "src/templates/assets/javascripts/browser/element/offset/_/index.ts", "src/templates/assets/javascripts/browser/element/offset/content/index.ts", "src/templates/assets/javascripts/browser/element/visibility/index.ts", "src/templates/assets/javascripts/browser/toggle/index.ts", "src/templates/assets/javascripts/browser/keyboard/index.ts", "src/templates/assets/javascripts/browser/location/_/index.ts", "src/templates/assets/javascripts/browser/location/hash/index.ts", "src/templates/assets/javascripts/browser/media/index.ts", "src/templates/assets/javascripts/browser/request/index.ts", "src/templates/assets/javascripts/browser/viewport/offset/index.ts", "src/templates/assets/javascripts/browser/viewport/size/index.ts", "src/templates/assets/javascripts/browser/viewport/_/index.ts", "src/templates/assets/javascripts/browser/viewport/at/index.ts", "src/templates/assets/javascripts/browser/worker/index.ts", "src/templates/assets/javascripts/_/index.ts", "src/templates/assets/javascripts/components/_/index.ts", "src/templates/assets/javascripts/components/announce/index.ts", "src/templates/assets/javascripts/components/consent/index.ts", "src/templates/assets/javascripts/templates/tooltip/index.tsx", "src/templates/assets/javascripts/templates/annotation/index.tsx", "src/templates/assets/javascripts/templates/clipboard/index.tsx", "src/templates/assets/javascripts/templates/search/index.tsx", "src/templates/assets/javascripts/templates/source/index.tsx", "src/templates/assets/javascripts/templates/tabbed/index.tsx", "src/templates/assets/javascripts/templates/table/index.tsx", "src/templates/assets/javascripts/templates/version/index.tsx", "src/templates/assets/javascripts/components/tooltip2/index.ts", "src/templates/assets/javascripts/components/content/annotation/_/index.ts", "src/templates/assets/javascripts/components/content/annotation/list/index.ts", "src/templates/assets/javascripts/components/content/annotation/block/index.ts", "src/templates/assets/javascripts/components/content/code/_/index.ts", "src/templates/assets/javascripts/components/content/details/index.ts", "src/templates/assets/javascripts/components/content/link/index.ts", "src/templates/assets/javascripts/components/content/mermaid/index.css", "src/templates/assets/javascripts/components/content/mermaid/index.ts", "src/templates/assets/javascripts/components/content/table/index.ts", "src/templates/assets/javascripts/components/content/tabs/index.ts", "src/templates/assets/javascripts/components/content/_/index.ts", "src/templates/assets/javascripts/components/dialog/index.ts", "src/templates/assets/javascripts/components/tooltip/index.ts", "src/templates/assets/javascripts/components/header/_/index.ts", "src/templates/assets/javascripts/components/header/title/index.ts", "src/templates/assets/javascripts/components/main/index.ts", "src/templates/assets/javascripts/components/palette/index.ts", "src/templates/assets/javascripts/components/progress/index.ts", "src/templates/assets/javascripts/integrations/sitemap/index.ts", "src/templates/assets/javascripts/integrations/alternate/index.ts", "src/templates/assets/javascripts/integrations/clipboard/index.ts", "src/templates/assets/javascripts/integrations/instant/index.ts", "src/templates/assets/javascripts/integrations/search/highlighter/index.ts", "src/templates/assets/javascripts/integrations/search/worker/message/index.ts", "src/templates/assets/javascripts/integrations/search/worker/_/index.ts", "src/templates/assets/javascripts/integrations/version/findurl/index.ts", "src/templates/assets/javascripts/integrations/version/index.ts", "src/templates/assets/javascripts/components/search/query/index.ts", "src/templates/assets/javascripts/components/search/result/index.ts", "src/templates/assets/javascripts/components/search/share/index.ts", "src/templates/assets/javascripts/components/search/suggest/index.ts", "src/templates/assets/javascripts/components/search/_/index.ts", "src/templates/assets/javascripts/components/search/highlight/index.ts", "src/templates/assets/javascripts/components/sidebar/index.ts", "src/templates/assets/javascripts/components/source/facts/github/index.ts", "src/templates/assets/javascripts/components/source/facts/gitlab/index.ts", "src/templates/assets/javascripts/components/source/facts/_/index.ts", "src/templates/assets/javascripts/components/source/_/index.ts", "src/templates/assets/javascripts/components/tabs/index.ts", "src/templates/assets/javascripts/components/toc/index.ts", "src/templates/assets/javascripts/components/top/index.ts", "src/templates/assets/javascripts/patches/ellipsis/index.ts", "src/templates/assets/javascripts/patches/indeterminate/index.ts", "src/templates/assets/javascripts/patches/scrollfix/index.ts", "src/templates/assets/javascripts/patches/scrolllock/index.ts", "src/templates/assets/javascripts/polyfills/index.ts"], + "sourcesContent": ["(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n (factory());\n}(this, (function () { 'use strict';\n\n /**\n * Applies the :focus-visible polyfill at the given scope.\n * A scope in this case is either the top-level Document or a Shadow Root.\n *\n * @param {(Document|ShadowRoot)} scope\n * @see https://github.com/WICG/focus-visible\n */\n function applyFocusVisiblePolyfill(scope) {\n var hadKeyboardEvent = true;\n var hadFocusVisibleRecently = false;\n var hadFocusVisibleRecentlyTimeout = null;\n\n var inputTypesAllowlist = {\n text: true,\n search: true,\n url: true,\n tel: true,\n email: true,\n password: true,\n number: true,\n date: true,\n month: true,\n week: true,\n time: true,\n datetime: true,\n 'datetime-local': true\n };\n\n /**\n * Helper function for legacy browsers and iframes which sometimes focus\n * elements like document, body, and non-interactive SVG.\n * @param {Element} el\n */\n function isValidFocusTarget(el) {\n if (\n el &&\n el !== document &&\n el.nodeName !== 'HTML' &&\n el.nodeName !== 'BODY' &&\n 'classList' in el &&\n 'contains' in el.classList\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Computes whether the given element should automatically trigger the\n * `focus-visible` class being added, i.e. whether it should always match\n * `:focus-visible` when focused.\n * @param {Element} el\n * @return {boolean}\n */\n function focusTriggersKeyboardModality(el) {\n var type = el.type;\n var tagName = el.tagName;\n\n if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n return true;\n }\n\n if (tagName === 'TEXTAREA' && !el.readOnly) {\n return true;\n }\n\n if (el.isContentEditable) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Add the `focus-visible` class to the given element if it was not added by\n * the author.\n * @param {Element} el\n */\n function addFocusVisibleClass(el) {\n if (el.classList.contains('focus-visible')) {\n return;\n }\n el.classList.add('focus-visible');\n el.setAttribute('data-focus-visible-added', '');\n }\n\n /**\n * Remove the `focus-visible` class from the given element if it was not\n * originally added by the author.\n * @param {Element} el\n */\n function removeFocusVisibleClass(el) {\n if (!el.hasAttribute('data-focus-visible-added')) {\n return;\n }\n el.classList.remove('focus-visible');\n el.removeAttribute('data-focus-visible-added');\n }\n\n /**\n * If the most recent user interaction was via the keyboard;\n * and the key press did not include a meta, alt/option, or control key;\n * then the modality is keyboard. Otherwise, the modality is not keyboard.\n * Apply `focus-visible` to any current active element and keep track\n * of our keyboard modality state with `hadKeyboardEvent`.\n * @param {KeyboardEvent} e\n */\n function onKeyDown(e) {\n if (e.metaKey || e.altKey || e.ctrlKey) {\n return;\n }\n\n if (isValidFocusTarget(scope.activeElement)) {\n addFocusVisibleClass(scope.activeElement);\n }\n\n hadKeyboardEvent = true;\n }\n\n /**\n * If at any point a user clicks with a pointing device, ensure that we change\n * the modality away from keyboard.\n * This avoids the situation where a user presses a key on an already focused\n * element, and then clicks on a different element, focusing it with a\n * pointing device, while we still think we're in keyboard modality.\n * @param {Event} e\n */\n function onPointerDown(e) {\n hadKeyboardEvent = false;\n }\n\n /**\n * On `focus`, add the `focus-visible` class to the target if:\n * - the target received focus as a result of keyboard navigation, or\n * - the event target is an element that will likely require interaction\n * via the keyboard (e.g. a text box)\n * @param {Event} e\n */\n function onFocus(e) {\n // Prevent IE from focusing the document or HTML element.\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n addFocusVisibleClass(e.target);\n }\n }\n\n /**\n * On `blur`, remove the `focus-visible` class from the target.\n * @param {Event} e\n */\n function onBlur(e) {\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (\n e.target.classList.contains('focus-visible') ||\n e.target.hasAttribute('data-focus-visible-added')\n ) {\n // To detect a tab/window switch, we look for a blur event followed\n // rapidly by a visibility change.\n // If we don't see a visibility change within 100ms, it's probably a\n // regular focus change.\n hadFocusVisibleRecently = true;\n window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n hadFocusVisibleRecently = false;\n }, 100);\n removeFocusVisibleClass(e.target);\n }\n }\n\n /**\n * If the user changes tabs, keep track of whether or not the previously\n * focused element had .focus-visible.\n * @param {Event} e\n */\n function onVisibilityChange(e) {\n if (document.visibilityState === 'hidden') {\n // If the tab becomes active again, the browser will handle calling focus\n // on the element (Safari actually calls it twice).\n // If this tab change caused a blur on an element with focus-visible,\n // re-apply the class when the user switches back to the tab.\n if (hadFocusVisibleRecently) {\n hadKeyboardEvent = true;\n }\n addInitialPointerMoveListeners();\n }\n }\n\n /**\n * Add a group of listeners to detect usage of any pointing devices.\n * These listeners will be added when the polyfill first loads, and anytime\n * the window is blurred, so that they are active when the window regains\n * focus.\n */\n function addInitialPointerMoveListeners() {\n document.addEventListener('mousemove', onInitialPointerMove);\n document.addEventListener('mousedown', onInitialPointerMove);\n document.addEventListener('mouseup', onInitialPointerMove);\n document.addEventListener('pointermove', onInitialPointerMove);\n document.addEventListener('pointerdown', onInitialPointerMove);\n document.addEventListener('pointerup', onInitialPointerMove);\n document.addEventListener('touchmove', onInitialPointerMove);\n document.addEventListener('touchstart', onInitialPointerMove);\n document.addEventListener('touchend', onInitialPointerMove);\n }\n\n function removeInitialPointerMoveListeners() {\n document.removeEventListener('mousemove', onInitialPointerMove);\n document.removeEventListener('mousedown', onInitialPointerMove);\n document.removeEventListener('mouseup', onInitialPointerMove);\n document.removeEventListener('pointermove', onInitialPointerMove);\n document.removeEventListener('pointerdown', onInitialPointerMove);\n document.removeEventListener('pointerup', onInitialPointerMove);\n document.removeEventListener('touchmove', onInitialPointerMove);\n document.removeEventListener('touchstart', onInitialPointerMove);\n document.removeEventListener('touchend', onInitialPointerMove);\n }\n\n /**\n * When the polfyill first loads, assume the user is in keyboard modality.\n * If any event is received from a pointing device (e.g. mouse, pointer,\n * touch), turn off keyboard modality.\n * This accounts for situations where focus enters the page from the URL bar.\n * @param {Event} e\n */\n function onInitialPointerMove(e) {\n // Work around a Safari quirk that fires a mousemove on whenever the\n // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n return;\n }\n\n hadKeyboardEvent = false;\n removeInitialPointerMoveListeners();\n }\n\n // For some kinds of state, we are interested in changes at the global scope\n // only. For example, global pointer input, global key presses and global\n // visibility change should affect the state at every scope:\n document.addEventListener('keydown', onKeyDown, true);\n document.addEventListener('mousedown', onPointerDown, true);\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('touchstart', onPointerDown, true);\n document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n addInitialPointerMoveListeners();\n\n // For focus and blur, we specifically care about state changes in the local\n // scope. This is because focus / blur events that originate from within a\n // shadow root are not re-dispatched from the host element if it was already\n // the active element in its own scope:\n scope.addEventListener('focus', onFocus, true);\n scope.addEventListener('blur', onBlur, true);\n\n // We detect that a node is a ShadowRoot by ensuring that it is a\n // DocumentFragment and also has a host property. This check covers native\n // implementation and polyfill implementation transparently. If we only cared\n // about the native implementation, we could just check if the scope was\n // an instance of a ShadowRoot.\n if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n // have a root element to add a class to. So, we add this attribute to the\n // host element instead:\n scope.host.setAttribute('data-js-focus-visible', '');\n } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n document.documentElement.classList.add('js-focus-visible');\n document.documentElement.setAttribute('data-js-focus-visible', '');\n }\n }\n\n // It is important to wrap all references to global window and document in\n // these checks to support server-side rendering use cases\n // @see https://github.com/WICG/focus-visible/issues/199\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n // Make the polyfill helper globally available. This can be used as a signal\n // to interested libraries that wish to coordinate with the polyfill for e.g.,\n // applying the polyfill to a shadow root:\n window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n // Notify interested libraries of the polyfill's presence, in case the\n // polyfill was loaded lazily:\n var event;\n\n try {\n event = new CustomEvent('focus-visible-polyfill-ready');\n } catch (error) {\n // IE11 does not support using CustomEvent as a constructor directly:\n event = document.createEvent('CustomEvent');\n event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n }\n\n window.dispatchEvent(event);\n }\n\n if (typeof document !== 'undefined') {\n // Apply the polyfill to the global document, so that no JavaScript\n // coordination is required to use the polyfill in the top-level document:\n applyFocusVisiblePolyfill(document);\n }\n\n})));\n", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "/*!\n * clipboard.js v2.0.11\n * https://clipboardjs.com/\n *\n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function() { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ 686:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n\n// EXPORTS\n__webpack_require__.d(__webpack_exports__, {\n \"default\": function() { return /* binding */ clipboard; }\n});\n\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(279);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(370);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(817);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n;// CONCATENATED MODULE: ./src/common/command.js\n/**\n * Executes a given operation type.\n * @param {String} type\n * @return {Boolean}\n */\nfunction command(type) {\n try {\n return document.execCommand(type);\n } catch (err) {\n return false;\n }\n}\n;// CONCATENATED MODULE: ./src/actions/cut.js\n\n\n/**\n * Cut action wrapper.\n * @param {String|HTMLElement} target\n * @return {String}\n */\n\nvar ClipboardActionCut = function ClipboardActionCut(target) {\n var selectedText = select_default()(target);\n command('cut');\n return selectedText;\n};\n\n/* harmony default export */ var actions_cut = (ClipboardActionCut);\n;// CONCATENATED MODULE: ./src/common/create-fake-element.js\n/**\n * Creates a fake textarea element with a value.\n * @param {String} value\n * @return {HTMLElement}\n */\nfunction createFakeElement(value) {\n var isRTL = document.documentElement.getAttribute('dir') === 'rtl';\n var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS\n\n fakeElement.style.fontSize = '12pt'; // Reset box model\n\n fakeElement.style.border = '0';\n fakeElement.style.padding = '0';\n fakeElement.style.margin = '0'; // Move element out of screen horizontally\n\n fakeElement.style.position = 'absolute';\n fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically\n\n var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n fakeElement.style.top = \"\".concat(yPosition, \"px\");\n fakeElement.setAttribute('readonly', '');\n fakeElement.value = value;\n return fakeElement;\n}\n;// CONCATENATED MODULE: ./src/actions/copy.js\n\n\n\n/**\n * Create fake copy action wrapper using a fake element.\n * @param {String} target\n * @param {Object} options\n * @return {String}\n */\n\nvar fakeCopyAction = function fakeCopyAction(value, options) {\n var fakeElement = createFakeElement(value);\n options.container.appendChild(fakeElement);\n var selectedText = select_default()(fakeElement);\n command('copy');\n fakeElement.remove();\n return selectedText;\n};\n/**\n * Copy action wrapper.\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @return {String}\n */\n\n\nvar ClipboardActionCopy = function ClipboardActionCopy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n var selectedText = '';\n\n if (typeof target === 'string') {\n selectedText = fakeCopyAction(target, options);\n } else if (target instanceof HTMLInputElement && !['text', 'search', 'url', 'tel', 'password'].includes(target === null || target === void 0 ? void 0 : target.type)) {\n // If input type doesn't support `setSelectionRange`. Simulate it. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange\n selectedText = fakeCopyAction(target.value, options);\n } else {\n selectedText = select_default()(target);\n command('copy');\n }\n\n return selectedText;\n};\n\n/* harmony default export */ var actions_copy = (ClipboardActionCopy);\n;// CONCATENATED MODULE: ./src/actions/default.js\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\n\n\n/**\n * Inner function which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n * @param {Object} options\n */\n\nvar ClipboardActionDefault = function ClipboardActionDefault() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n // Defines base properties passed from constructor.\n var _options$action = options.action,\n action = _options$action === void 0 ? 'copy' : _options$action,\n container = options.container,\n target = options.target,\n text = options.text; // Sets the `action` to be performed which can be either 'copy' or 'cut'.\n\n if (action !== 'copy' && action !== 'cut') {\n throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n } // Sets the `target` property using an element that will be have its content copied.\n\n\n if (target !== undefined) {\n if (target && _typeof(target) === 'object' && target.nodeType === 1) {\n if (action === 'copy' && target.hasAttribute('disabled')) {\n throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n }\n\n if (action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n }\n } else {\n throw new Error('Invalid \"target\" value, use a valid Element');\n }\n } // Define selection strategy based on `text` property.\n\n\n if (text) {\n return actions_copy(text, {\n container: container\n });\n } // Defines which selection strategy based on `target` property.\n\n\n if (target) {\n return action === 'cut' ? actions_cut(target) : actions_copy(target, {\n container: container\n });\n }\n};\n\n/* harmony default export */ var actions_default = (ClipboardActionDefault);\n;// CONCATENATED MODULE: ./src/clipboard.js\nfunction clipboard_typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return clipboard_typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\nfunction getAttributeValue(suffix, element) {\n var attribute = \"data-clipboard-\".concat(suffix);\n\n if (!element.hasAttribute(attribute)) {\n return;\n }\n\n return element.getAttribute(attribute);\n}\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\n\nvar Clipboard = /*#__PURE__*/function (_Emitter) {\n _inherits(Clipboard, _Emitter);\n\n var _super = _createSuper(Clipboard);\n\n /**\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n * @param {Object} options\n */\n function Clipboard(trigger, options) {\n var _this;\n\n _classCallCheck(this, Clipboard);\n\n _this = _super.call(this);\n\n _this.resolveOptions(options);\n\n _this.listenClick(trigger);\n\n return _this;\n }\n /**\n * Defines if attributes would be resolved using internal setter functions\n * or custom functions that were passed in the constructor.\n * @param {Object} options\n */\n\n\n _createClass(Clipboard, [{\n key: \"resolveOptions\",\n value: function resolveOptions() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n }\n /**\n * Adds a click event listener to the passed trigger.\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n */\n\n }, {\n key: \"listenClick\",\n value: function listenClick(trigger) {\n var _this2 = this;\n\n this.listener = listen_default()(trigger, 'click', function (e) {\n return _this2.onClick(e);\n });\n }\n /**\n * Defines a new `ClipboardAction` on each click event.\n * @param {Event} e\n */\n\n }, {\n key: \"onClick\",\n value: function onClick(e) {\n var trigger = e.delegateTarget || e.currentTarget;\n var action = this.action(trigger) || 'copy';\n var text = actions_default({\n action: action,\n container: this.container,\n target: this.target(trigger),\n text: this.text(trigger)\n }); // Fires an event based on the copy operation result.\n\n this.emit(text ? 'success' : 'error', {\n action: action,\n text: text,\n trigger: trigger,\n clearSelection: function clearSelection() {\n if (trigger) {\n trigger.focus();\n }\n\n window.getSelection().removeAllRanges();\n }\n });\n }\n /**\n * Default `action` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultAction\",\n value: function defaultAction(trigger) {\n return getAttributeValue('action', trigger);\n }\n /**\n * Default `target` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultTarget\",\n value: function defaultTarget(trigger) {\n var selector = getAttributeValue('target', trigger);\n\n if (selector) {\n return document.querySelector(selector);\n }\n }\n /**\n * Allow fire programmatically a copy action\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @returns Text copied.\n */\n\n }, {\n key: \"defaultText\",\n\n /**\n * Default `text` lookup function.\n * @param {Element} trigger\n */\n value: function defaultText(trigger) {\n return getAttributeValue('text', trigger);\n }\n /**\n * Destroy lifecycle.\n */\n\n }, {\n key: \"destroy\",\n value: function destroy() {\n this.listener.destroy();\n }\n }], [{\n key: \"copy\",\n value: function copy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n return actions_copy(target, options);\n }\n /**\n * Allow fire programmatically a cut action\n * @param {String|HTMLElement} target\n * @returns Text cutted.\n */\n\n }, {\n key: \"cut\",\n value: function cut(target) {\n return actions_cut(target);\n }\n /**\n * Returns the support of the given action, or all actions if no action is\n * given.\n * @param {String} [action]\n */\n\n }, {\n key: \"isSupported\",\n value: function isSupported() {\n var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n var actions = typeof action === 'string' ? [action] : action;\n var support = !!document.queryCommandSupported;\n actions.forEach(function (action) {\n support = support && !!document.queryCommandSupported(action);\n });\n return support;\n }\n }]);\n\n return Clipboard;\n}((tiny_emitter_default()));\n\n/* harmony default export */ var clipboard = (Clipboard);\n\n/***/ }),\n\n/***/ 828:\n/***/ (function(module) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n var proto = Element.prototype;\n\n proto.matches = proto.matchesSelector ||\n proto.mozMatchesSelector ||\n proto.msMatchesSelector ||\n proto.oMatchesSelector ||\n proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n if (typeof element.matches === 'function' &&\n element.matches(selector)) {\n return element;\n }\n element = element.parentNode;\n }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n\n/***/ 438:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar closest = __webpack_require__(828);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n var listenerFn = listener.apply(this, arguments);\n\n element.addEventListener(type, listenerFn, useCapture);\n\n return {\n destroy: function() {\n element.removeEventListener(type, listenerFn, useCapture);\n }\n }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n // Handle the regular Element usage\n if (typeof elements.addEventListener === 'function') {\n return _delegate.apply(null, arguments);\n }\n\n // Handle Element-less usage, it defaults to global delegation\n if (typeof type === 'function') {\n // Use `document` as the first parameter, then apply arguments\n // This is a short way to .unshift `arguments` without running into deoptimizations\n return _delegate.bind(null, document).apply(null, arguments);\n }\n\n // Handle Selector-based usage\n if (typeof elements === 'string') {\n elements = document.querySelectorAll(elements);\n }\n\n // Handle Array-like based usage\n return Array.prototype.map.call(elements, function (element) {\n return _delegate(element, selector, type, callback, useCapture);\n });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n return function(e) {\n e.delegateTarget = closest(e.target, selector);\n\n if (e.delegateTarget) {\n callback.call(element, e);\n }\n }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n\n/***/ 879:\n/***/ (function(__unused_webpack_module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n return value !== undefined\n && value instanceof HTMLElement\n && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return value !== undefined\n && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n && ('length' in value)\n && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n return typeof value === 'string'\n || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return type === '[object Function]';\n};\n\n\n/***/ }),\n\n/***/ 370:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar is = __webpack_require__(879);\nvar delegate = __webpack_require__(438);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n if (!target && !type && !callback) {\n throw new Error('Missing required arguments');\n }\n\n if (!is.string(type)) {\n throw new TypeError('Second argument must be a String');\n }\n\n if (!is.fn(callback)) {\n throw new TypeError('Third argument must be a Function');\n }\n\n if (is.node(target)) {\n return listenNode(target, type, callback);\n }\n else if (is.nodeList(target)) {\n return listenNodeList(target, type, callback);\n }\n else if (is.string(target)) {\n return listenSelector(target, type, callback);\n }\n else {\n throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n node.addEventListener(type, callback);\n\n return {\n destroy: function() {\n node.removeEventListener(type, callback);\n }\n }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.addEventListener(type, callback);\n });\n\n return {\n destroy: function() {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.removeEventListener(type, callback);\n });\n }\n }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n\n/***/ 817:\n/***/ (function(module) {\n\nfunction select(element) {\n var selectedText;\n\n if (element.nodeName === 'SELECT') {\n element.focus();\n\n selectedText = element.value;\n }\n else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n var isReadOnly = element.hasAttribute('readonly');\n\n if (!isReadOnly) {\n element.setAttribute('readonly', '');\n }\n\n element.select();\n element.setSelectionRange(0, element.value.length);\n\n if (!isReadOnly) {\n element.removeAttribute('readonly');\n }\n\n selectedText = element.value;\n }\n else {\n if (element.hasAttribute('contenteditable')) {\n element.focus();\n }\n\n var selection = window.getSelection();\n var range = document.createRange();\n\n range.selectNodeContents(element);\n selection.removeAllRanges();\n selection.addRange(range);\n\n selectedText = selection.toString();\n }\n\n return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n\n/***/ 279:\n/***/ (function(module) {\n\nfunction E () {\n // Keep this empty so it's easier to inherit from\n // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n on: function (name, callback, ctx) {\n var e = this.e || (this.e = {});\n\n (e[name] || (e[name] = [])).push({\n fn: callback,\n ctx: ctx\n });\n\n return this;\n },\n\n once: function (name, callback, ctx) {\n var self = this;\n function listener () {\n self.off(name, listener);\n callback.apply(ctx, arguments);\n };\n\n listener._ = callback\n return this.on(name, listener, ctx);\n },\n\n emit: function (name) {\n var data = [].slice.call(arguments, 1);\n var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n var i = 0;\n var len = evtArr.length;\n\n for (i; i < len; i++) {\n evtArr[i].fn.apply(evtArr[i].ctx, data);\n }\n\n return this;\n },\n\n off: function (name, callback) {\n var e = this.e || (this.e = {});\n var evts = e[name];\n var liveEvents = [];\n\n if (evts && callback) {\n for (var i = 0, len = evts.length; i < len; i++) {\n if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n liveEvents.push(evts[i]);\n }\n }\n\n // Remove event from queue to prevent memory leak\n // Suggested by https://github.com/lazd\n // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n (liveEvents.length)\n ? e[name] = liveEvents\n : delete e[name];\n\n return this;\n }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(__webpack_module_cache__[moduleId]) {\n/******/ \t\t\treturn __webpack_module_cache__[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t/* webpack/runtime/compat get default export */\n/******/ \t!function() {\n/******/ \t\t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t\t__webpack_require__.n = function(module) {\n/******/ \t\t\tvar getter = module && module.__esModule ?\n/******/ \t\t\t\tfunction() { return module['default']; } :\n/******/ \t\t\t\tfunction() { return module; };\n/******/ \t\t\t__webpack_require__.d(getter, { a: getter });\n/******/ \t\t\treturn getter;\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/define property getters */\n/******/ \t!function() {\n/******/ \t\t// define getter functions for harmony exports\n/******/ \t\t__webpack_require__.d = function(exports, definition) {\n/******/ \t\t\tfor(var key in definition) {\n/******/ \t\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n/******/ \t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n/******/ \t\t\t\t}\n/******/ \t\t\t}\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/hasOwnProperty shorthand */\n/******/ \t!function() {\n/******/ \t\t__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }\n/******/ \t}();\n/******/ \t\n/************************************************************************/\n/******/ \t// module exports must be returned from runtime so entry inlining is disabled\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(686);\n/******/ })()\n.default;\n});", "/*\n * Copyright (c) 2016-2025 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"focus-visible\"\n\nimport {\n EMPTY,\n NEVER,\n Observable,\n Subject,\n defer,\n delay,\n filter,\n map,\n merge,\n mergeWith,\n shareReplay,\n switchMap\n} from \"rxjs\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n at,\n getActiveElement,\n getOptionalElement,\n requestJSON,\n setLocation,\n setToggle,\n watchDocument,\n watchKeyboard,\n watchLocation,\n watchLocationTarget,\n watchMedia,\n watchPrint,\n watchScript,\n watchViewport\n} from \"./browser\"\nimport {\n getComponentElement,\n getComponentElements,\n mountAnnounce,\n mountBackToTop,\n mountConsent,\n mountContent,\n mountDialog,\n mountHeader,\n mountHeaderTitle,\n mountPalette,\n mountProgress,\n mountSearch,\n mountSearchHiglight,\n mountSidebar,\n mountSource,\n mountTableOfContents,\n mountTabs,\n watchHeader,\n watchMain\n} from \"./components\"\nimport {\n SearchIndex,\n fetchSitemap,\n setupAlternate,\n setupClipboardJS,\n setupInstantNavigation,\n setupVersionSelector\n} from \"./integrations\"\nimport {\n patchEllipsis,\n patchIndeterminate,\n patchScrollfix,\n patchScrolllock\n} from \"./patches\"\nimport \"./polyfills\"\n\n/* ----------------------------------------------------------------------------\n * Functions - @todo refactor\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch search index\n *\n * @returns Search index observable\n */\nfunction fetchSearchIndex(): Observable {\n if (location.protocol === \"file:\") {\n return watchScript(\n `${new URL(\"search/search_index.js\", config.base)}`\n )\n .pipe(\n // @ts-ignore - @todo fix typings\n map(() => __index),\n shareReplay(1)\n )\n } else {\n return requestJSON(\n new URL(\"search/search_index.json\", config.base)\n )\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$ = watchLocationTarget(location$)\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$ = watchMedia(\"(min-width: 60em)\")\nconst screen$ = watchMedia(\"(min-width: 76.25em)\")\nconst print$ = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n ? fetchSearchIndex()\n : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject()\nsetupClipboardJS({ alert$ })\n\n/* Set up language selector */\nsetupAlternate({ document$ })\n\n/* Set up progress indicator */\nconst progress$ = new Subject()\n\n/* Set up sitemap for instant navigation and previews */\nconst sitemap$ = fetchSitemap(config.base)\n\n/* Set up instant navigation, if enabled */\nif (feature(\"navigation.instant\"))\n setupInstantNavigation({ sitemap$, location$, viewport$, progress$ })\n .subscribe(document$)\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n setupVersionSelector({ document$ })\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n .pipe(\n delay(125)\n )\n .subscribe(() => {\n setToggle(\"drawer\", false)\n setToggle(\"search\", false)\n })\n\n/* Set up global keyboard handlers */\nkeyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\")\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Go to previous page */\n case \"p\":\n case \",\":\n const prev = getOptionalElement(\"link[rel=prev]\")\n if (typeof prev !== \"undefined\")\n setLocation(prev)\n break\n\n /* Go to next page */\n case \"n\":\n case \".\":\n const next = getOptionalElement(\"link[rel=next]\")\n if (typeof next !== \"undefined\")\n setLocation(next)\n break\n\n /* Expand navigation, see https://bit.ly/3ZjG5io */\n case \"Enter\":\n const active = getActiveElement()\n if (active instanceof HTMLLabelElement)\n active.click()\n }\n })\n\n/* Set up patches */\npatchEllipsis({ viewport$, document$ })\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n .pipe(\n map(() => getComponentElement(\"main\")),\n switchMap(el => watchMain(el, { viewport$, header$ })),\n shareReplay(1)\n )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n /* Consent */\n ...getComponentElements(\"consent\")\n .map(el => mountConsent(el, { target$ })),\n\n /* Dialog */\n ...getComponentElements(\"dialog\")\n .map(el => mountDialog(el, { alert$ })),\n\n /* Color palette */\n ...getComponentElements(\"palette\")\n .map(el => mountPalette(el)),\n\n /* Progress bar */\n ...getComponentElements(\"progress\")\n .map(el => mountProgress(el, { progress$ })),\n\n /* Search */\n ...getComponentElements(\"search\")\n .map(el => mountSearch(el, { index$, keyboard$ })),\n\n /* Repository information */\n ...getComponentElements(\"source\")\n .map(el => mountSource(el))\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n /* Announcement bar */\n ...getComponentElements(\"announce\")\n .map(el => mountAnnounce(el)),\n\n /* Content */\n ...getComponentElements(\"content\")\n .map(el => mountContent(el, { sitemap$, viewport$, target$, print$ })),\n\n /* Search highlighting */\n ...getComponentElements(\"content\")\n .map(el => feature(\"search.highlight\")\n ? mountSearchHiglight(el, { index$, location$ })\n : EMPTY\n ),\n\n /* Header */\n ...getComponentElements(\"header\")\n .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n /* Header title */\n ...getComponentElements(\"header-title\")\n .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n /* Sidebar */\n ...getComponentElements(\"sidebar\")\n .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n ),\n\n /* Navigation tabs */\n ...getComponentElements(\"tabs\")\n .map(el => mountTabs(el, { viewport$, header$ })),\n\n /* Table of contents */\n ...getComponentElements(\"toc\")\n .map(el => mountTableOfContents(el, {\n viewport$, header$, main$, target$\n })),\n\n /* Back-to-top button */\n ...getComponentElements(\"top\")\n .map(el => mountBackToTop(el, { viewport$, header$, main$, target$ }))\n))\n\n/* Set up component observables */\nconst component$ = document$\n .pipe(\n switchMap(() => content$),\n mergeWith(control$),\n shareReplay(1)\n )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$ = document$ /* Document observable */\nwindow.location$ = location$ /* Location subject */\nwindow.target$ = target$ /* Location target observable */\nwindow.keyboard$ = keyboard$ /* Keyboard observable */\nwindow.viewport$ = viewport$ /* Viewport observable */\nwindow.tablet$ = tablet$ /* Media tablet observable */\nwindow.screen$ = screen$ /* Media screen observable */\nwindow.print$ = print$ /* Media print observable */\nwindow.alert$ = alert$ /* Alert subject */\nwindow.progress$ = progress$ /* Progress indicator subject */\nwindow.component$ = component$ /* Component observable */\n", "/******************************************************************************\nCopyright (c) Microsoft Corporation.\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n***************************************************************************** */\n/* global Reflect, Promise, SuppressedError, Symbol, Iterator */\n\nvar extendStatics = function(d, b) {\n extendStatics = Object.setPrototypeOf ||\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\n return extendStatics(d, b);\n};\n\nexport function __extends(d, b) {\n if (typeof b !== \"function\" && b !== null)\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\n extendStatics(d, b);\n function __() { this.constructor = d; }\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\n}\n\nexport var __assign = function() {\n __assign = Object.assign || function __assign(t) {\n for (var s, i = 1, n = arguments.length; i < n; i++) {\n s = arguments[i];\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\n }\n return t;\n }\n return __assign.apply(this, arguments);\n}\n\nexport function __rest(s, e) {\n var t = {};\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\n t[p] = s[p];\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\n t[p[i]] = s[p[i]];\n }\n return t;\n}\n\nexport function __decorate(decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n}\n\nexport function __param(paramIndex, decorator) {\n return function (target, key) { decorator(target, key, paramIndex); }\n}\n\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\n var _, done = false;\n for (var i = decorators.length - 1; i >= 0; i--) {\n var context = {};\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\n context.addInitializer = function (f) { if (done) throw new TypeError(\"Cannot add initializers after decoration has completed\"); extraInitializers.push(accept(f || null)); };\n var result = (0, decorators[i])(kind === \"accessor\" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);\n if (kind === \"accessor\") {\n if (result === void 0) continue;\n if (result === null || typeof result !== \"object\") throw new TypeError(\"Object expected\");\n if (_ = accept(result.get)) descriptor.get = _;\n if (_ = accept(result.set)) descriptor.set = _;\n if (_ = accept(result.init)) initializers.unshift(_);\n }\n else if (_ = accept(result)) {\n if (kind === \"field\") initializers.unshift(_);\n else descriptor[key] = _;\n }\n }\n if (target) Object.defineProperty(target, contextIn.name, descriptor);\n done = true;\n};\n\nexport function __runInitializers(thisArg, initializers, value) {\n var useValue = arguments.length > 2;\n for (var i = 0; i < initializers.length; i++) {\n value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);\n }\n return useValue ? value : void 0;\n};\n\nexport function __propKey(x) {\n return typeof x === \"symbol\" ? x : \"\".concat(x);\n};\n\nexport function __setFunctionName(f, name, prefix) {\n if (typeof name === \"symbol\") name = name.description ? \"[\".concat(name.description, \"]\") : \"\";\n return Object.defineProperty(f, \"name\", { configurable: true, value: prefix ? \"\".concat(prefix, \" \", name) : name });\n};\n\nexport function __metadata(metadataKey, metadataValue) {\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\n}\n\nexport function __awaiter(thisArg, _arguments, P, generator) {\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n}\n\nexport function __generator(thisArg, body) {\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === \"function\" ? Iterator : Object).prototype);\n return g.next = verb(0), g[\"throw\"] = verb(1), g[\"return\"] = verb(2), typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\n function verb(n) { return function (v) { return step([n, v]); }; }\n function step(op) {\n if (f) throw new TypeError(\"Generator is already executing.\");\n while (g && (g = 0, op[0] && (_ = 0)), _) try {\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\n if (y = 0, t) op = [op[0] & 2, t.value];\n switch (op[0]) {\n case 0: case 1: t = op; break;\n case 4: _.label++; return { value: op[1], done: false };\n case 5: _.label++; y = op[1]; op = [0]; continue;\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\n default:\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\n if (t[2]) _.ops.pop();\n _.trys.pop(); continue;\n }\n op = body.call(thisArg, _);\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\n }\n}\n\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n var desc = Object.getOwnPropertyDescriptor(m, k);\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\n desc = { enumerable: true, get: function() { return m[k]; } };\n }\n Object.defineProperty(o, k2, desc);\n}) : (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n o[k2] = m[k];\n});\n\nexport function __exportStar(m, o) {\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\n}\n\nexport function __values(o) {\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\n if (m) return m.call(o);\n if (o && typeof o.length === \"number\") return {\n next: function () {\n if (o && i >= o.length) o = void 0;\n return { value: o && o[i++], done: !o };\n }\n };\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\n}\n\nexport function __read(o, n) {\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\n if (!m) return o;\n var i = m.call(o), r, ar = [], e;\n try {\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\n }\n catch (error) { e = { error: error }; }\n finally {\n try {\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\n }\n finally { if (e) throw e.error; }\n }\n return ar;\n}\n\n/** @deprecated */\nexport function __spread() {\n for (var ar = [], i = 0; i < arguments.length; i++)\n ar = ar.concat(__read(arguments[i]));\n return ar;\n}\n\n/** @deprecated */\nexport function __spreadArrays() {\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\n r[k] = a[j];\n return r;\n}\n\nexport function __spreadArray(to, from, pack) {\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\n if (ar || !(i in from)) {\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\n ar[i] = from[i];\n }\n }\n return to.concat(ar || Array.prototype.slice.call(from));\n}\n\nexport function __await(v) {\n return this instanceof __await ? (this.v = v, this) : new __await(v);\n}\n\nexport function __asyncGenerator(thisArg, _arguments, generator) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\n return i = Object.create((typeof AsyncIterator === \"function\" ? AsyncIterator : Object).prototype), verb(\"next\"), verb(\"throw\"), verb(\"return\", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;\n function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }\n function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\n function fulfill(value) { resume(\"next\", value); }\n function reject(value) { resume(\"throw\", value); }\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\n}\n\nexport function __asyncDelegator(o) {\n var i, p;\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }\n}\n\nexport function __asyncValues(o) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var m = o[Symbol.asyncIterator], i;\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\n}\n\nexport function __makeTemplateObject(cooked, raw) {\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\n return cooked;\n};\n\nvar __setModuleDefault = Object.create ? (function(o, v) {\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\n}) : function(o, v) {\n o[\"default\"] = v;\n};\n\nexport function __importStar(mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\n __setModuleDefault(result, mod);\n return result;\n}\n\nexport function __importDefault(mod) {\n return (mod && mod.__esModule) ? mod : { default: mod };\n}\n\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\n}\n\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\n}\n\nexport function __classPrivateFieldIn(state, receiver) {\n if (receiver === null || (typeof receiver !== \"object\" && typeof receiver !== \"function\")) throw new TypeError(\"Cannot use 'in' operator on non-object\");\n return typeof state === \"function\" ? receiver === state : state.has(receiver);\n}\n\nexport function __addDisposableResource(env, value, async) {\n if (value !== null && value !== void 0) {\n if (typeof value !== \"object\" && typeof value !== \"function\") throw new TypeError(\"Object expected.\");\n var dispose, inner;\n if (async) {\n if (!Symbol.asyncDispose) throw new TypeError(\"Symbol.asyncDispose is not defined.\");\n dispose = value[Symbol.asyncDispose];\n }\n if (dispose === void 0) {\n if (!Symbol.dispose) throw new TypeError(\"Symbol.dispose is not defined.\");\n dispose = value[Symbol.dispose];\n if (async) inner = dispose;\n }\n if (typeof dispose !== \"function\") throw new TypeError(\"Object not disposable.\");\n if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };\n env.stack.push({ value: value, dispose: dispose, async: async });\n }\n else if (async) {\n env.stack.push({ async: true });\n }\n return value;\n}\n\nvar _SuppressedError = typeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\n var e = new Error(message);\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\n};\n\nexport function __disposeResources(env) {\n function fail(e) {\n env.error = env.hasError ? new _SuppressedError(e, env.error, \"An error was suppressed during disposal.\") : e;\n env.hasError = true;\n }\n var r, s = 0;\n function next() {\n while (r = env.stack.pop()) {\n try {\n if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);\n if (r.dispose) {\n var result = r.dispose.call(r.value);\n if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });\n }\n else s |= 1;\n }\n catch (e) {\n fail(e);\n }\n }\n if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();\n if (env.hasError) throw env.error;\n }\n return next();\n}\n\nexport default {\n __extends,\n __assign,\n __rest,\n __decorate,\n __param,\n __metadata,\n __awaiter,\n __generator,\n __createBinding,\n __exportStar,\n __values,\n __read,\n __spread,\n __spreadArrays,\n __spreadArray,\n __await,\n __asyncGenerator,\n __asyncDelegator,\n __asyncValues,\n __makeTemplateObject,\n __importStar,\n __importDefault,\n __classPrivateFieldGet,\n __classPrivateFieldSet,\n __classPrivateFieldIn,\n __addDisposableResource,\n __disposeResources,\n};\n", "/**\n * Returns true if the object is a function.\n * @param value The value to check\n */\nexport function isFunction(value: any): value is (...args: any[]) => any {\n return typeof value === 'function';\n}\n", "/**\n * Used to create Error subclasses until the community moves away from ES5.\n *\n * This is because compiling from TypeScript down to ES5 has issues with subclassing Errors\n * as well as other built-in types: https://github.com/Microsoft/TypeScript/issues/12123\n *\n * @param createImpl A factory function to create the actual constructor implementation. The returned\n * function should be a named function that calls `_super` internally.\n */\nexport function createErrorClass(createImpl: (_super: any) => any): T {\n const _super = (instance: any) => {\n Error.call(instance);\n instance.stack = new Error().stack;\n };\n\n const ctorFunc = createImpl(_super);\n ctorFunc.prototype = Object.create(Error.prototype);\n ctorFunc.prototype.constructor = ctorFunc;\n return ctorFunc;\n}\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface UnsubscriptionError extends Error {\n readonly errors: any[];\n}\n\nexport interface UnsubscriptionErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (errors: any[]): UnsubscriptionError;\n}\n\n/**\n * An error thrown when one or more errors have occurred during the\n * `unsubscribe` of a {@link Subscription}.\n */\nexport const UnsubscriptionError: UnsubscriptionErrorCtor = createErrorClass(\n (_super) =>\n function UnsubscriptionErrorImpl(this: any, errors: (Error | string)[]) {\n _super(this);\n this.message = errors\n ? `${errors.length} errors occurred during unsubscription:\n${errors.map((err, i) => `${i + 1}) ${err.toString()}`).join('\\n ')}`\n : '';\n this.name = 'UnsubscriptionError';\n this.errors = errors;\n }\n);\n", "/**\n * Removes an item from an array, mutating it.\n * @param arr The array to remove the item from\n * @param item The item to remove\n */\nexport function arrRemove(arr: T[] | undefined | null, item: T) {\n if (arr) {\n const index = arr.indexOf(item);\n 0 <= index && arr.splice(index, 1);\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { UnsubscriptionError } from './util/UnsubscriptionError';\nimport { SubscriptionLike, TeardownLogic, Unsubscribable } from './types';\nimport { arrRemove } from './util/arrRemove';\n\n/**\n * Represents a disposable resource, such as the execution of an Observable. A\n * Subscription has one important method, `unsubscribe`, that takes no argument\n * and just disposes the resource held by the subscription.\n *\n * Additionally, subscriptions may be grouped together through the `add()`\n * method, which will attach a child Subscription to the current Subscription.\n * When a Subscription is unsubscribed, all its children (and its grandchildren)\n * will be unsubscribed as well.\n */\nexport class Subscription implements SubscriptionLike {\n public static EMPTY = (() => {\n const empty = new Subscription();\n empty.closed = true;\n return empty;\n })();\n\n /**\n * A flag to indicate whether this Subscription has already been unsubscribed.\n */\n public closed = false;\n\n private _parentage: Subscription[] | Subscription | null = null;\n\n /**\n * The list of registered finalizers to execute upon unsubscription. Adding and removing from this\n * list occurs in the {@link #add} and {@link #remove} methods.\n */\n private _finalizers: Exclude[] | null = null;\n\n /**\n * @param initialTeardown A function executed first as part of the finalization\n * process that is kicked off when {@link #unsubscribe} is called.\n */\n constructor(private initialTeardown?: () => void) {}\n\n /**\n * Disposes the resources held by the subscription. May, for instance, cancel\n * an ongoing Observable execution or cancel any other type of work that\n * started when the Subscription was created.\n */\n unsubscribe(): void {\n let errors: any[] | undefined;\n\n if (!this.closed) {\n this.closed = true;\n\n // Remove this from it's parents.\n const { _parentage } = this;\n if (_parentage) {\n this._parentage = null;\n if (Array.isArray(_parentage)) {\n for (const parent of _parentage) {\n parent.remove(this);\n }\n } else {\n _parentage.remove(this);\n }\n }\n\n const { initialTeardown: initialFinalizer } = this;\n if (isFunction(initialFinalizer)) {\n try {\n initialFinalizer();\n } catch (e) {\n errors = e instanceof UnsubscriptionError ? e.errors : [e];\n }\n }\n\n const { _finalizers } = this;\n if (_finalizers) {\n this._finalizers = null;\n for (const finalizer of _finalizers) {\n try {\n execFinalizer(finalizer);\n } catch (err) {\n errors = errors ?? [];\n if (err instanceof UnsubscriptionError) {\n errors = [...errors, ...err.errors];\n } else {\n errors.push(err);\n }\n }\n }\n }\n\n if (errors) {\n throw new UnsubscriptionError(errors);\n }\n }\n }\n\n /**\n * Adds a finalizer to this subscription, so that finalization will be unsubscribed/called\n * when this subscription is unsubscribed. If this subscription is already {@link #closed},\n * because it has already been unsubscribed, then whatever finalizer is passed to it\n * will automatically be executed (unless the finalizer itself is also a closed subscription).\n *\n * Closed Subscriptions cannot be added as finalizers to any subscription. Adding a closed\n * subscription to a any subscription will result in no operation. (A noop).\n *\n * Adding a subscription to itself, or adding `null` or `undefined` will not perform any\n * operation at all. (A noop).\n *\n * `Subscription` instances that are added to this instance will automatically remove themselves\n * if they are unsubscribed. Functions and {@link Unsubscribable} objects that you wish to remove\n * will need to be removed manually with {@link #remove}\n *\n * @param teardown The finalization logic to add to this subscription.\n */\n add(teardown: TeardownLogic): void {\n // Only add the finalizer if it's not undefined\n // and don't add a subscription to itself.\n if (teardown && teardown !== this) {\n if (this.closed) {\n // If this subscription is already closed,\n // execute whatever finalizer is handed to it automatically.\n execFinalizer(teardown);\n } else {\n if (teardown instanceof Subscription) {\n // We don't add closed subscriptions, and we don't add the same subscription\n // twice. Subscription unsubscribe is idempotent.\n if (teardown.closed || teardown._hasParent(this)) {\n return;\n }\n teardown._addParent(this);\n }\n (this._finalizers = this._finalizers ?? []).push(teardown);\n }\n }\n }\n\n /**\n * Checks to see if a this subscription already has a particular parent.\n * This will signal that this subscription has already been added to the parent in question.\n * @param parent the parent to check for\n */\n private _hasParent(parent: Subscription) {\n const { _parentage } = this;\n return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent));\n }\n\n /**\n * Adds a parent to this subscription so it can be removed from the parent if it\n * unsubscribes on it's own.\n *\n * NOTE: THIS ASSUMES THAT {@link _hasParent} HAS ALREADY BEEN CHECKED.\n * @param parent The parent subscription to add\n */\n private _addParent(parent: Subscription) {\n const { _parentage } = this;\n this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;\n }\n\n /**\n * Called on a child when it is removed via {@link #remove}.\n * @param parent The parent to remove\n */\n private _removeParent(parent: Subscription) {\n const { _parentage } = this;\n if (_parentage === parent) {\n this._parentage = null;\n } else if (Array.isArray(_parentage)) {\n arrRemove(_parentage, parent);\n }\n }\n\n /**\n * Removes a finalizer from this subscription that was previously added with the {@link #add} method.\n *\n * Note that `Subscription` instances, when unsubscribed, will automatically remove themselves\n * from every other `Subscription` they have been added to. This means that using the `remove` method\n * is not a common thing and should be used thoughtfully.\n *\n * If you add the same finalizer instance of a function or an unsubscribable object to a `Subscription` instance\n * more than once, you will need to call `remove` the same number of times to remove all instances.\n *\n * All finalizer instances are removed to free up memory upon unsubscription.\n *\n * @param teardown The finalizer to remove from this subscription\n */\n remove(teardown: Exclude): void {\n const { _finalizers } = this;\n _finalizers && arrRemove(_finalizers, teardown);\n\n if (teardown instanceof Subscription) {\n teardown._removeParent(this);\n }\n }\n}\n\nexport const EMPTY_SUBSCRIPTION = Subscription.EMPTY;\n\nexport function isSubscription(value: any): value is Subscription {\n return (\n value instanceof Subscription ||\n (value && 'closed' in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe))\n );\n}\n\nfunction execFinalizer(finalizer: Unsubscribable | (() => void)) {\n if (isFunction(finalizer)) {\n finalizer();\n } else {\n finalizer.unsubscribe();\n }\n}\n", "import { Subscriber } from './Subscriber';\nimport { ObservableNotification } from './types';\n\n/**\n * The {@link GlobalConfig} object for RxJS. It is used to configure things\n * like how to react on unhandled errors.\n */\nexport const config: GlobalConfig = {\n onUnhandledError: null,\n onStoppedNotification: null,\n Promise: undefined,\n useDeprecatedSynchronousErrorHandling: false,\n useDeprecatedNextContext: false,\n};\n\n/**\n * The global configuration object for RxJS, used to configure things\n * like how to react on unhandled errors. Accessible via {@link config}\n * object.\n */\nexport interface GlobalConfig {\n /**\n * A registration point for unhandled errors from RxJS. These are errors that\n * cannot were not handled by consuming code in the usual subscription path. For\n * example, if you have this configured, and you subscribe to an observable without\n * providing an error handler, errors from that subscription will end up here. This\n * will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onUnhandledError: ((err: any) => void) | null;\n\n /**\n * A registration point for notifications that cannot be sent to subscribers because they\n * have completed, errored or have been explicitly unsubscribed. By default, next, complete\n * and error notifications sent to stopped subscribers are noops. However, sometimes callers\n * might want a different behavior. For example, with sources that attempt to report errors\n * to stopped subscribers, a caller can configure RxJS to throw an unhandled error instead.\n * This will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onStoppedNotification: ((notification: ObservableNotification, subscriber: Subscriber) => void) | null;\n\n /**\n * The promise constructor used by default for {@link Observable#toPromise toPromise} and {@link Observable#forEach forEach}\n * methods.\n *\n * @deprecated As of version 8, RxJS will no longer support this sort of injection of a\n * Promise constructor. If you need a Promise implementation other than native promises,\n * please polyfill/patch Promise as you see appropriate. Will be removed in v8.\n */\n Promise?: PromiseConstructorLike;\n\n /**\n * If true, turns on synchronous error rethrowing, which is a deprecated behavior\n * in v6 and higher. This behavior enables bad patterns like wrapping a subscribe\n * call in a try/catch block. It also enables producer interference, a nasty bug\n * where a multicast can be broken for all observers by a downstream consumer with\n * an unhandled error. DO NOT USE THIS FLAG UNLESS IT'S NEEDED TO BUY TIME\n * FOR MIGRATION REASONS.\n *\n * @deprecated As of version 8, RxJS will no longer support synchronous throwing\n * of unhandled errors. All errors will be thrown on a separate call stack to prevent bad\n * behaviors described above. Will be removed in v8.\n */\n useDeprecatedSynchronousErrorHandling: boolean;\n\n /**\n * If true, enables an as-of-yet undocumented feature from v5: The ability to access\n * `unsubscribe()` via `this` context in `next` functions created in observers passed\n * to `subscribe`.\n *\n * This is being removed because the performance was severely problematic, and it could also cause\n * issues when types other than POJOs are passed to subscribe as subscribers, as they will likely have\n * their `this` context overwritten.\n *\n * @deprecated As of version 8, RxJS will no longer support altering the\n * context of next functions provided as part of an observer to Subscribe. Instead,\n * you will have access to a subscription or a signal or token that will allow you to do things like\n * unsubscribe and test closed status. Will be removed in v8.\n */\n useDeprecatedNextContext: boolean;\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetTimeoutFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearTimeoutFunction = (handle: TimerHandle) => void;\n\ninterface TimeoutProvider {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n delegate:\n | {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n }\n | undefined;\n}\n\nexport const timeoutProvider: TimeoutProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setTimeout(handler: () => void, timeout?: number, ...args) {\n const { delegate } = timeoutProvider;\n if (delegate?.setTimeout) {\n return delegate.setTimeout(handler, timeout, ...args);\n }\n return setTimeout(handler, timeout, ...args);\n },\n clearTimeout(handle) {\n const { delegate } = timeoutProvider;\n return (delegate?.clearTimeout || clearTimeout)(handle as any);\n },\n delegate: undefined,\n};\n", "import { config } from '../config';\nimport { timeoutProvider } from '../scheduler/timeoutProvider';\n\n/**\n * Handles an error on another job either with the user-configured {@link onUnhandledError},\n * or by throwing it on that new job so it can be picked up by `window.onerror`, `process.on('error')`, etc.\n *\n * This should be called whenever there is an error that is out-of-band with the subscription\n * or when an error hits a terminal boundary of the subscription and no error handler was provided.\n *\n * @param err the error to report\n */\nexport function reportUnhandledError(err: any) {\n timeoutProvider.setTimeout(() => {\n const { onUnhandledError } = config;\n if (onUnhandledError) {\n // Execute the user-configured error handler.\n onUnhandledError(err);\n } else {\n // Throw so it is picked up by the runtime's uncaught error mechanism.\n throw err;\n }\n });\n}\n", "/* tslint:disable:no-empty */\nexport function noop() { }\n", "import { CompleteNotification, NextNotification, ErrorNotification } from './types';\n\n/**\n * A completion object optimized for memory use and created to be the\n * same \"shape\" as other notifications in v8.\n * @internal\n */\nexport const COMPLETE_NOTIFICATION = (() => createNotification('C', undefined, undefined) as CompleteNotification)();\n\n/**\n * Internal use only. Creates an optimized error notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function errorNotification(error: any): ErrorNotification {\n return createNotification('E', undefined, error) as any;\n}\n\n/**\n * Internal use only. Creates an optimized next notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function nextNotification(value: T) {\n return createNotification('N', value, undefined) as NextNotification;\n}\n\n/**\n * Ensures that all notifications created internally have the same \"shape\" in v8.\n *\n * TODO: This is only exported to support a crazy legacy test in `groupBy`.\n * @internal\n */\nexport function createNotification(kind: 'N' | 'E' | 'C', value: any, error: any) {\n return {\n kind,\n value,\n error,\n };\n}\n", "import { config } from '../config';\n\nlet context: { errorThrown: boolean; error: any } | null = null;\n\n/**\n * Handles dealing with errors for super-gross mode. Creates a context, in which\n * any synchronously thrown errors will be passed to {@link captureError}. Which\n * will record the error such that it will be rethrown after the call back is complete.\n * TODO: Remove in v8\n * @param cb An immediately executed function.\n */\nexport function errorContext(cb: () => void) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n const isRoot = !context;\n if (isRoot) {\n context = { errorThrown: false, error: null };\n }\n cb();\n if (isRoot) {\n const { errorThrown, error } = context!;\n context = null;\n if (errorThrown) {\n throw error;\n }\n }\n } else {\n // This is the general non-deprecated path for everyone that\n // isn't crazy enough to use super-gross mode (useDeprecatedSynchronousErrorHandling)\n cb();\n }\n}\n\n/**\n * Captures errors only in super-gross mode.\n * @param err the error to capture\n */\nexport function captureError(err: any) {\n if (config.useDeprecatedSynchronousErrorHandling && context) {\n context.errorThrown = true;\n context.error = err;\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { Observer, ObservableNotification } from './types';\nimport { isSubscription, Subscription } from './Subscription';\nimport { config } from './config';\nimport { reportUnhandledError } from './util/reportUnhandledError';\nimport { noop } from './util/noop';\nimport { nextNotification, errorNotification, COMPLETE_NOTIFICATION } from './NotificationFactories';\nimport { timeoutProvider } from './scheduler/timeoutProvider';\nimport { captureError } from './util/errorContext';\n\n/**\n * Implements the {@link Observer} interface and extends the\n * {@link Subscription} class. While the {@link Observer} is the public API for\n * consuming the values of an {@link Observable}, all Observers get converted to\n * a Subscriber, in order to provide Subscription-like capabilities such as\n * `unsubscribe`. Subscriber is a common type in RxJS, and crucial for\n * implementing operators, but it is rarely used as a public API.\n */\nexport class Subscriber extends Subscription implements Observer {\n /**\n * A static factory for a Subscriber, given a (potentially partial) definition\n * of an Observer.\n * @param next The `next` callback of an Observer.\n * @param error The `error` callback of an\n * Observer.\n * @param complete The `complete` callback of an\n * Observer.\n * @return A Subscriber wrapping the (partially defined)\n * Observer represented by the given arguments.\n * @deprecated Do not use. Will be removed in v8. There is no replacement for this\n * method, and there is no reason to be creating instances of `Subscriber` directly.\n * If you have a specific use case, please file an issue.\n */\n static create(next?: (x?: T) => void, error?: (e?: any) => void, complete?: () => void): Subscriber {\n return new SafeSubscriber(next, error, complete);\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected isStopped: boolean = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected destination: Subscriber | Observer; // this `any` is the escape hatch to erase extra type param (e.g. R)\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * There is no reason to directly create an instance of Subscriber. This type is exported for typings reasons.\n */\n constructor(destination?: Subscriber | Observer) {\n super();\n if (destination) {\n this.destination = destination;\n // Automatically chain subscriptions together here.\n // if destination is a Subscription, then it is a Subscriber.\n if (isSubscription(destination)) {\n destination.add(this);\n }\n } else {\n this.destination = EMPTY_OBSERVER;\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `next` from\n * the Observable, with a value. The Observable may call this method 0 or more\n * times.\n * @param value The `next` value.\n */\n next(value: T): void {\n if (this.isStopped) {\n handleStoppedNotification(nextNotification(value), this);\n } else {\n this._next(value!);\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `error` from\n * the Observable, with an attached `Error`. Notifies the Observer that\n * the Observable has experienced an error condition.\n * @param err The `error` exception.\n */\n error(err?: any): void {\n if (this.isStopped) {\n handleStoppedNotification(errorNotification(err), this);\n } else {\n this.isStopped = true;\n this._error(err);\n }\n }\n\n /**\n * The {@link Observer} callback to receive a valueless notification of type\n * `complete` from the Observable. Notifies the Observer that the Observable\n * has finished sending push-based notifications.\n */\n complete(): void {\n if (this.isStopped) {\n handleStoppedNotification(COMPLETE_NOTIFICATION, this);\n } else {\n this.isStopped = true;\n this._complete();\n }\n }\n\n unsubscribe(): void {\n if (!this.closed) {\n this.isStopped = true;\n super.unsubscribe();\n this.destination = null!;\n }\n }\n\n protected _next(value: T): void {\n this.destination.next(value);\n }\n\n protected _error(err: any): void {\n try {\n this.destination.error(err);\n } finally {\n this.unsubscribe();\n }\n }\n\n protected _complete(): void {\n try {\n this.destination.complete();\n } finally {\n this.unsubscribe();\n }\n }\n}\n\n/**\n * This bind is captured here because we want to be able to have\n * compatibility with monoid libraries that tend to use a method named\n * `bind`. In particular, a library called Monio requires this.\n */\nconst _bind = Function.prototype.bind;\n\nfunction bind any>(fn: Fn, thisArg: any): Fn {\n return _bind.call(fn, thisArg);\n}\n\n/**\n * Internal optimization only, DO NOT EXPOSE.\n * @internal\n */\nclass ConsumerObserver implements Observer {\n constructor(private partialObserver: Partial>) {}\n\n next(value: T): void {\n const { partialObserver } = this;\n if (partialObserver.next) {\n try {\n partialObserver.next(value);\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n\n error(err: any): void {\n const { partialObserver } = this;\n if (partialObserver.error) {\n try {\n partialObserver.error(err);\n } catch (error) {\n handleUnhandledError(error);\n }\n } else {\n handleUnhandledError(err);\n }\n }\n\n complete(): void {\n const { partialObserver } = this;\n if (partialObserver.complete) {\n try {\n partialObserver.complete();\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n}\n\nexport class SafeSubscriber extends Subscriber {\n constructor(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((e?: any) => void) | null,\n complete?: (() => void) | null\n ) {\n super();\n\n let partialObserver: Partial>;\n if (isFunction(observerOrNext) || !observerOrNext) {\n // The first argument is a function, not an observer. The next\n // two arguments *could* be observers, or they could be empty.\n partialObserver = {\n next: (observerOrNext ?? undefined) as ((value: T) => void) | undefined,\n error: error ?? undefined,\n complete: complete ?? undefined,\n };\n } else {\n // The first argument is a partial observer.\n let context: any;\n if (this && config.useDeprecatedNextContext) {\n // This is a deprecated path that made `this.unsubscribe()` available in\n // next handler functions passed to subscribe. This only exists behind a flag\n // now, as it is *very* slow.\n context = Object.create(observerOrNext);\n context.unsubscribe = () => this.unsubscribe();\n partialObserver = {\n next: observerOrNext.next && bind(observerOrNext.next, context),\n error: observerOrNext.error && bind(observerOrNext.error, context),\n complete: observerOrNext.complete && bind(observerOrNext.complete, context),\n };\n } else {\n // The \"normal\" path. Just use the partial observer directly.\n partialObserver = observerOrNext;\n }\n }\n\n // Wrap the partial observer to ensure it's a full observer, and\n // make sure proper error handling is accounted for.\n this.destination = new ConsumerObserver(partialObserver);\n }\n}\n\nfunction handleUnhandledError(error: any) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n captureError(error);\n } else {\n // Ideal path, we report this as an unhandled error,\n // which is thrown on a new call stack.\n reportUnhandledError(error);\n }\n}\n\n/**\n * An error handler used when no error handler was supplied\n * to the SafeSubscriber -- meaning no error handler was supplied\n * do the `subscribe` call on our observable.\n * @param err The error to handle\n */\nfunction defaultErrorHandler(err: any) {\n throw err;\n}\n\n/**\n * A handler for notifications that cannot be sent to a stopped subscriber.\n * @param notification The notification being sent.\n * @param subscriber The stopped subscriber.\n */\nfunction handleStoppedNotification(notification: ObservableNotification, subscriber: Subscriber) {\n const { onStoppedNotification } = config;\n onStoppedNotification && timeoutProvider.setTimeout(() => onStoppedNotification(notification, subscriber));\n}\n\n/**\n * The observer used as a stub for subscriptions where the user did not\n * pass any arguments to `subscribe`. Comes with the default error handling\n * behavior.\n */\nexport const EMPTY_OBSERVER: Readonly> & { closed: true } = {\n closed: true,\n next: noop,\n error: defaultErrorHandler,\n complete: noop,\n};\n", "/**\n * Symbol.observable or a string \"@@observable\". Used for interop\n *\n * @deprecated We will no longer be exporting this symbol in upcoming versions of RxJS.\n * Instead polyfill and use Symbol.observable directly *or* use https://www.npmjs.com/package/symbol-observable\n */\nexport const observable: string | symbol = (() => (typeof Symbol === 'function' && Symbol.observable) || '@@observable')();\n", "/**\n * This function takes one parameter and just returns it. Simply put,\n * this is like `(x: T): T => x`.\n *\n * ## Examples\n *\n * This is useful in some cases when using things like `mergeMap`\n *\n * ```ts\n * import { interval, take, map, range, mergeMap, identity } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(5));\n *\n * const result$ = source$.pipe(\n * map(i => range(i)),\n * mergeMap(identity) // same as mergeMap(x => x)\n * );\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * Or when you want to selectively apply an operator\n *\n * ```ts\n * import { interval, take, identity } from 'rxjs';\n *\n * const shouldLimit = () => Math.random() < 0.5;\n *\n * const source$ = interval(1000);\n *\n * const result$ = source$.pipe(shouldLimit() ? take(5) : identity);\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * @param x Any value that is returned by this function\n * @returns The value passed as the first parameter to this function\n */\nexport function identity(x: T): T {\n return x;\n}\n", "import { identity } from './identity';\nimport { UnaryFunction } from '../types';\n\nexport function pipe(): typeof identity;\nexport function pipe(fn1: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction, fn3: UnaryFunction): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction,\n ...fns: UnaryFunction[]\n): UnaryFunction;\n\n/**\n * pipe() can be called on one or more functions, each of which can take one argument (\"UnaryFunction\")\n * and uses it to return a value.\n * It returns a function that takes one argument, passes it to the first UnaryFunction, and then\n * passes the result to the next one, passes that result to the next one, and so on. \n */\nexport function pipe(...fns: Array>): UnaryFunction {\n return pipeFromArray(fns);\n}\n\n/** @internal */\nexport function pipeFromArray(fns: Array>): UnaryFunction {\n if (fns.length === 0) {\n return identity as UnaryFunction;\n }\n\n if (fns.length === 1) {\n return fns[0];\n }\n\n return function piped(input: T): R {\n return fns.reduce((prev: any, fn: UnaryFunction) => fn(prev), input as any);\n };\n}\n", "import { Operator } from './Operator';\nimport { SafeSubscriber, Subscriber } from './Subscriber';\nimport { isSubscription, Subscription } from './Subscription';\nimport { TeardownLogic, OperatorFunction, Subscribable, Observer } from './types';\nimport { observable as Symbol_observable } from './symbol/observable';\nimport { pipeFromArray } from './util/pipe';\nimport { config } from './config';\nimport { isFunction } from './util/isFunction';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A representation of any set of values over any amount of time. This is the most basic building block\n * of RxJS.\n */\nexport class Observable implements Subscribable {\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n source: Observable | undefined;\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n operator: Operator | undefined;\n\n /**\n * @param subscribe The function that is called when the Observable is\n * initially subscribed to. This function is given a Subscriber, to which new values\n * can be `next`ed, or an `error` method can be called to raise an error, or\n * `complete` can be called to notify of a successful completion.\n */\n constructor(subscribe?: (this: Observable, subscriber: Subscriber) => TeardownLogic) {\n if (subscribe) {\n this._subscribe = subscribe;\n }\n }\n\n // HACK: Since TypeScript inherits static properties too, we have to\n // fight against TypeScript here so Subject can have a different static create signature\n /**\n * Creates a new Observable by calling the Observable constructor\n * @param subscribe the subscriber function to be passed to the Observable constructor\n * @return A new observable.\n * @deprecated Use `new Observable()` instead. Will be removed in v8.\n */\n static create: (...args: any[]) => any = (subscribe?: (subscriber: Subscriber) => TeardownLogic) => {\n return new Observable(subscribe);\n };\n\n /**\n * Creates a new Observable, with this Observable instance as the source, and the passed\n * operator defined as the new observable's operator.\n * @param operator the operator defining the operation to take on the observable\n * @return A new observable with the Operator applied.\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * If you have implemented an operator using `lift`, it is recommended that you create an\n * operator by simply returning `new Observable()` directly. See \"Creating new operators from\n * scratch\" section here: https://rxjs.dev/guide/operators\n */\n lift(operator?: Operator): Observable {\n const observable = new Observable();\n observable.source = this;\n observable.operator = operator;\n return observable;\n }\n\n subscribe(observerOrNext?: Partial> | ((value: T) => void)): Subscription;\n /** @deprecated Instead of passing separate callback arguments, use an observer argument. Signatures taking separate callback arguments will be removed in v8. Details: https://rxjs.dev/deprecations/subscribe-arguments */\n subscribe(next?: ((value: T) => void) | null, error?: ((error: any) => void) | null, complete?: (() => void) | null): Subscription;\n /**\n * Invokes an execution of an Observable and registers Observer handlers for notifications it will emit.\n *\n * Use it when you have all these Observables, but still nothing is happening.\n *\n * `subscribe` is not a regular operator, but a method that calls Observable's internal `subscribe` function. It\n * might be for example a function that you passed to Observable's constructor, but most of the time it is\n * a library implementation, which defines what will be emitted by an Observable, and when it be will emitted. This means\n * that calling `subscribe` is actually the moment when Observable starts its work, not when it is created, as it is often\n * the thought.\n *\n * Apart from starting the execution of an Observable, this method allows you to listen for values\n * that an Observable emits, as well as for when it completes or errors. You can achieve this in two\n * of the following ways.\n *\n * The first way is creating an object that implements {@link Observer} interface. It should have methods\n * defined by that interface, but note that it should be just a regular JavaScript object, which you can create\n * yourself in any way you want (ES6 class, classic function constructor, object literal etc.). In particular, do\n * not attempt to use any RxJS implementation details to create Observers - you don't need them. Remember also\n * that your object does not have to implement all methods. If you find yourself creating a method that doesn't\n * do anything, you can simply omit it. Note however, if the `error` method is not provided and an error happens,\n * it will be thrown asynchronously. Errors thrown asynchronously cannot be caught using `try`/`catch`. Instead,\n * use the {@link onUnhandledError} configuration option or use a runtime handler (like `window.onerror` or\n * `process.on('error)`) to be notified of unhandled errors. Because of this, it's recommended that you provide\n * an `error` method to avoid missing thrown errors.\n *\n * The second way is to give up on Observer object altogether and simply provide callback functions in place of its methods.\n * This means you can provide three functions as arguments to `subscribe`, where the first function is equivalent\n * of a `next` method, the second of an `error` method and the third of a `complete` method. Just as in case of an Observer,\n * if you do not need to listen for something, you can omit a function by passing `undefined` or `null`,\n * since `subscribe` recognizes these functions by where they were placed in function call. When it comes\n * to the `error` function, as with an Observer, if not provided, errors emitted by an Observable will be thrown asynchronously.\n *\n * You can, however, subscribe with no parameters at all. This may be the case where you're not interested in terminal events\n * and you also handled emissions internally by using operators (e.g. using `tap`).\n *\n * Whichever style of calling `subscribe` you use, in both cases it returns a Subscription object.\n * This object allows you to call `unsubscribe` on it, which in turn will stop the work that an Observable does and will clean\n * up all resources that an Observable used. Note that cancelling a subscription will not call `complete` callback\n * provided to `subscribe` function, which is reserved for a regular completion signal that comes from an Observable.\n *\n * Remember that callbacks provided to `subscribe` are not guaranteed to be called asynchronously.\n * It is an Observable itself that decides when these functions will be called. For example {@link of}\n * by default emits all its values synchronously. Always check documentation for how given Observable\n * will behave when subscribed and if its default behavior can be modified with a `scheduler`.\n *\n * #### Examples\n *\n * Subscribe with an {@link guide/observer Observer}\n *\n * ```ts\n * import { of } from 'rxjs';\n *\n * const sumObserver = {\n * sum: 0,\n * next(value) {\n * console.log('Adding: ' + value);\n * this.sum = this.sum + value;\n * },\n * error() {\n * // We actually could just remove this method,\n * // since we do not really care about errors right now.\n * },\n * complete() {\n * console.log('Sum equals: ' + this.sum);\n * }\n * };\n *\n * of(1, 2, 3) // Synchronously emits 1, 2, 3 and then completes.\n * .subscribe(sumObserver);\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Subscribe with functions ({@link deprecations/subscribe-arguments deprecated})\n *\n * ```ts\n * import { of } from 'rxjs'\n *\n * let sum = 0;\n *\n * of(1, 2, 3).subscribe(\n * value => {\n * console.log('Adding: ' + value);\n * sum = sum + value;\n * },\n * undefined,\n * () => console.log('Sum equals: ' + sum)\n * );\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Cancel a subscription\n *\n * ```ts\n * import { interval } from 'rxjs';\n *\n * const subscription = interval(1000).subscribe({\n * next(num) {\n * console.log(num)\n * },\n * complete() {\n * // Will not be called, even when cancelling subscription.\n * console.log('completed!');\n * }\n * });\n *\n * setTimeout(() => {\n * subscription.unsubscribe();\n * console.log('unsubscribed!');\n * }, 2500);\n *\n * // Logs:\n * // 0 after 1s\n * // 1 after 2s\n * // 'unsubscribed!' after 2.5s\n * ```\n *\n * @param observerOrNext Either an {@link Observer} with some or all callback methods,\n * or the `next` handler that is called for each value emitted from the subscribed Observable.\n * @param error A handler for a terminal event resulting from an error. If no error handler is provided,\n * the error will be thrown asynchronously as unhandled.\n * @param complete A handler for a terminal event resulting from successful completion.\n * @return A subscription reference to the registered handlers.\n */\n subscribe(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((error: any) => void) | null,\n complete?: (() => void) | null\n ): Subscription {\n const subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);\n\n errorContext(() => {\n const { operator, source } = this;\n subscriber.add(\n operator\n ? // We're dealing with a subscription in the\n // operator chain to one of our lifted operators.\n operator.call(subscriber, source)\n : source\n ? // If `source` has a value, but `operator` does not, something that\n // had intimate knowledge of our API, like our `Subject`, must have\n // set it. We're going to just call `_subscribe` directly.\n this._subscribe(subscriber)\n : // In all other cases, we're likely wrapping a user-provided initializer\n // function, so we need to catch errors and handle them appropriately.\n this._trySubscribe(subscriber)\n );\n });\n\n return subscriber;\n }\n\n /** @internal */\n protected _trySubscribe(sink: Subscriber): TeardownLogic {\n try {\n return this._subscribe(sink);\n } catch (err) {\n // We don't need to return anything in this case,\n // because it's just going to try to `add()` to a subscription\n // above.\n sink.error(err);\n }\n }\n\n /**\n * Used as a NON-CANCELLABLE means of subscribing to an observable, for use with\n * APIs that expect promises, like `async/await`. You cannot unsubscribe from this.\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * #### Example\n *\n * ```ts\n * import { interval, take } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(4));\n *\n * async function getTotal() {\n * let total = 0;\n *\n * await source$.forEach(value => {\n * total += value;\n * console.log('observable -> ' + value);\n * });\n *\n * return total;\n * }\n *\n * getTotal().then(\n * total => console.log('Total: ' + total)\n * );\n *\n * // Expected:\n * // 'observable -> 0'\n * // 'observable -> 1'\n * // 'observable -> 2'\n * // 'observable -> 3'\n * // 'Total: 6'\n * ```\n *\n * @param next A handler for each value emitted by the observable.\n * @return A promise that either resolves on observable completion or\n * rejects with the handled error.\n */\n forEach(next: (value: T) => void): Promise;\n\n /**\n * @param next a handler for each value emitted by the observable\n * @param promiseCtor a constructor function used to instantiate the Promise\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n * @deprecated Passing a Promise constructor will no longer be available\n * in upcoming versions of RxJS. This is because it adds weight to the library, for very\n * little benefit. If you need this functionality, it is recommended that you either\n * polyfill Promise, or you create an adapter to convert the returned native promise\n * to whatever promise implementation you wanted. Will be removed in v8.\n */\n forEach(next: (value: T) => void, promiseCtor: PromiseConstructorLike): Promise;\n\n forEach(next: (value: T) => void, promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n const subscriber = new SafeSubscriber({\n next: (value) => {\n try {\n next(value);\n } catch (err) {\n reject(err);\n subscriber.unsubscribe();\n }\n },\n error: reject,\n complete: resolve,\n });\n this.subscribe(subscriber);\n }) as Promise;\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): TeardownLogic {\n return this.source?.subscribe(subscriber);\n }\n\n /**\n * An interop point defined by the es7-observable spec https://github.com/zenparsing/es-observable\n * @return This instance of the observable.\n */\n [Symbol_observable]() {\n return this;\n }\n\n /* tslint:disable:max-line-length */\n pipe(): Observable;\n pipe(op1: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction, op3: OperatorFunction): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction,\n ...operations: OperatorFunction[]\n ): Observable;\n /* tslint:enable:max-line-length */\n\n /**\n * Used to stitch together functional operators into a chain.\n *\n * ## Example\n *\n * ```ts\n * import { interval, filter, map, scan } from 'rxjs';\n *\n * interval(1000)\n * .pipe(\n * filter(x => x % 2 === 0),\n * map(x => x + x),\n * scan((acc, x) => acc + x)\n * )\n * .subscribe(x => console.log(x));\n * ```\n *\n * @return The Observable result of all the operators having been called\n * in the order they were passed in.\n */\n pipe(...operations: OperatorFunction[]): Observable {\n return pipeFromArray(operations)(this);\n }\n\n /* tslint:disable:max-line-length */\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: typeof Promise): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: PromiseConstructorLike): Promise;\n /* tslint:enable:max-line-length */\n\n /**\n * Subscribe to this Observable and get a Promise resolving on\n * `complete` with the last emission (if any).\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * @param [promiseCtor] a constructor function used to instantiate\n * the Promise\n * @return A Promise that resolves with the last value emit, or\n * rejects on an error. If there were no emissions, Promise\n * resolves with undefined.\n * @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise\n */\n toPromise(promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n let value: T | undefined;\n this.subscribe(\n (x: T) => (value = x),\n (err: any) => reject(err),\n () => resolve(value)\n );\n }) as Promise;\n }\n}\n\n/**\n * Decides between a passed promise constructor from consuming code,\n * A default configured promise constructor, and the native promise\n * constructor and returns it. If nothing can be found, it will throw\n * an error.\n * @param promiseCtor The optional promise constructor to passed by consuming code\n */\nfunction getPromiseCtor(promiseCtor: PromiseConstructorLike | undefined) {\n return promiseCtor ?? config.Promise ?? Promise;\n}\n\nfunction isObserver(value: any): value is Observer {\n return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete);\n}\n\nfunction isSubscriber(value: any): value is Subscriber {\n return (value && value instanceof Subscriber) || (isObserver(value) && isSubscription(value));\n}\n", "import { Observable } from '../Observable';\nimport { Subscriber } from '../Subscriber';\nimport { OperatorFunction } from '../types';\nimport { isFunction } from './isFunction';\n\n/**\n * Used to determine if an object is an Observable with a lift function.\n */\nexport function hasLift(source: any): source is { lift: InstanceType['lift'] } {\n return isFunction(source?.lift);\n}\n\n/**\n * Creates an `OperatorFunction`. Used to define operators throughout the library in a concise way.\n * @param init The logic to connect the liftedSource to the subscriber at the moment of subscription.\n */\nexport function operate(\n init: (liftedSource: Observable, subscriber: Subscriber) => (() => void) | void\n): OperatorFunction {\n return (source: Observable) => {\n if (hasLift(source)) {\n return source.lift(function (this: Subscriber, liftedSource: Observable) {\n try {\n return init(liftedSource, this);\n } catch (err) {\n this.error(err);\n }\n });\n }\n throw new TypeError('Unable to lift unknown Observable type');\n };\n}\n", "import { Subscriber } from '../Subscriber';\n\n/**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional teardown logic here. This will only be called on teardown if the\n * subscriber itself is not already closed. This is called after all other teardown logic is executed.\n */\nexport function createOperatorSubscriber(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n onFinalize?: () => void\n): Subscriber {\n return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize);\n}\n\n/**\n * A generic helper for allowing operators to be created with a Subscriber and\n * use closures to capture necessary state from the operator function itself.\n */\nexport class OperatorSubscriber extends Subscriber {\n /**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional finalization logic here. This will only be called on finalization if the\n * subscriber itself is not already closed. This is called after all other finalization logic is executed.\n * @param shouldUnsubscribe An optional check to see if an unsubscribe call should truly unsubscribe.\n * NOTE: This currently **ONLY** exists to support the strange behavior of {@link groupBy}, where unsubscription\n * to the resulting observable does not actually disconnect from the source if there are active subscriptions\n * to any grouped observable. (DO NOT EXPOSE OR USE EXTERNALLY!!!)\n */\n constructor(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n private onFinalize?: () => void,\n private shouldUnsubscribe?: () => boolean\n ) {\n // It's important - for performance reasons - that all of this class's\n // members are initialized and that they are always initialized in the same\n // order. This will ensure that all OperatorSubscriber instances have the\n // same hidden class in V8. This, in turn, will help keep the number of\n // hidden classes involved in property accesses within the base class as\n // low as possible. If the number of hidden classes involved exceeds four,\n // the property accesses will become megamorphic and performance penalties\n // will be incurred - i.e. inline caches won't be used.\n //\n // The reasons for ensuring all instances have the same hidden class are\n // further discussed in this blog post from Benedikt Meurer:\n // https://benediktmeurer.de/2018/03/23/impact-of-polymorphism-on-component-based-frameworks-like-react/\n super(destination);\n this._next = onNext\n ? function (this: OperatorSubscriber, value: T) {\n try {\n onNext(value);\n } catch (err) {\n destination.error(err);\n }\n }\n : super._next;\n this._error = onError\n ? function (this: OperatorSubscriber, err: any) {\n try {\n onError(err);\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._error;\n this._complete = onComplete\n ? function (this: OperatorSubscriber) {\n try {\n onComplete();\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._complete;\n }\n\n unsubscribe() {\n if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) {\n const { closed } = this;\n super.unsubscribe();\n // Execute additional teardown if we have any and we didn't already do so.\n !closed && this.onFinalize?.();\n }\n }\n}\n", "import { Subscription } from '../Subscription';\n\ninterface AnimationFrameProvider {\n schedule(callback: FrameRequestCallback): Subscription;\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n delegate:\n | {\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n }\n | undefined;\n}\n\nexport const animationFrameProvider: AnimationFrameProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n schedule(callback) {\n let request = requestAnimationFrame;\n let cancel: typeof cancelAnimationFrame | undefined = cancelAnimationFrame;\n const { delegate } = animationFrameProvider;\n if (delegate) {\n request = delegate.requestAnimationFrame;\n cancel = delegate.cancelAnimationFrame;\n }\n const handle = request((timestamp) => {\n // Clear the cancel function. The request has been fulfilled, so\n // attempting to cancel the request upon unsubscription would be\n // pointless.\n cancel = undefined;\n callback(timestamp);\n });\n return new Subscription(() => cancel?.(handle));\n },\n requestAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.requestAnimationFrame || requestAnimationFrame)(...args);\n },\n cancelAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.cancelAnimationFrame || cancelAnimationFrame)(...args);\n },\n delegate: undefined,\n};\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface ObjectUnsubscribedError extends Error {}\n\nexport interface ObjectUnsubscribedErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (): ObjectUnsubscribedError;\n}\n\n/**\n * An error thrown when an action is invalid because the object has been\n * unsubscribed.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n *\n * @class ObjectUnsubscribedError\n */\nexport const ObjectUnsubscribedError: ObjectUnsubscribedErrorCtor = createErrorClass(\n (_super) =>\n function ObjectUnsubscribedErrorImpl(this: any) {\n _super(this);\n this.name = 'ObjectUnsubscribedError';\n this.message = 'object unsubscribed';\n }\n);\n", "import { Operator } from './Operator';\nimport { Observable } from './Observable';\nimport { Subscriber } from './Subscriber';\nimport { Subscription, EMPTY_SUBSCRIPTION } from './Subscription';\nimport { Observer, SubscriptionLike, TeardownLogic } from './types';\nimport { ObjectUnsubscribedError } from './util/ObjectUnsubscribedError';\nimport { arrRemove } from './util/arrRemove';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A Subject is a special type of Observable that allows values to be\n * multicasted to many Observers. Subjects are like EventEmitters.\n *\n * Every Subject is an Observable and an Observer. You can subscribe to a\n * Subject, and you can call next to feed values as well as error and complete.\n */\nexport class Subject extends Observable implements SubscriptionLike {\n closed = false;\n\n private currentObservers: Observer[] | null = null;\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n observers: Observer[] = [];\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n isStopped = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n hasError = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n thrownError: any = null;\n\n /**\n * Creates a \"subject\" by basically gluing an observer to an observable.\n *\n * @deprecated Recommended you do not use. Will be removed at some point in the future. Plans for replacement still under discussion.\n */\n static create: (...args: any[]) => any = (destination: Observer, source: Observable): AnonymousSubject => {\n return new AnonymousSubject(destination, source);\n };\n\n constructor() {\n // NOTE: This must be here to obscure Observable's constructor.\n super();\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n lift(operator: Operator): Observable {\n const subject = new AnonymousSubject(this, this);\n subject.operator = operator as any;\n return subject as any;\n }\n\n /** @internal */\n protected _throwIfClosed() {\n if (this.closed) {\n throw new ObjectUnsubscribedError();\n }\n }\n\n next(value: T) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n if (!this.currentObservers) {\n this.currentObservers = Array.from(this.observers);\n }\n for (const observer of this.currentObservers) {\n observer.next(value);\n }\n }\n });\n }\n\n error(err: any) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.hasError = this.isStopped = true;\n this.thrownError = err;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.error(err);\n }\n }\n });\n }\n\n complete() {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.isStopped = true;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.complete();\n }\n }\n });\n }\n\n unsubscribe() {\n this.isStopped = this.closed = true;\n this.observers = this.currentObservers = null!;\n }\n\n get observed() {\n return this.observers?.length > 0;\n }\n\n /** @internal */\n protected _trySubscribe(subscriber: Subscriber): TeardownLogic {\n this._throwIfClosed();\n return super._trySubscribe(subscriber);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._checkFinalizedStatuses(subscriber);\n return this._innerSubscribe(subscriber);\n }\n\n /** @internal */\n protected _innerSubscribe(subscriber: Subscriber) {\n const { hasError, isStopped, observers } = this;\n if (hasError || isStopped) {\n return EMPTY_SUBSCRIPTION;\n }\n this.currentObservers = null;\n observers.push(subscriber);\n return new Subscription(() => {\n this.currentObservers = null;\n arrRemove(observers, subscriber);\n });\n }\n\n /** @internal */\n protected _checkFinalizedStatuses(subscriber: Subscriber) {\n const { hasError, thrownError, isStopped } = this;\n if (hasError) {\n subscriber.error(thrownError);\n } else if (isStopped) {\n subscriber.complete();\n }\n }\n\n /**\n * Creates a new Observable with this Subject as the source. You can do this\n * to create custom Observer-side logic of the Subject and conceal it from\n * code that uses the Observable.\n * @return Observable that this Subject casts to.\n */\n asObservable(): Observable {\n const observable: any = new Observable();\n observable.source = this;\n return observable;\n }\n}\n\nexport class AnonymousSubject extends Subject {\n constructor(\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n public destination?: Observer,\n source?: Observable\n ) {\n super();\n this.source = source;\n }\n\n next(value: T) {\n this.destination?.next?.(value);\n }\n\n error(err: any) {\n this.destination?.error?.(err);\n }\n\n complete() {\n this.destination?.complete?.();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n return this.source?.subscribe(subscriber) ?? EMPTY_SUBSCRIPTION;\n }\n}\n", "import { Subject } from './Subject';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\n\n/**\n * A variant of Subject that requires an initial value and emits its current\n * value whenever it is subscribed to.\n */\nexport class BehaviorSubject extends Subject {\n constructor(private _value: T) {\n super();\n }\n\n get value(): T {\n return this.getValue();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n const subscription = super._subscribe(subscriber);\n !subscription.closed && subscriber.next(this._value);\n return subscription;\n }\n\n getValue(): T {\n const { hasError, thrownError, _value } = this;\n if (hasError) {\n throw thrownError;\n }\n this._throwIfClosed();\n return _value;\n }\n\n next(value: T): void {\n super.next((this._value = value));\n }\n}\n", "import { TimestampProvider } from '../types';\n\ninterface DateTimestampProvider extends TimestampProvider {\n delegate: TimestampProvider | undefined;\n}\n\nexport const dateTimestampProvider: DateTimestampProvider = {\n now() {\n // Use the variable rather than `this` so that the function can be called\n // without being bound to the provider.\n return (dateTimestampProvider.delegate || Date).now();\n },\n delegate: undefined,\n};\n", "import { Subject } from './Subject';\nimport { TimestampProvider } from './types';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * A variant of {@link Subject} that \"replays\" old values to new subscribers by emitting them when they first subscribe.\n *\n * `ReplaySubject` has an internal buffer that will store a specified number of values that it has observed. Like `Subject`,\n * `ReplaySubject` \"observes\" values by having them passed to its `next` method. When it observes a value, it will store that\n * value for a time determined by the configuration of the `ReplaySubject`, as passed to its constructor.\n *\n * When a new subscriber subscribes to the `ReplaySubject` instance, it will synchronously emit all values in its buffer in\n * a First-In-First-Out (FIFO) manner. The `ReplaySubject` will also complete, if it has observed completion; and it will\n * error if it has observed an error.\n *\n * There are two main configuration items to be concerned with:\n *\n * 1. `bufferSize` - This will determine how many items are stored in the buffer, defaults to infinite.\n * 2. `windowTime` - The amount of time to hold a value in the buffer before removing it from the buffer.\n *\n * Both configurations may exist simultaneously. So if you would like to buffer a maximum of 3 values, as long as the values\n * are less than 2 seconds old, you could do so with a `new ReplaySubject(3, 2000)`.\n *\n * ### Differences with BehaviorSubject\n *\n * `BehaviorSubject` is similar to `new ReplaySubject(1)`, with a couple of exceptions:\n *\n * 1. `BehaviorSubject` comes \"primed\" with a single value upon construction.\n * 2. `ReplaySubject` will replay values, even after observing an error, where `BehaviorSubject` will not.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n * @see {@link shareReplay}\n */\nexport class ReplaySubject extends Subject {\n private _buffer: (T | number)[] = [];\n private _infiniteTimeWindow = true;\n\n /**\n * @param _bufferSize The size of the buffer to replay on subscription\n * @param _windowTime The amount of time the buffered items will stay buffered\n * @param _timestampProvider An object with a `now()` method that provides the current timestamp. This is used to\n * calculate the amount of time something has been buffered.\n */\n constructor(\n private _bufferSize = Infinity,\n private _windowTime = Infinity,\n private _timestampProvider: TimestampProvider = dateTimestampProvider\n ) {\n super();\n this._infiniteTimeWindow = _windowTime === Infinity;\n this._bufferSize = Math.max(1, _bufferSize);\n this._windowTime = Math.max(1, _windowTime);\n }\n\n next(value: T): void {\n const { isStopped, _buffer, _infiniteTimeWindow, _timestampProvider, _windowTime } = this;\n if (!isStopped) {\n _buffer.push(value);\n !_infiniteTimeWindow && _buffer.push(_timestampProvider.now() + _windowTime);\n }\n this._trimBuffer();\n super.next(value);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._trimBuffer();\n\n const subscription = this._innerSubscribe(subscriber);\n\n const { _infiniteTimeWindow, _buffer } = this;\n // We use a copy here, so reentrant code does not mutate our array while we're\n // emitting it to a new subscriber.\n const copy = _buffer.slice();\n for (let i = 0; i < copy.length && !subscriber.closed; i += _infiniteTimeWindow ? 1 : 2) {\n subscriber.next(copy[i] as T);\n }\n\n this._checkFinalizedStatuses(subscriber);\n\n return subscription;\n }\n\n private _trimBuffer() {\n const { _bufferSize, _timestampProvider, _buffer, _infiniteTimeWindow } = this;\n // If we don't have an infinite buffer size, and we're over the length,\n // use splice to truncate the old buffer values off. Note that we have to\n // double the size for instances where we're not using an infinite time window\n // because we're storing the values and the timestamps in the same array.\n const adjustedBufferSize = (_infiniteTimeWindow ? 1 : 2) * _bufferSize;\n _bufferSize < Infinity && adjustedBufferSize < _buffer.length && _buffer.splice(0, _buffer.length - adjustedBufferSize);\n\n // Now, if we're not in an infinite time window, remove all values where the time is\n // older than what is allowed.\n if (!_infiniteTimeWindow) {\n const now = _timestampProvider.now();\n let last = 0;\n // Search the array for the first timestamp that isn't expired and\n // truncate the buffer up to that point.\n for (let i = 1; i < _buffer.length && (_buffer[i] as number) <= now; i += 2) {\n last = i;\n }\n last && _buffer.splice(0, last + 1);\n }\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Subscription } from '../Subscription';\nimport { SchedulerAction } from '../types';\n\n/**\n * A unit of work to be executed in a `scheduler`. An action is typically\n * created from within a {@link SchedulerLike} and an RxJS user does not need to concern\n * themselves about creating and manipulating an Action.\n *\n * ```ts\n * class Action extends Subscription {\n * new (scheduler: Scheduler, work: (state?: T) => void);\n * schedule(state?: T, delay: number = 0): Subscription;\n * }\n * ```\n */\nexport class Action extends Subscription {\n constructor(scheduler: Scheduler, work: (this: SchedulerAction, state?: T) => void) {\n super();\n }\n /**\n * Schedules this action on its parent {@link SchedulerLike} for execution. May be passed\n * some context object, `state`. May happen at some point in the future,\n * according to the `delay` parameter, if specified.\n * @param state Some contextual data that the `work` function uses when called by the\n * Scheduler.\n * @param delay Time to wait before executing the work, where the time unit is implicit\n * and defined by the Scheduler.\n * @return A subscription in order to be able to unsubscribe the scheduled work.\n */\n public schedule(state?: T, delay: number = 0): Subscription {\n return this;\n }\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetIntervalFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearIntervalFunction = (handle: TimerHandle) => void;\n\ninterface IntervalProvider {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n delegate:\n | {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n }\n | undefined;\n}\n\nexport const intervalProvider: IntervalProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setInterval(handler: () => void, timeout?: number, ...args) {\n const { delegate } = intervalProvider;\n if (delegate?.setInterval) {\n return delegate.setInterval(handler, timeout, ...args);\n }\n return setInterval(handler, timeout, ...args);\n },\n clearInterval(handle) {\n const { delegate } = intervalProvider;\n return (delegate?.clearInterval || clearInterval)(handle as any);\n },\n delegate: undefined,\n};\n", "import { Action } from './Action';\nimport { SchedulerAction } from '../types';\nimport { Subscription } from '../Subscription';\nimport { AsyncScheduler } from './AsyncScheduler';\nimport { intervalProvider } from './intervalProvider';\nimport { arrRemove } from '../util/arrRemove';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncAction extends Action {\n public id: TimerHandle | undefined;\n public state?: T;\n // @ts-ignore: Property has no initializer and is not definitely assigned\n public delay: number;\n protected pending: boolean = false;\n\n constructor(protected scheduler: AsyncScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (this.closed) {\n return this;\n }\n\n // Always replace the current state with the new state.\n this.state = state;\n\n const id = this.id;\n const scheduler = this.scheduler;\n\n //\n // Important implementation note:\n //\n // Actions only execute once by default, unless rescheduled from within the\n // scheduled callback. This allows us to implement single and repeat\n // actions via the same code path, without adding API surface area, as well\n // as mimic traditional recursion but across asynchronous boundaries.\n //\n // However, JS runtimes and timers distinguish between intervals achieved by\n // serial `setTimeout` calls vs. a single `setInterval` call. An interval of\n // serial `setTimeout` calls can be individually delayed, which delays\n // scheduling the next `setTimeout`, and so on. `setInterval` attempts to\n // guarantee the interval callback will be invoked more precisely to the\n // interval period, regardless of load.\n //\n // Therefore, we use `setInterval` to schedule single and repeat actions.\n // If the action reschedules itself with the same delay, the interval is not\n // canceled. If the action doesn't reschedule, or reschedules with a\n // different delay, the interval will be canceled after scheduled callback\n // execution.\n //\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, delay);\n }\n\n // Set the pending flag indicating that this action has been scheduled, or\n // has recursively rescheduled itself.\n this.pending = true;\n\n this.delay = delay;\n // If this action has already an async Id, don't request a new one.\n this.id = this.id ?? this.requestAsyncId(scheduler, this.id, delay);\n\n return this;\n }\n\n protected requestAsyncId(scheduler: AsyncScheduler, _id?: TimerHandle, delay: number = 0): TimerHandle {\n return intervalProvider.setInterval(scheduler.flush.bind(scheduler, this), delay);\n }\n\n protected recycleAsyncId(_scheduler: AsyncScheduler, id?: TimerHandle, delay: number | null = 0): TimerHandle | undefined {\n // If this action is rescheduled with the same delay time, don't clear the interval id.\n if (delay != null && this.delay === delay && this.pending === false) {\n return id;\n }\n // Otherwise, if the action's delay time is different from the current delay,\n // or the action has been rescheduled before it's executed, clear the interval id\n if (id != null) {\n intervalProvider.clearInterval(id);\n }\n\n return undefined;\n }\n\n /**\n * Immediately executes this action and the `work` it contains.\n */\n public execute(state: T, delay: number): any {\n if (this.closed) {\n return new Error('executing a cancelled action');\n }\n\n this.pending = false;\n const error = this._execute(state, delay);\n if (error) {\n return error;\n } else if (this.pending === false && this.id != null) {\n // Dequeue if the action didn't reschedule itself. Don't call\n // unsubscribe(), because the action could reschedule later.\n // For example:\n // ```\n // scheduler.schedule(function doWork(counter) {\n // /* ... I'm a busy worker bee ... */\n // var originalAction = this;\n // /* wait 100ms before rescheduling the action */\n // setTimeout(function () {\n // originalAction.schedule(counter + 1);\n // }, 100);\n // }, 1000);\n // ```\n this.id = this.recycleAsyncId(this.scheduler, this.id, null);\n }\n }\n\n protected _execute(state: T, _delay: number): any {\n let errored: boolean = false;\n let errorValue: any;\n try {\n this.work(state);\n } catch (e) {\n errored = true;\n // HACK: Since code elsewhere is relying on the \"truthiness\" of the\n // return here, we can't have it return \"\" or 0 or false.\n // TODO: Clean this up when we refactor schedulers mid-version-8 or so.\n errorValue = e ? e : new Error('Scheduled action threw falsy error');\n }\n if (errored) {\n this.unsubscribe();\n return errorValue;\n }\n }\n\n unsubscribe() {\n if (!this.closed) {\n const { id, scheduler } = this;\n const { actions } = scheduler;\n\n this.work = this.state = this.scheduler = null!;\n this.pending = false;\n\n arrRemove(actions, this);\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, null);\n }\n\n this.delay = null!;\n super.unsubscribe();\n }\n }\n}\n", "import { Action } from './scheduler/Action';\nimport { Subscription } from './Subscription';\nimport { SchedulerLike, SchedulerAction } from './types';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * An execution context and a data structure to order tasks and schedule their\n * execution. Provides a notion of (potentially virtual) time, through the\n * `now()` getter method.\n *\n * Each unit of work in a Scheduler is called an `Action`.\n *\n * ```ts\n * class Scheduler {\n * now(): number;\n * schedule(work, delay?, state?): Subscription;\n * }\n * ```\n *\n * @deprecated Scheduler is an internal implementation detail of RxJS, and\n * should not be used directly. Rather, create your own class and implement\n * {@link SchedulerLike}. Will be made internal in v8.\n */\nexport class Scheduler implements SchedulerLike {\n public static now: () => number = dateTimestampProvider.now;\n\n constructor(private schedulerActionCtor: typeof Action, now: () => number = Scheduler.now) {\n this.now = now;\n }\n\n /**\n * A getter method that returns a number representing the current time\n * (at the time this function was called) according to the scheduler's own\n * internal clock.\n * @return A number that represents the current time. May or may not\n * have a relation to wall-clock time. May or may not refer to a time unit\n * (e.g. milliseconds).\n */\n public now: () => number;\n\n /**\n * Schedules a function, `work`, for execution. May happen at some point in\n * the future, according to the `delay` parameter, if specified. May be passed\n * some context object, `state`, which will be passed to the `work` function.\n *\n * The given arguments will be processed an stored as an Action object in a\n * queue of actions.\n *\n * @param work A function representing a task, or some unit of work to be\n * executed by the Scheduler.\n * @param delay Time to wait before executing the work, where the time unit is\n * implicit and defined by the Scheduler itself.\n * @param state Some contextual data that the `work` function uses when called\n * by the Scheduler.\n * @return A subscription in order to be able to unsubscribe the scheduled work.\n */\n public schedule(work: (this: SchedulerAction, state?: T) => void, delay: number = 0, state?: T): Subscription {\n return new this.schedulerActionCtor(this, work).schedule(state, delay);\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Action } from './Action';\nimport { AsyncAction } from './AsyncAction';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncScheduler extends Scheduler {\n public actions: Array> = [];\n /**\n * A flag to indicate whether the Scheduler is currently executing a batch of\n * queued actions.\n * @internal\n */\n public _active: boolean = false;\n /**\n * An internal ID used to track the latest asynchronous task such as those\n * coming from `setTimeout`, `setInterval`, `requestAnimationFrame`, and\n * others.\n * @internal\n */\n public _scheduled: TimerHandle | undefined;\n\n constructor(SchedulerAction: typeof Action, now: () => number = Scheduler.now) {\n super(SchedulerAction, now);\n }\n\n public flush(action: AsyncAction): void {\n const { actions } = this;\n\n if (this._active) {\n actions.push(action);\n return;\n }\n\n let error: any;\n this._active = true;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions.shift()!)); // exhaust the scheduler queue\n\n this._active = false;\n\n if (error) {\n while ((action = actions.shift()!)) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\n/**\n *\n * Async Scheduler\n *\n * Schedule task as if you used setTimeout(task, duration)\n *\n * `async` scheduler schedules tasks asynchronously, by putting them on the JavaScript\n * event loop queue. It is best used to delay tasks in time or to schedule tasks repeating\n * in intervals.\n *\n * If you just want to \"defer\" task, that is to perform it right after currently\n * executing synchronous code ends (commonly achieved by `setTimeout(deferredTask, 0)`),\n * better choice will be the {@link asapScheduler} scheduler.\n *\n * ## Examples\n * Use async scheduler to delay task\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * const task = () => console.log('it works!');\n *\n * asyncScheduler.schedule(task, 2000);\n *\n * // After 2 seconds logs:\n * // \"it works!\"\n * ```\n *\n * Use async scheduler to repeat task in intervals\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * function task(state) {\n * console.log(state);\n * this.schedule(state + 1, 1000); // `this` references currently executing Action,\n * // which we reschedule with new state and delay\n * }\n *\n * asyncScheduler.schedule(task, 3000, 0);\n *\n * // Logs:\n * // 0 after 3s\n * // 1 after 4s\n * // 2 after 5s\n * // 3 after 6s\n * ```\n */\n\nexport const asyncScheduler = new AsyncScheduler(AsyncAction);\n\n/**\n * @deprecated Renamed to {@link asyncScheduler}. Will be removed in v8.\n */\nexport const async = asyncScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { Subscription } from '../Subscription';\nimport { QueueScheduler } from './QueueScheduler';\nimport { SchedulerAction } from '../types';\nimport { TimerHandle } from './timerHandle';\n\nexport class QueueAction extends AsyncAction {\n constructor(protected scheduler: QueueScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (delay > 0) {\n return super.schedule(state, delay);\n }\n this.delay = delay;\n this.state = state;\n this.scheduler.flush(this);\n return this;\n }\n\n public execute(state: T, delay: number): any {\n return delay > 0 || this.closed ? super.execute(state, delay) : this._execute(state, delay);\n }\n\n protected requestAsyncId(scheduler: QueueScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n\n if ((delay != null && delay > 0) || (delay == null && this.delay > 0)) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n\n // Otherwise flush the scheduler starting with this action.\n scheduler.flush(this);\n\n // HACK: In the past, this was returning `void`. However, `void` isn't a valid\n // `TimerHandle`, and generally the return value here isn't really used. So the\n // compromise is to return `0` which is both \"falsy\" and a valid `TimerHandle`,\n // as opposed to refactoring every other instanceo of `requestAsyncId`.\n return 0;\n }\n}\n", "import { AsyncScheduler } from './AsyncScheduler';\n\nexport class QueueScheduler extends AsyncScheduler {\n}\n", "import { QueueAction } from './QueueAction';\nimport { QueueScheduler } from './QueueScheduler';\n\n/**\n *\n * Queue Scheduler\n *\n * Put every next task on a queue, instead of executing it immediately\n *\n * `queue` scheduler, when used with delay, behaves the same as {@link asyncScheduler} scheduler.\n *\n * When used without delay, it schedules given task synchronously - executes it right when\n * it is scheduled. However when called recursively, that is when inside the scheduled task,\n * another task is scheduled with queue scheduler, instead of executing immediately as well,\n * that task will be put on a queue and wait for current one to finish.\n *\n * This means that when you execute task with `queue` scheduler, you are sure it will end\n * before any other task scheduled with that scheduler will start.\n *\n * ## Examples\n * Schedule recursively first, then do something\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(() => {\n * queueScheduler.schedule(() => console.log('second')); // will not happen now, but will be put on a queue\n *\n * console.log('first');\n * });\n *\n * // Logs:\n * // \"first\"\n * // \"second\"\n * ```\n *\n * Reschedule itself recursively\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(function(state) {\n * if (state !== 0) {\n * console.log('before', state);\n * this.schedule(state - 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * console.log('after', state);\n * }\n * }, 0, 3);\n *\n * // In scheduler that runs recursively, you would expect:\n * // \"before\", 3\n * // \"before\", 2\n * // \"before\", 1\n * // \"after\", 1\n * // \"after\", 2\n * // \"after\", 3\n *\n * // But with queue it logs:\n * // \"before\", 3\n * // \"after\", 3\n * // \"before\", 2\n * // \"after\", 2\n * // \"before\", 1\n * // \"after\", 1\n * ```\n */\n\nexport const queueScheduler = new QueueScheduler(QueueAction);\n\n/**\n * @deprecated Renamed to {@link queueScheduler}. Will be removed in v8.\n */\nexport const queue = queueScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\nimport { SchedulerAction } from '../types';\nimport { animationFrameProvider } from './animationFrameProvider';\nimport { TimerHandle } from './timerHandle';\n\nexport class AnimationFrameAction extends AsyncAction {\n constructor(protected scheduler: AnimationFrameScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n protected requestAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay is greater than 0, request as an async action.\n if (delay !== null && delay > 0) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n // Push the action to the end of the scheduler queue.\n scheduler.actions.push(this);\n // If an animation frame has already been requested, don't request another\n // one. If an animation frame hasn't been requested yet, request one. Return\n // the current animation frame request id.\n return scheduler._scheduled || (scheduler._scheduled = animationFrameProvider.requestAnimationFrame(() => scheduler.flush(undefined)));\n }\n\n protected recycleAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle | undefined {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n if (delay != null ? delay > 0 : this.delay > 0) {\n return super.recycleAsyncId(scheduler, id, delay);\n }\n // If the scheduler queue has no remaining actions with the same async id,\n // cancel the requested animation frame and set the scheduled flag to\n // undefined so the next AnimationFrameAction will request its own.\n const { actions } = scheduler;\n if (id != null && id === scheduler._scheduled && actions[actions.length - 1]?.id !== id) {\n animationFrameProvider.cancelAnimationFrame(id as number);\n scheduler._scheduled = undefined;\n }\n // Return undefined so the action knows to request a new async id if it's rescheduled.\n return undefined;\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\nexport class AnimationFrameScheduler extends AsyncScheduler {\n public flush(action?: AsyncAction): void {\n this._active = true;\n // The async id that effects a call to flush is stored in _scheduled.\n // Before executing an action, it's necessary to check the action's async\n // id to determine whether it's supposed to be executed in the current\n // flush.\n // Previous implementations of this method used a count to determine this,\n // but that was unsound, as actions that are unsubscribed - i.e. cancelled -\n // are removed from the actions array and that can shift actions that are\n // scheduled to be executed in a subsequent flush into positions at which\n // they are executed within the current flush.\n let flushId;\n if (action) {\n flushId = action.id;\n } else {\n flushId = this._scheduled;\n this._scheduled = undefined;\n }\n\n const { actions } = this;\n let error: any;\n action = action || actions.shift()!;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions[0]) && action.id === flushId && actions.shift());\n\n this._active = false;\n\n if (error) {\n while ((action = actions[0]) && action.id === flushId && actions.shift()) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AnimationFrameAction } from './AnimationFrameAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\n\n/**\n *\n * Animation Frame Scheduler\n *\n * Perform task when `window.requestAnimationFrame` would fire\n *\n * When `animationFrame` scheduler is used with delay, it will fall back to {@link asyncScheduler} scheduler\n * behaviour.\n *\n * Without delay, `animationFrame` scheduler can be used to create smooth browser animations.\n * It makes sure scheduled task will happen just before next browser content repaint,\n * thus performing animations as efficiently as possible.\n *\n * ## Example\n * Schedule div height animation\n * ```ts\n * // html:
\n * import { animationFrameScheduler } from 'rxjs';\n *\n * const div = document.querySelector('div');\n *\n * animationFrameScheduler.schedule(function(height) {\n * div.style.height = height + \"px\";\n *\n * this.schedule(height + 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * }, 0, 0);\n *\n * // You will see a div element growing in height\n * ```\n */\n\nexport const animationFrameScheduler = new AnimationFrameScheduler(AnimationFrameAction);\n\n/**\n * @deprecated Renamed to {@link animationFrameScheduler}. Will be removed in v8.\n */\nexport const animationFrame = animationFrameScheduler;\n", "import { Observable } from '../Observable';\nimport { SchedulerLike } from '../types';\n\n/**\n * A simple Observable that emits no items to the Observer and immediately\n * emits a complete notification.\n *\n * Just emits 'complete', and nothing else.\n *\n * ![](empty.png)\n *\n * A simple Observable that only emits the complete notification. It can be used\n * for composing with other Observables, such as in a {@link mergeMap}.\n *\n * ## Examples\n *\n * Log complete notification\n *\n * ```ts\n * import { EMPTY } from 'rxjs';\n *\n * EMPTY.subscribe({\n * next: () => console.log('Next'),\n * complete: () => console.log('Complete!')\n * });\n *\n * // Outputs\n * // Complete!\n * ```\n *\n * Emit the number 7, then complete\n *\n * ```ts\n * import { EMPTY, startWith } from 'rxjs';\n *\n * const result = EMPTY.pipe(startWith(7));\n * result.subscribe(x => console.log(x));\n *\n * // Outputs\n * // 7\n * ```\n *\n * Map and flatten only odd numbers to the sequence `'a'`, `'b'`, `'c'`\n *\n * ```ts\n * import { interval, mergeMap, of, EMPTY } from 'rxjs';\n *\n * const interval$ = interval(1000);\n * const result = interval$.pipe(\n * mergeMap(x => x % 2 === 1 ? of('a', 'b', 'c') : EMPTY),\n * );\n * result.subscribe(x => console.log(x));\n *\n * // Results in the following to the console:\n * // x is equal to the count on the interval, e.g. (0, 1, 2, 3, ...)\n * // x will occur every 1000ms\n * // if x % 2 is equal to 1, print a, b, c (each on its own)\n * // if x % 2 is not equal to 1, nothing will be output\n * ```\n *\n * @see {@link Observable}\n * @see {@link NEVER}\n * @see {@link of}\n * @see {@link throwError}\n */\nexport const EMPTY = new Observable((subscriber) => subscriber.complete());\n\n/**\n * @param scheduler A {@link SchedulerLike} to use for scheduling\n * the emission of the complete notification.\n * @deprecated Replaced with the {@link EMPTY} constant or {@link scheduled} (e.g. `scheduled([], scheduler)`). Will be removed in v8.\n */\nexport function empty(scheduler?: SchedulerLike) {\n return scheduler ? emptyScheduled(scheduler) : EMPTY;\n}\n\nfunction emptyScheduled(scheduler: SchedulerLike) {\n return new Observable((subscriber) => scheduler.schedule(() => subscriber.complete()));\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport function isScheduler(value: any): value is SchedulerLike {\n return value && isFunction(value.schedule);\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\nimport { isScheduler } from './isScheduler';\n\nfunction last(arr: T[]): T | undefined {\n return arr[arr.length - 1];\n}\n\nexport function popResultSelector(args: any[]): ((...args: unknown[]) => unknown) | undefined {\n return isFunction(last(args)) ? args.pop() : undefined;\n}\n\nexport function popScheduler(args: any[]): SchedulerLike | undefined {\n return isScheduler(last(args)) ? args.pop() : undefined;\n}\n\nexport function popNumber(args: any[], defaultValue: number): number {\n return typeof last(args) === 'number' ? args.pop()! : defaultValue;\n}\n", "export const isArrayLike = ((x: any): x is ArrayLike => x && typeof x.length === 'number' && typeof x !== 'function');", "import { isFunction } from \"./isFunction\";\n\n/**\n * Tests to see if the object is \"thennable\".\n * @param value the object to test\n */\nexport function isPromise(value: any): value is PromiseLike {\n return isFunction(value?.then);\n}\n", "import { InteropObservable } from '../types';\nimport { observable as Symbol_observable } from '../symbol/observable';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being Observable (but not necessary an Rx Observable) */\nexport function isInteropObservable(input: any): input is InteropObservable {\n return isFunction(input[Symbol_observable]);\n}\n", "import { isFunction } from './isFunction';\n\nexport function isAsyncIterable(obj: any): obj is AsyncIterable {\n return Symbol.asyncIterator && isFunction(obj?.[Symbol.asyncIterator]);\n}\n", "/**\n * Creates the TypeError to throw if an invalid object is passed to `from` or `scheduled`.\n * @param input The object that was passed.\n */\nexport function createInvalidObservableTypeError(input: any) {\n // TODO: We should create error codes that can be looked up, so this can be less verbose.\n return new TypeError(\n `You provided ${\n input !== null && typeof input === 'object' ? 'an invalid object' : `'${input}'`\n } where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.`\n );\n}\n", "export function getSymbolIterator(): symbol {\n if (typeof Symbol !== 'function' || !Symbol.iterator) {\n return '@@iterator' as any;\n }\n\n return Symbol.iterator;\n}\n\nexport const iterator = getSymbolIterator();\n", "import { iterator as Symbol_iterator } from '../symbol/iterator';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being an Iterable */\nexport function isIterable(input: any): input is Iterable {\n return isFunction(input?.[Symbol_iterator]);\n}\n", "import { ReadableStreamLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport async function* readableStreamLikeToAsyncGenerator(readableStream: ReadableStreamLike): AsyncGenerator {\n const reader = readableStream.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n return;\n }\n yield value!;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\nexport function isReadableStreamLike(obj: any): obj is ReadableStreamLike {\n // We don't want to use instanceof checks because they would return\n // false for instances from another Realm, like an