feat(search): add ANONYMOUS_SEARCH to skip the login for a service

A service that sets the flag gets no cookie load, no authenticate() call and no cookie save on a search, on the CLI and in the REST search handler. /api/services reports it as anonymous_search.
This commit is contained in:
imSp4rky
2026-10-10 13:21:14 -06:00
parent 0cded6427f
commit 0a7c76561c
12 changed files with 121 additions and 13 deletions
+7
View File
@@ -100,6 +100,7 @@ Declared at class level to configure framework behaviour:
| `GEOBLOCK` | `tuple[str, ...]` | IP region codes where the service refuses to work. Every other region is allowed. Default `()`. |
| `VAULT_TAG` | `Optional[str]` | Overrides the key-vault namespace so sibling services can share one vault. Default `None` (use the service's own tag). |
| `AUTH_METHODS` | `Optional[tuple[str, ...]]` | Auth methods accepted (`"cookies"` / `"credentials"`). When `None`, the REST `/services` endpoint infers them from `authenticate()`. |
| `ANONYMOUS_SEARCH` | `bool` | Set `True` when `search()` needs no login and no state from `authenticate()`. A search then skips the cookie load, `authenticate()` and the cookie save. Default `False`. |
| `NO_SUBTITLES` | `bool` | Set `True` on a service with no subtitle tracks to skip subtitle handling entirely. |
| `ANIME` | `bool` | Set `True` when the catalogue is anime, so metadata lookups prefer AniList. A title's own `anime` flag overrides it. |
| `DAILY` | `bool` | Set `True` when the catalogue is daily/date-based (talk shows, news, sports), so unshackle names episodes by air date. A title's own `daily` flag overrides it. |
@@ -609,6 +610,12 @@ Override any of these to react to pipeline stages (all no-ops by default):
`on_track_repacked`, `on_track_multiplex`. Also override `search()` to yield
`SearchResult` objects for `unshackle search`.
!!! warning "Set `ANONYMOUS_SEARCH` only for a search that needs no account"
With `ANONYMOUS_SEARCH = True`, `search()` operates on an HTTP session with no cookies, and
`authenticate()` does not run. Set it only when the search results do not depend on the
account, and when `search()` does its own setup. A `search()` that reads a token, a header
or a region from `authenticate()` fails or gives results for the wrong region.
```python
def search(self) -> Generator[SearchResult, None, None]:
results = self.session.get(self.config["endpoints"]["search"],
+4
View File
@@ -98,6 +98,7 @@ Show the streaming services available on this server, filtered by your allowlist
],
"needs_auth": true,
"has_search": true,
"anonymous_search": false,
"has_drm": true,
"auth_methods": ["cookies"]
}
@@ -120,6 +121,7 @@ Field notes:
| `cli_params` | Array of parameter descriptors. Arguments have `{name, kind: "argument", required}`; options have `{name, kind: "option", opts, is_flag, default, help, type, multiple}` and, for choice options, a `choices` list. |
| `needs_auth` | Whether the service overrides `authenticate()`. |
| `has_search` | Whether the service supports `POST /api/search`. |
| `anonymous_search` | Whether `POST /api/search` operates with no login for this service. The server then ignores `credentials` and `cookies` on a search. |
| `has_drm` | Whether the service overrides a Widevine or PlayReady license hook. |
| `pending_update` | Set to `true` while a service repository refresh waits for this service's jobs to finish before it swaps in the new code. Absent otherwise. |
| `server_accounts` | Present when the server lends its own accounts for this service: `{regions: ["ca", "gb"], global: bool}`, the regions those accounts cover. A client sends no credentials, cookies, or cache for such a service. |
@@ -134,6 +136,8 @@ Field notes:
Find titles in a service by query string. The service must have a `search()` method (see `has_search` above).
For a service with `anonymous_search`, the server does not log in and ignores `credentials` and `cookies`. The server account check still applies.
**Request body**
| Field | Type | Required | Default | Description |
+1
View File
@@ -150,6 +150,7 @@ curl "$BASE/api/services" \
"help": "EXAMPLE service documentation...",
"needs_auth": true,
"has_search": true,
"anonymous_search": false,
"has_drm": true,
"auth_methods": ["cookies", "credentials"],
"cli_params": []
+3 -1
View File
@@ -66,6 +66,7 @@ Declared at the top of your service class, these are static descriptors:
| `GEOBLOCK` | `tuple[str, ...]` | ISO region codes where the service refuses to work; every other region is allowed. Use it for a service that works worldwide except in its home market. |
| `VAULT_TAG` | `Optional[str]` | Overrides the key-vault namespace; defaults to the service's own tag. |
| `AUTH_METHODS` | `Optional[tuple[str, ...]]` | Accepted auth methods (`"cookies"` / `"credentials"`). When `None`, the REST `/services` endpoint infers them from `authenticate()`. |
| `ANONYMOUS_SEARCH` | `bool` | `search()` needs no login and no state from `authenticate()`. A search then skips the cookie load, `authenticate()` and the cookie save. Default `False`. |
| `ANIME` | `bool` | The catalogue is anime, so metadata lookups prefer AniList. A title's own `anime` flag overrides it. |
| `DAILY` | `bool` | The catalogue is daily/date-based, so unshackle names episodes by air date. A title's own `daily` flag overrides it. |
@@ -83,7 +84,8 @@ These are abstract. A service will not load unless it defines all three:
`CookieJar` into `self.session.cookies` and stores `self.credential`. Override it to
do a real login handshake.
- **`search()`**: raises `NotImplementedError` by default. Write it so that the
`search` command can operate.
`search` command can operate. unshackle calls `authenticate()` first, unless the
service sets `ANONYMOUS_SEARCH`.
- **DRM license hooks**: `get_widevine_service_certificate`, `get_widevine_license`,
`get_playready_license` (delegates to Widevine by default), and
`get_clearkey_license` (returns `None` by default). See [DRM hooks](#drm-hooks) below.