developing a plugin - degoya/rDownloader GitHub Wiki
rdownloader plugin new scaffolds a plugin that builds, packages and passes conformance before
you change a line of it โ so the first failure you see is about your code, not about the setup.
| Tool | Version |
|---|---|
| Rust | 1.99, Edition 2024 |
| Target |
wasm32-unknown-unknown (rustup target add) |
wasm-tools |
1.261.0 โ the version the bundled plugins are made with; it makes the component and checks its imports |
rdownloader plugin new --type resolver --out ./myhoster
cd myhoster
cargo build --release --target wasm32-unknown-unknown
wasm-tools component new target/wasm32-unknown-unknown/release/myhoster.wasm -o target/myhoster.wasm--type takes every world of the contract, thirteen in all, and each has a page of its own under
Plugin development that goes from the scaffold to a published package:
--type |
What it does |
|---|---|
resolver |
Turns a hoster link into a downloadable address and checks links |
transfer |
Carries the bytes of a protocol the application does not know |
intake |
Turns text and URLs into LinkGrabber candidates |
intake-mirrors |
An intake parser that also states every source of a file |
crawler |
Turns one address into the files behind it |
remote-job |
Runs a job that lives at a provider and outlives the call |
auth |
Signs an account in through the provider's own flow |
oauth |
Signs an account in by OAuth and renews the token |
enricher |
Adds metadata to a link |
notifier |
Delivers notifications to one more destination |
postprocess |
Runs one more step after a package has been downloaded |
storage |
Uploads finished packages somewhere |
stream-transform |
Answers with an address and how its bytes become a file |
The directory name becomes the crate name and the manifest's provider slug; --name sets a
different one.
plugin newreads its templates fromsdk/templates, so run it from a checkout of the repository or from a directory holding a copy of that folder. A release binary on its own does not carry them.
The scaffold brings its own copy of the WIT contract, rdownloader:[email protected], in wit/, so nothing about the build
needs a checkout of rDownloader, and its own Ed25519 key pair in plugin-signing.key โ which
belongs in your secret store, not in version control. plugin new writes a .gitignore before
the key, listing plugin-signing.key, target/ and *.rdplug, so a first git add . leaves the
key out; keep those lines when you edit the file.
cargo build --release --target wasm32-unknown-unknown
wasm-tools component new target/wasm32-unknown-unknown/release/myhoster.wasm -o target/myhoster.wasmcargo build makes a WebAssembly core module; wit_bindgen::generate! in src/guest.rs has
embedded the world it implements, so wasm-tools component new needs neither a world nor a WASI
adapter to turn it into the component, and the finished plugin imports only the WIT host
contract.
A component that imports anything outside rdownloader:plugin is refused โ when it is
packaged, and again when it is installed. That includes a wasi: interface pulled in by a
dependency you did not choose directly.
scripts/check-plugin-imports.sh reads the import section with wasm-tools and names the
offending interface. The CI workflow template runs the same check.
Each grant in [capabilities] is one WIT interface.
- A component importing an interface its manifest does not grant is refused at packaging time and again at install time, so asking for less than you use fails at build time, not at a user's download.
- Ask for exactly what you use. The plugin manager shows your grants to the person deciding whether to install you.
- The file system, other processes, and the network beyond your declared domains are not capabilities that happen to be denied. There is no interface for them.
The scaffold ships locales/en.json. English is the one language a localised plugin must
carry โ a package with any locale file and no en.json is refused.
The interface is offered in German, English, Spanish and French, and the bundled plugins ship all
four as de.json, en.json, es.json and fr.json. A language a plugin lacks falls back to
English.
Each scaffold is a small, working plugin of its type rather than a set of signatures: a PIN
sign-in, a resumable chunked upload, a CRC-32 over a package, a bounded folder walk, a key read out
of a link's fragment. The promises the host makes to the type are written at the top of
src/lib.rs; they are what shapes the code, so read them before changing it.
Everything that can run without a WebAssembly toolchain lives outside the component, and
src/guest.rs โ the translation into the contract's types โ exists only on wasm32, so
cargo test runs a fresh scaffold's unit tests straight away:
rdownloader plugin new --type oauth --out ./myprovider
cd myprovider && cargo testEvery scaffold carries a README.md naming its files and the rules worth knowing first. The
templates themselves are in
sdk/templates.
rdownloader plugin conformance myhoster.rdplug --jsonAnswers machine-readably whether this application would run your package. Run it before you publish anything. Besides what installation checks, it asks a resolver or a crawler whether it claims links on hosts it does not declare, and checks that each German, Spanish or French catalogue you ship carries every code your English one does โ a language you do not ship falls back to English.
Every other type answers one question of its own, without a network: the run has none, and
every request your plugin makes is refused. Each question is a check with a stable id in the
--json report, so a CI job can act on the one that failed. A crash (a trap) never counts as a
refusal.
| Type | Check id
|
What is asked |
|---|---|---|
| notifier | notifier_empty_destination |
A delivery to an empty destination is refused, not reported as delivered |
| auth | auth_no_credentials |
A sign-in begun without credentials authorizes no account |
| intake | intake_unknown_input |
Text in no format the parser reads yields no candidate |
| transfer | transfer_undeclared_scheme |
An address in a scheme the manifest does not declare is refused |
| storage | storage_unreached_verify |
verify never confirms an upload it could not reach |
| remote-job | remote_job_containers |
A .torrent and an .nzb are claimed exactly when [extension] containers declares them |
| enricher | enricher_empty_input |
An empty link gets no fields, and no failure |
| postprocess | postprocess_empty_input |
A package without files finishes or is skipped, with no failure and no warning |
| stream-transform | stream_transform_empty_input |
The empty address is not claimed, and asking does not fail |
An OAuth plugin is instantiated against its world; it has no question of its own yet.
[limits] in your manifest names only what you change. Each field you leave out, or the whole
table, is the host's default:
| Field | Default | Most a manifest may declare |
|---|---|---|
memory_bytes |
64 MiB | 512 MiB |
fuel |
2 000 000 000 | 40 000 000 000 |
timeout_milliseconds |
15 000 (15 s) | 1 800 000 (30 min) |
max_response_bytes |
8 MiB | 64 MiB |
wait_budget_milliseconds |
600 000 (10 min) | 1 800 000 (30 min) |
A value above the last column is refused when the plugin is packaged.
Every other field of your manifest stays yours to state: the identity, the signing key and
[metadata]. The bundled plugins share theirs through a file of their own repository, which
plugin package --defaults writes into the packaged copy; a plugin of your own needs no such file.
User-facing text never lives in the plugin as prose. It lives as a stable code, translated in the plugin's own catalogue โ the same rule the application follows.
An error value a provider wrote that is not code-shaped is dropped whole rather than filtered: filtering keeps the digits of a leaked credential.
Read an HTTP status nothing else explains the way the bundled plugins do, so a person sees the same outcome whichever plugin served the link:
| Status | Category | Why |
|---|---|---|
401, 403
|
account-invalid โ or a plain HTTP error (permanent) when the request carried no account |
A bot wall must not send anybody to fix credentials they never gave |
404, 410
|
permanent |
The file is gone for good; it is not retried. A link check still reports it offline
|
451 |
offline |
Withheld for legal reasons, often regionally; it is retried |
429 |
rate-limited, with the provider's Retry-After
|
|
5xx |
transient, with the provider's Retry-After where one was sent |
Take Retry-After in seconds, at most one day โ the host holds every wait to a day anyway โ and
read Retry-After: 0 as your own default wait, not as "retry at once".
rdownloader serve --plugin-development-modeaccepts unsigned packages. Package with --development to produce one. Local development only.
- The page of your type under Plugin development, from the table under Scaffolding
- Plugin overview โ what each type may and may not do
- Plugin reference โ the manifest field by field, every plugin type, the contract
- Packaging, signing and distribution
- Installing and trust
Reference in the repository:
sdk/README.md ยท
rdownloader.wit,
the contract