huge refactor of the entire codebase, with the goal of improving maintainability, readability, and extensibility. This commit includes changes to almost every file in the project, including:

This commit is contained in:
2026-04-19 00:41:09 -07:00
parent d9e736172a
commit bafd37fdfb
50 changed files with 3258 additions and 4177 deletions
+20 -21
View File
@@ -1,23 +1,23 @@
# Provider Development Guide
# Plugin Development Guide
## 🎯 Purpose
This guide describes how to write, test, and register a provider so the application can discover and use it as a pluggable component.
This guide describes how to write, test, and register a plugin so the application can discover and use it as a pluggable component.
> Keep provider code small, focused, and well-tested. Use existing providers as examples.
> Keep plugin code small, focused, and well-tested. Built-in plugins live in `Provider/` and external drop-in plugins live under `plugins/`.
---
## 🔧 Anatomy of a Provider
A provider is a Python class that extends `ProviderCore.base.Provider` and implements a few key methods and attributes.
## 🔧 Anatomy of a Plugin
A plugin is a Python class that extends `ProviderCore.base.Provider` and implements a few key methods and attributes.
Minimum expectations:
- `class MyProvider(Provider):` — subclass the base provider
- `class MyPlugin(Provider):` — subclass the base plugin class
- `URL` / `URL_DOMAINS` or `url_patterns()` — to let the registry route URLs
- `validate(self) -> bool` — return True when provider is configured and usable
- `validate(self) -> bool` — return True when the plugin is configured and usable
- `search(self, query, limit=50, filters=None, **kwargs)` — return a list of `SearchResult`
Optional but common:
- `download(self, result: SearchResult, output_dir: Path) -> Optional[Path]` — download a provider result
- `download(self, result: SearchResult, output_dir: Path) -> Optional[Path]` — download a plugin result
- `selector(self, selected_items, *, ctx, stage_is_last=True, **kwargs) -> bool` — handle `@N` selections
- `download_url(self, url, output_dir, progress_cb=None)` — direct URL-handling helper
@@ -71,8 +71,8 @@ class HelloProvider(Provider):
---
## ⬇️ Implementing download() and download_url()
- Prefer provider `download(self, result, output_dir)` for piped provider items.
- For provider-provided URLs, implement `download_url` to allow `download-file` to route downloads through providers.
- Prefer plugin `download(self, result, output_dir)` for piped plugin items.
- For plugin-provided URLs, implement `download_url` to allow `download-file` to route downloads through plugins.
- Use the repo `_download_direct_file` helper for HTTP downloads when possible.
Example download():
@@ -90,12 +90,12 @@ def download(self, result: SearchResult, output_dir: Path) -> Optional[Path]:
---
## 🧭 URL routing
Providers can declare:
Plugins can declare:
- `URL = ("magnet:",)` or similar prefix list
- `URL_DOMAINS = ("example.com",)` to match hosts
- Or override `@classmethod def url_patterns(cls):` to combine static and dynamic patterns
The registry uses these to match `download-file <url>` or to pick which provider should handle the URL.
The registry uses these to match `download-file <url>` or to pick which plugin should handle the URL.
---
@@ -106,8 +106,8 @@ The registry uses these to match `download-file <url>` or to pick which provider
---
## 🧪 Testing providers
- Keep tests small and local. Create `tests/test_provider_<name>.py`.
## 🧪 Testing plugins
- Keep tests small and local. Create `tests/test_provider_<name>.py` or another tracked test target.
- Test `search()` with mock HTTP responses (use `requests-mock` or similar).
- Test `download()` using a temp directory and a small file server or by mocking `_download_direct_file`.
- Test `selector()` by constructing a fake result and `ctx` object.
@@ -125,10 +125,9 @@ pytest -q
---
## 📦 Registration & packaging
- Add your provider module under `Provider/` and ensure it is imported by module package initialization. Common approach:
- Place file `Provider/myprovider.py`
- Ensure `Provider/__init__.py` imports the module (or the registry auto-discovers by package import)
- If the project has a central provider registry, add lookup helpers there (e.g., `ProviderCore/registry.py`). Usually providers register themselves at import time.
- Built-in plugins live under `Provider/` and are auto-discovered from that package.
- External user plugins can be dropped into `plugins/` or any directory listed in `MM_PLUGIN_PATH` / `MEDEIA_PLUGIN_PATH`.
- Plugin authors should import from `ProviderCore.*`.
---
@@ -147,19 +146,19 @@ pytest -q
- [ ] Provide `URL` / `URL_DOMAINS` or `url_patterns()` for routing
- [ ] Add `download()` or `download_url()` for piped/passed URL downloads
- [ ] Add tests under `tests/`
- [ ] Add module to `Provider/` package and ensure import/registration
- [ ] Add the plugin module to `Provider/` for built-ins, or drop it into `plugins/` for plug-and-play user installs
---
## 🔗 Further reading
- See existing providers in `Provider/` for patterns and edge cases.
- See existing built-in plugins in `Provider/` for patterns and edge cases.
- Check `API/` helpers for HTTP and debrid clients.
---
If you'd like, I can:
- Add an example provider file under `Provider/` as a template (see `Provider/hello_provider.py`), and
- Add an example plugin file under `Provider/` as a template (see `Provider/hello_provider.py`), and
- Create unit tests for it (see `tests/test_provider_hello.py`).
I have added a minimal example provider and tests in this repository; use them as a starting point for new providers.