Metadata-Version: 2.4
Name: yap_torrent_transmission_rpc
Version: 0.1.1
Summary: Transmission RPC server API and web interface for yap torrent
Project-URL: Homepage, https://github.com/Angel777d/yap_torrent/tree/master/plugins/transmission_rpc
Project-URL: Issues, https://github.com/Angel777d/yap_torrent/issues
Author-email: Angelovich <angel777da@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: BitTorrent,RPC,Torrent,Transmission,Web Interface
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Education
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.13
Requires-Dist: aiohttp
Requires-Dist: yap-torrent>=0.2.0
Provides-Extra: test
Requires-Dist: pytest; extra == 'test'
Requires-Dist: pytest-aiohttp; extra == 'test'
Description-Content-Type: text/markdown

# yap_torrent Transmission RPC

A [yap_torrent](https://github.com/Angel777d/yap_torrent) plugin that exposes a
[Transmission RPC](https://github.com/transmission/transmission/blob/main/docs/rpc-spec.md)
compatible server, so existing Transmission remote clients (Transmission Remote GUI,
`transmission-remote`, the `transmission-rpc` Python library, mobile remotes, …) can
drive yap_torrent — **and a browser UI, served on the same port, that is itself one of
those clients.**

## Installation

```bash
pip install yap_torrent_transmission_rpc
```

Or from a checkout, for development:

```bash
pip install -e plugins/transmission_rpc
```

The plugin is discovered automatically through the `yap_torrent.plugins` entry point.
Disable it by adding `yap_torrent_transmission_rpc` to `disabled_plugins` in `config.json`.

## Configuration

Add a `yap_torrent_transmission_rpc` block to `config.json`:

```json
{
  "yap_torrent_transmission_rpc": {
    "host": "0.0.0.0",
    "port": 9091,
    "path": "/transmission/rpc",
    "web_enabled": true,
    "web_path": "/transmission/web",
    "username": null,
    "password": null
  }
}
```

- `host` / `port` — where the server binds (Transmission's default port is `9091`).
- `path` — RPC endpoint path (Transmission's default is `/transmission/rpc`).
- `web_enabled` — serve the bundled browser UI. Turning it off leaves the RPC untouched.
- `web_path` — where the UI is mounted. `/` redirects to it.
- `username` / `password` — **reserved for a future HTTP Basic auth implementation.**
  They are read but not enforced yet; the endpoint is currently unauthenticated.

Point a remote at `http://<host>:9091/transmission/rpc`, or a browser at
`http://<host>:9091/`.

## Web interface

The UI lives in `src/yap_torrent_transmission_rpc/html/` and is a **pure RPC client** — it
has no endpoint of its own and reads nothing out of the ECS. Everything it shows comes from
`torrent-get` / `session-get`, and everything it does goes out as a spec method, which is
why it lives here rather than in a plugin of its own: a field added for a Transmission
remote is a field the browser can show, and a torrent becomes JSON in exactly one place
(`mapping.py`). It replaces the old `yap_torrent_web` plugin, which derived the same values
a second time behind its own `/api/*` routes.

Sharing the RPC's port is what keeps it same-origin, so there is no CORS to configure and
the CSRF handshake below works from the page unchanged. The endpoint is substituted into
the page at serve time (the `rpc-path` meta tag), so a changed `path` needs no edit to the
script.

It covers: the torrent list with live rates, status and ETA, refreshed every 2s; add by
magnet, URL or `.torrent` upload; start / stop / verify / reannounce; remove, with or
without the data; queue reordering; per-file wanted flags and priorities; labels; tracker
state; and a session settings panel. Multi-select with ctrl/cmd-click applies an action to
every selected torrent.

The settings panel says plainly which values do nothing yet, because `session-set` answers
`success` for all of them: the peer port and DHT are start-up-only (saved, but the running
client keeps its values), and the speed, queue, ratio and peer limits are stored and
reported back but enforced by nothing.

## CSRF handshake

Per the spec, the server issues an `X-Transmission-Session-Id`. The first request (or one
with a stale id) receives an HTTP `409` carrying the current id in its headers; well-behaved
clients transparently retry with it. This is handled automatically for real clients.

## Supported methods

This targets the **legacy** Transmission RPC protocol — `method`/`arguments`/`tag` with
kebab-case names — which is what existing remotes and the `transmission-rpc` Python client
speak. Transmission 4.1 deprecates it in favour of JSON-RPC 2.0 with snake_case names
(`rpc_version` 19); that is a separate surface, not a newer version of this one. The
supported range is declared by `RPC_VERSION_MIN_SUPPORTED` / `RPC_VERSION_MAX_SUPPORTED`
in `methods.py` (14–17, semver 5.3.0 — the semver must track the max per the spec's
version table, because clients gate features on it).

Implemented: `torrent-add`, `torrent-remove`, `torrent-start`, `torrent-start-now`,
`torrent-stop`, `torrent-verify`, `torrent-get`, `torrent-set`, `torrent-reannounce`,
`queue-move-top`, `queue-move-up`, `queue-move-down`, `queue-move-bottom`, `session-get`,
`session-set`, `session-stats`, `free-space`, `port-test`.

`torrent-set` — and `torrent-add`, which takes the same arguments — applies `labels`,
`files-wanted`, `files-unwanted`, `priority-high|normal|low` and `queuePosition`, and
**stores** `downloadLimit`, `uploadLimit`, `honorsSessionLimits`, `seedRatioLimit`,
`seedRatioMode`, `peer-limit` and `bandwidthPriority` without enforcing them: core has no
bandwidth limiting or ratio tracking, so the values round-trip and core logs a warning
naming each one. `location` and the deprecated tracker edits are ignored.

`session-set` writes through to core's config (which persists to `config.json`). Speed
limits, queue sizes and the blocklist keys are stored but not enforced, and core warns on
each change.

**The speed model is mostly the plugin's.** Core holds one number per direction where
**0 means no limit** — no separate on/off flag, and no second "alternative" pair. The
plugin translates:

- `speed-limit-down` / `speed-limit-down-enabled` (and the `-up` pair) fold into core's
  single number. Switching a limit off writes 0; the number you typed is remembered in the
  plugin's config section, so it is still in the box afterwards and comes back when you
  switch the limit on again. A number sent **without** the flag does not switch a disabled
  limit on — that is what the flag is for — it is only remembered.
- `alt-speed-down` / `alt-speed-up` / `alt-speed-enabled` (turtle mode) never reach core at
  all.

Both the turtle values and the remembered numbers live in `SpeedSettingsEC`, a **singleton
component in the shared ECS** — one instance for the whole app, reachable with
`get_speed_settings(env)`, so anything else that wants to know whether turtle mode is on
reads the same object. It is **runtime state**: seeded from the
`yap_torrent_transmission_rpc` section of `config.json` at startup and never written back,
because turning turtle mode on is something you do now, not something the next run should
inherit. The only speed value that outlives the process is the one core already keeps — the
limit actually in force.

Once core enforces limits, enabling turtle mode should push the alt pair into core's
`speed_limit_*` and restore the normal pair on the way out — see the `SpeedSettingsEC` TODO.

Every other spec method is recognised but returns an explanatory error string (it is *not*
treated as an unknown method). See `UNIMPLEMENTED` in `methods.py` for the list and the notes
on what each one needs. `torrent-add` accepts a magnet link or `.torrent` path/URL via the
`filename` field, or base64 `.torrent` content via `metainfo`.

`torrent-get` supports both `format: "objects"` and `format: "table"`, and reports live
transfer rates and ETA, the added/started/done/activity dates, queue position, labels,
exact per-file `bytesCompleted` with each file's wanted flag and priority, per-tracker
announce state, and tracker errors via `error`/`errorString`. `percentDone`,
`sizeWhenDone` and `leftUntilDone` are relative to the files the user **wants**, per
`tr_stat`; `percentComplete` is of the whole torrent. Every documented field is answered —
absent keys are not safe, since `transmission-rpc` reads them as `self.fields[name]` and
raises `KeyError`. Still placeholder: the `peers` detail list, `availability`,
`secondsDownloading` / `secondsSeeding`, and the seed-idle fields.

## Tests

```bash
# only the test runner is needed; yap_torrent / angelovich.core are imported
# from source by conftest.py (no package install required)
pip install pytest pytest-aiohttp
pytest plugins/transmission_rpc          # test_rpc.py: the methods; test_web_ui.py: the UI routes

# manual smoke test against a running instance:
python plugins/transmission_rpc/scripts/manual_test.py --url http://127.0.0.1:9091/transmission/rpc
```
