Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ Releases of this fork. Images: `ghcr.io/mikaeltarquin/shelfmark:<version>` and `

## Unreleased

### Added

- **The Queued page shows the size of the queue and when each item fits your ratio.**
At the top: the total size, how much counts against your MyAnonamouse ratio, the room
left before it drops below **Keep Ratio At Least**, and the upload credit the whole
queue needs. Each saved item gets an **ETA**, when both the ratio and an unsatisfied slot
allow it to start: from the upload credit it needs and (for admins) how long the bonus
points take to earn, and from when torrents seeding in your client reach 72 hours.
- **MyAnonamouse has its own settings tab**, under **Settings → Indexers**, after Prowlarr.

### Changed

- **The MyAnonamouse panel's Buffer is now Room to your ratio**: how much more can be
downloaded before the ratio drops below **Keep Ratio At Least**, counting Shelfmark's
active MAM downloads. The plain buffer is in its tooltip.

### Fixed

- **MyAnonamouse downloads no longer run past the unsatisfied limit when MAM's count
Expand Down
12 changes: 6 additions & 6 deletions docs/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,8 +348,8 @@ Audiobook formats to include in search results. ZIP/RAR archives are extracted a
| `METADATA_PROVIDER_COMBINED` | Metadata provider for combined mode searches. Uses the book provider if not set. | string (choice) | _empty string_ |
| `DEFAULT_RELEASE_SOURCE` | The release source tab to open by default in the release modal for books. Leave unset to use the first available source. | string (choice) | _empty string_ |
| `DEFAULT_RELEASE_SOURCE_AUDIOBOOK` | The release source tab to open by default in the release modal for audiobooks. Uses the book release source if not set. | string (choice) | _empty string_ |
| `SHOW_SERIES_COLUMN` | Series name and number, for books and audiobooks. Filled in for MyAnonamouse results from Prowlarr when a MAM session ID is set in the Prowlarr settings. | boolean | `true` |
| `SHOW_NARRATOR_COLUMN` | Audiobooks only. Filled in for MyAnonamouse results from Prowlarr when a MAM session ID is set in the Prowlarr settings. | boolean | `true` |
| `SHOW_SERIES_COLUMN` | Series name and number, for books and audiobooks. Filled in for MyAnonamouse results from Prowlarr when a MAM session ID is set in the MyAnonamouse settings. | boolean | `true` |
| `SHOW_NARRATOR_COLUMN` | Audiobooks only. Filled in for MyAnonamouse results from Prowlarr when a MAM session ID is set in the MyAnonamouse settings. | boolean | `true` |
| `SHOW_BITRATE_COLUMN` | Audiobooks only. AudiobookBay lists it for most releases. In Prowlarr results it needs a MAM session ID and is read from the MyAnonamouse uploader's tags, so some releases won't have one. Other Prowlarr indexers fill it only if they report a bitrate attribute, which most don't. | boolean | `true` |

<details>
Expand Down Expand Up @@ -475,7 +475,7 @@ The release source tab to open by default in the release modal for audiobooks. U

**Show Series Column**

Series name and number, for books and audiobooks. Filled in for MyAnonamouse results from Prowlarr when a MAM session ID is set in the Prowlarr settings.
Series name and number, for books and audiobooks. Filled in for MyAnonamouse results from Prowlarr when a MAM session ID is set in the MyAnonamouse settings.

- **Type:** boolean
- **Default:** `true`
Expand All @@ -484,7 +484,7 @@ Series name and number, for books and audiobooks. Filled in for MyAnonamouse res

**Show Narrator Column**

Audiobooks only. Filled in for MyAnonamouse results from Prowlarr when a MAM session ID is set in the Prowlarr settings.
Audiobooks only. Filled in for MyAnonamouse results from Prowlarr when a MAM session ID is set in the MyAnonamouse settings.

- **Type:** boolean
- **Default:** `true`
Expand Down Expand Up @@ -1355,7 +1355,7 @@ How long to cache individual book details. Default: 600 (10 minutes). Max: 60480
| `PROWLARR_USE_SEED_PREFERENCES` | Apply per-indexer seed time and ratio preferences from Prowlarr when sending torrents to the download client | boolean | `false` |
| `PROWLARR_MAM_ID` | The mam_id value MyAnonamouse shows when you create a session. MAM locks each session to one IP or ASN, so reusing Prowlarr's or another client's session often fails with a 403: you will likely need a separate session for Shelfmark if it reaches MAM from another IP (different host, VPN or proxy) or the existing session is ASN-locked to another network. Leave empty to turn enrichment off. | string (secret) | _none_ |
| `MAM_BLOCK_ON_LOW_BUFFER` | Before queueing MyAnonamouse torrents, check that they fit in the account's buffer (uploaded minus downloaded), counting Shelfmark's MAM downloads that are still active. If not, admins are offered upload credit to buy first; other users are asked to contact an admin. Freeleech torrents don't count. If MAM can't be reached, downloads go ahead. | boolean | `true` |
| `MAM_BLOCK_ON_UNSAT_LIMIT` | MyAnonamouse limits how many torrents can be unsatisfied (not yet seeded 72 hours) at once, by user class. Before queueing MAM torrents (freeleech too), check that they, plus Shelfmark's MAM downloads not yet started, leave the slots below free. Buying upload credit doesn't help here; wait for torrents to finish seeding. | boolean | `true` |
| `MAM_BLOCK_ON_UNSAT_LIMIT` | MyAnonamouse limits how many torrents can be unsatisfied (not yet seeded 72 hours) at once, by user class. Before queueing MAM torrents (freeleech too), check that they, plus Shelfmark's MAM downloads not yet started, leave the slots below free. Buying upload credit doesn't help here; wait for torrents to finish seeding. Queued MAM downloads also wait while MAM can't be read, rejects the torrent client's announces, or has frozen downloads. | boolean | `true` |
| `MAM_UNSAT_RESERVE_SLOTS` | Hold back downloads that would leave fewer free unsatisfied slots than this. | number | `5` |
| `SAVED_AUTO_GET_ENABLED` | Turn off to pause every automatic download; items stay marked. | boolean | `true` |
| `SAVED_AUTO_GET_TARGET_RATIO` | Non-freeleech downloads that would take the ratio below this wait, unless they're ebook-sized (100 MB or less) and lower it by less than 0.01. 0 turns the ratio check off. | number | `2.0` |
Expand Down Expand Up @@ -1472,7 +1472,7 @@ Before queueing MyAnonamouse torrents, check that they fit in the account's buff

**Hold Back Downloads at the Unsatisfied Limit**

MyAnonamouse limits how many torrents can be unsatisfied (not yet seeded 72 hours) at once, by user class. Before queueing MAM torrents (freeleech too), check that they, plus Shelfmark's MAM downloads not yet started, leave the slots below free. Buying upload credit doesn't help here; wait for torrents to finish seeding.
MyAnonamouse limits how many torrents can be unsatisfied (not yet seeded 72 hours) at once, by user class. Before queueing MAM torrents (freeleech too), check that they, plus Shelfmark's MAM downloads not yet started, leave the slots below free. Buying upload credit doesn't help here; wait for torrents to finish seeding. Queued MAM downloads also wait while MAM can't be read, rejects the torrent client's announces, or has frozen downloads.

- **Type:** boolean
- **Default:** `true`
Expand Down
6 changes: 3 additions & 3 deletions docs/myanonamouse-enrichment.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Only MyAnonamouse results are enriched. Other Prowlarr indexers fill the bitrate
## Setup

1. On MyAnonamouse, open **Preferences > Security** and create a session for Shelfmark. Lock it to the public IP (or ASN) Shelfmark connects from.
2. In Shelfmark, open **Settings > Prowlarr > MyAnonamouse Enrichment**, paste the `mam_id` value into **MAM Session ID** (or set `PROWLARR_MAM_ID`), and click **Test MAM Session**.
2. In Shelfmark, open **Settings > Indexers > MyAnonamouse**, paste the `mam_id` value into **MAM Session ID** (or set `PROWLARR_MAM_ID`), and click **Test MAM Session**.
3. Choose which columns appear under **Settings > Search Mode > Release List Columns** (`SHOW_SERIES_COLUMN`, `SHOW_NARRATOR_COLUMN`, `SHOW_BITRATE_COLUMN`). Users can override these for their own account.

## Use a session made for Shelfmark
Expand Down Expand Up @@ -51,7 +51,7 @@ figures) and unsatisfied count at a glance (amber or red as they near their limi
refreshed every 5 minutes), plus a countdown to the next unsatisfied slot freeing up, and
opens a panel with links to your MAM profile and the MAM store. The panel shows:

- **Account:** ratio, buffer (uploaded − downloaded), bonus points, uploaded, downloaded, class, VIP expiry and unsatisfied torrents against your class's limit (amber at 90%, red at the limit), from MAM's `jsonLoad.php?snatch_summary`. Refreshed at most once a minute unless you press **Refresh**.
- **Account:** ratio, **Room to** your ratio (how much more can be downloaded, not counting freeleech, before the ratio drops below **Keep Ratio At Least**, or 1.0 when that's off; it counts Shelfmark's active MAM downloads, and its tooltip shows the plain buffer, uploaded − downloaded), bonus points, uploaded, downloaded, class, VIP expiry and unsatisfied torrents against your class's limit (amber at 90%, red at the limit), from MAM's `jsonLoad.php?snatch_summary`. Refreshed at most once a minute unless you press **Refresh**.
- **Points / hour:** MAM's API has no earning rate, so Shelfmark estimates it from its own readings of your balance (taken hourly, kept for two days) over the last 24 hours. Points Shelfmark spent on upload credit are added back; a period where points dropped for another reason (spending on MAM's site) or hit MAM's 99,999 cap is left out. It shows once a few hours of readings exist.
- **Connections:** whether MAM accepts the session, and whether Shelfmark can reach its torrent client, each with the error when it can't.
- **Buy upload credit:** spends bonus points in MAM's store (500 points per GB). Pick 50, 100, 250 or 500 GB, a custom amount in multiples of 50 GB, or **Max affordable**. Nothing is bought until you confirm. Larger amounts are bought as 100 GB and 50 GB purchases, and a purchase stops at the first one MAM declines, reporting what was already added.
Expand All @@ -69,7 +69,7 @@ Active downloads are counted at their full size, because MAM's figures only incl

## Upload credit auto-buy

Under **Settings > Prowlarr > Upload Credit Auto-Buy**, Shelfmark can spend bonus points on upload credit by itself. Each mode is off until you turn it on:
Under **Settings > Indexers > MyAnonamouse > Upload Credit Auto-Buy**, Shelfmark can spend bonus points on upload credit by itself. Each mode is off until you turn it on:

| Mode | Buys when | Default threshold | Default amount |
|------|-----------|-------------------|----------------|
Expand Down
31 changes: 27 additions & 4 deletions docs/saved-for-later.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,40 @@ A saved item is one of two things, marked on books everywhere they show:

## The Queued and Wanted pages

**Activity → Queued** is everything waiting to download, in the order it goes:
**Activity → Queued** is everything waiting to download, in the order it goes. At the
top, its total size, and with MyAnonamouse set up: how much of the saved picks counts
against your ratio (freeleech doesn't), the room left before the ratio drops below **Keep
Ratio At Least**, and the upload credit it would take for all of it to download without
going under (with, for admins, the bonus points that costs and roughly how long earning
them takes at your points-per-hour rate), and when the last of it can start.

- **Up next**: downloads already handed to Shelfmark that haven't started, such as a
MyAnonamouse torrent waiting for an unsatisfied slot. They're numbered in the order they
start; drag a row by its handle (or use the arrows) to change it.
- **Waiting for room**: saved picks, numbered after them, each showing its **Releases** (one line per
release, with the format, narrator and a link to its page at the source, the torrent on
MyAnonamouse for example), what it's **Waiting for**, when it was **Last checked** and a
countdown to the **Next check**. Drag these to reorder them too: earlier ones get free
MyAnonamouse for example), what it's **Waiting for**, its **ETA** (with MyAnonamouse
set up), when it was **Last checked** and a countdown to the **Next check**. Drag these to reorder them too: earlier ones get free
room first. **+ Get** downloads one now without waiting; **Remove** drops it.

The **ETA** is when an item can start, as the automatic check would get to it: once both
the ratio and an unsatisfied slot allow.

- **Ratio:** a pick that fits now goes; once the room runs out, a pick waits for the
upload credit that makes room for it (counting the picks ahead of it, in queue order)
and the bonus points to buy it at your points-per-hour rate. **Buy credit** means you
have the points already. Picks that fit go ahead of earlier ones that don't.
- **Slots:** MAM picks (freeleech too) take the free slots first (after **Up next** and the
slots kept free), then each slot as a torrent seeding in your client reaches 72 hours,
then the slots the queue's own torrents free 72 hours after they start. Without the
client's seeding times, a pick past the free slots shows **For a slot**, time unknown.
MAM's download pause holds everything until it lifts.

It doesn't know when a torrent turns freeleech, counts seeding time as the client reports
it (MAM keeps its own clock, so pausing or going offline pushes slots later), and upload
credit for automatic downloads is only bought by you or auto-buy. A wait on bonus points
needs an admin: other users see the credit needed.

**Wanted** lists books saved without picks. **Choose releases** opens the release list;
pick and save, and the book moves to Queued. Rows with picks that couldn't be queued
have **Choose again** and **+ Get**.
Expand Down Expand Up @@ -94,7 +117,7 @@ Queuing something doesn't take you away from the page you're on. **Show Download
Queued** (off by default) goes to Activity → Downloads when you start a download
yourself; background downloads never move the page.

**Settings → Indexers → Prowlarr → Saved for Later** has the global switch (**Get Saved
**Settings → Indexers → MyAnonamouse → Saved for Later** has the global switch (**Get Saved
Books Automatically**, on by default; turning it off pauses every item without unmarking
it), **Keep Ratio At Least** (0 turns the ratio check off) and the check interval (at
least 5 minutes).
4 changes: 2 additions & 2 deletions shelfmark/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,7 @@ def search_mode_settings() -> list[SettingsField]:
description=(
"Series name and number, for books and audiobooks. Filled in for "
"MyAnonamouse results from Prowlarr when a MAM session ID is set in the "
"Prowlarr settings."
"MyAnonamouse settings."
),
default=True,
show_when={"field": "SEARCH_MODE", "value": "universal"},
Expand All @@ -779,7 +779,7 @@ def search_mode_settings() -> list[SettingsField]:
label="Show Narrator Column",
description=(
"Audiobooks only. Filled in for MyAnonamouse results from Prowlarr when a "
"MAM session ID is set in the Prowlarr settings."
"MAM session ID is set in the MyAnonamouse settings."
),
default=True,
show_when={"field": "SEARCH_MODE", "value": "universal"},
Expand Down
8 changes: 8 additions & 0 deletions shelfmark/core/mam_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,19 @@ def _account_payload(*, refresh: bool) -> dict[str, Any]:
stats = mam_account.get_stats(refresh=refresh)
except mam_account.MAM_ERRORS as exc:
return {"configured": True, "stats": None, "error": mam_account.describe_error(exc)}
pending = mam_account.pending_charge_bytes()
keep = mam_account.keep_ratio()
return {
"configured": True,
"stats": stats.to_dict(),
"error": None,
"points_per_hour": mam_points.estimate_rate(),
"pending_bytes": pending,
"keep_ratio": keep,
# Room before the ratio drops below Keep Ratio At Least, after active MAM downloads
"room_bytes": mam_account.ratio_room_bytes(
stats.uploaded_bytes, stats.downloaded_bytes + pending, keep
),
}


Expand Down
2 changes: 1 addition & 1 deletion shelfmark/release_sources/prowlarr/mam.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ def _record_failure(mam_id: str, error: Exception) -> None:
if count >= _MAX_CONSECUTIVE_FAILURES:
logger.warning(
"MAM enrichment stopped after %s consecutive failures (last: %s). "
"Test the MAM session in the Prowlarr settings to turn it back on.",
"Test the MAM session in the MyAnonamouse settings to turn it back on.",
count,
error,
)
Expand Down
37 changes: 32 additions & 5 deletions shelfmark/release_sources/prowlarr/mam_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,8 @@ def summarize_unsat_timing(torrents: list[Any], client_name: str | None) -> dict
"available": True,
"client": client_name,
"next_seconds": remaining[0] if remaining else None,
# When each one frees its slot, soonest first: the Queued page's slot ETAs
"free_seconds": remaining,
"within_window": sum(1 for r in remaining if r <= UNSAT_WINDOW_SECONDS),
"window_hours": UNSAT_WINDOW_SECONDS // 3600,
"seeding": len(remaining),
Expand Down Expand Up @@ -483,12 +485,13 @@ def _client_torrents(*, refresh: bool = False) -> _ClientRead:


def _aged_timing(timing: dict[str, Any], age: float) -> dict[str, Any]:
"""A cached timing as of now: the next slot is `age` seconds closer than when read."""
"""A cached timing as of now: each slot is `age` seconds closer than when read."""
# Rounded up, so a slot is never promised early.
aged = [max(0, math.ceil(seconds - age)) for seconds in timing.get("free_seconds", [])]
next_seconds = timing.get("next_seconds")
if not isinstance(next_seconds, (int, float)):
return timing
# Rounded up, so a slot is never promised early.
return {**timing, "next_seconds": max(0, math.ceil(next_seconds - age))}
return {**timing, "free_seconds": aged}
return {**timing, "next_seconds": max(0, math.ceil(next_seconds - age)), "free_seconds": aged}


def client_unsat_count(
Expand Down Expand Up @@ -876,6 +879,22 @@ def warning_ratio() -> float:
return value if value is not None and math.isfinite(value) else RATIO_WARNING


def keep_ratio() -> float:
"""The ratio to stay at or above: Keep Ratio At Least, or 1.0 (the buffer) when it's off."""
from shelfmark.core import saved_autoget

target = saved_autoget.target_ratio()
return target if target > 0 else 1.0


def ratio_room_bytes(uploaded: int, downloaded: int, ratio: float) -> int:
"""How much more can be downloaded before uploaded / downloaded drops below `ratio`.

Negative once it already has: then it's how far the downloaded total is over.
"""
return int(uploaded / ratio) - downloaded if ratio > 0 else uploaded - downloaded


def ratio_snapshot() -> dict[str, Any]:
"""What the projected-ratio line needs; no username or bonus points (all users see it)."""
if not is_configured():
Expand All @@ -884,16 +903,24 @@ def ratio_snapshot() -> dict[str, Any]:
stats = get_stats()
except MAM_ERRORS as exc:
return {"available": False, "error": describe_error(exc)}
pending = pending_charge_bytes()
keep = keep_ratio()
return {
"available": True,
"uploaded_bytes": stats.uploaded_bytes,
"downloaded_bytes": stats.downloaded_bytes,
"ratio": stats.ratio,
"buffer_bytes": stats.buffer_bytes,
"pending_bytes": pending_charge_bytes(),
"pending_bytes": pending,
"warning_ratio": warning_ratio(),
"keep_ratio": keep,
# Room left after Shelfmark's active MAM downloads
"room_bytes": ratio_room_bytes(
stats.uploaded_bytes, stats.downloaded_bytes + pending, keep
),
"unsat_count": stats.unsat_count,
"unsat_limit": stats.unsat_limit,
"unsat_pending": pending_unsat_count(stats.fetched_at, stats.unsat_count),
"unsat_reserve": unsat_reserve_slots(),
"unsat_check": unsat_check_enabled(),
}
Loading
Loading