Skip to content
Closed
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
2 changes: 1 addition & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ services:

Notes:
- Point `/books` to your library ingest folder (Calibre-Web, Booklore, Audiobookshelf, etc) for automatic import.
- If you set Books Output Mode to Booklore (API), books are uploaded via API instead of written to `/books`. Audiobooks still use a destination folder.
- If you set Books Output Mode to Booklore (API) or Litara (API), books are uploaded via API instead of written to `/books`. Audiobooks still use a destination folder.
- Ensure `PUID`/`PGID` (or legacy `UID`/`GID`) match the owner of the host directories.
- For non-root mode, start the container as `1000:1000`.
- On Kubernetes, set `runAsUser: 1000`, `runAsGroup: 1000`, and `runAsNonRoot: true` together.
Expand Down
9 changes: 5 additions & 4 deletions docs/custom-scripts.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Custom Scripts

Shelfmark can run an executable you provide after a download task completes successfully. The script runs after the selected output has finished (for example: transfer to the folder destination, or upload to Booklore).
Shelfmark can run an executable you provide after a download task completes successfully. The script runs after the selected output has finished (for example: transfer to the folder destination, or upload to Booklore/Litara).


## Quick Start (Recommended)
Expand Down Expand Up @@ -72,6 +72,7 @@ What the target path refers to depends on the output mode:

- Folder output (`output.mode=folder`, `phase=post_transfer`): the final imported file or folder inside your destination.
- Booklore output (`output.mode=booklore`, `phase=post_upload`): the local file or folder that was uploaded (the destination is remote).
- Litara output (`output.mode=litara`, `phase=post_upload`): the local file or folder that was uploaded to Litara Book Drop (the destination is remote).

By default, `$1` is an absolute path inside the Shelfmark container (or on your host, if you are not using Docker).

Expand All @@ -83,8 +84,8 @@ When enabled, Shelfmark sends a versioned JSON payload to your script via stdin

- The JSON payload always includes absolute paths in `paths.*`, even if you set Custom Script Path Mode to `relative` for `$1`.
- `output.mode` tells you which output ran.
- `output.details` is output-specific. For Booklore output, `output.details.booklore` includes connection details such as `base_url`, `library_id`, and `path_id`.
- `phase` indicates when the script is running. Current values: `post_transfer` (folder output), `post_upload` (Booklore output).
- `output.details` is output-specific. For Booklore output, `output.details.booklore` includes connection details such as `base_url`, `library_id`, and `path_id`. For Litara output, `output.details.litara` includes `base_url`.
- `phase` indicates when the script is running. Current values: `post_transfer` (folder output), `post_upload` (Booklore and Litara output).
- `transfer` is only included for outputs that do a local transfer (for example the folder output).

If JSON payload is disabled, stdin is empty (EOF). Don't `cat` stdin unless you've enabled the payload.
Expand Down Expand Up @@ -192,4 +193,4 @@ Note: if the target is the destination folder itself, `relative` mode may pass `
## Notes And Caveats

- **Hardlinks and torrents:** if you use hardlinking to keep seeding, avoid scripts that modify file contents, since hardlinked files share data with the seeding copy.
- **Booklore output mode:** scripts run after upload. `$1` will point at the local uploaded file (or staging folder).
- **Booklore/Litara output mode:** scripts run after upload. `$1` will point at the local uploaded file (or staging folder).
35 changes: 34 additions & 1 deletion docs/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,9 @@ The release source tab to open by default in the release modal for audiobooks. U
| `BOOKLORE_DESTINATION` | Choose whether uploads go directly to a specific library path or to Bookdrop for review. | string (choice) | `library` |
| `BOOKLORE_LIBRARY_ID` | Grimmory library to upload into. | string (choice) | _none_ |
| `BOOKLORE_PATH_ID` | Grimmory library path for uploads. | string (choice) | _none_ |
| `LITARA_HOST` | Base URL of your Litara instance | string | _none_ |
| `LITARA_EMAIL` | Litara account email address | string | _none_ |
| `LITARA_PASSWORD` | Litara account password | string (secret) | _none_ |
| `EMAIL_RECIPIENT` | Optional fallback email address when no per-user email recipient override is configured. | string | _none_ |
| `EMAIL_ATTACHMENT_SIZE_LIMIT_MB` | Maximum total attachment size per email. Email encoding adds overhead; keep this below your provider's limit. | number | `25` |
| `EMAIL_SMTP_HOST` | SMTP server hostname or IP (e.g., smtp.gmail.com). | string | _none_ |
Expand Down Expand Up @@ -335,7 +338,7 @@ Choose where completed book files are sent.

- **Type:** string (choice)
- **Default:** `folder`
- **Options:** `folder` (Folder), `email` (Email (SMTP)), `booklore` (Grimmory (API))
- **Options:** `folder` (Folder), `email` (Email (SMTP)), `booklore` (Grimmory (API)), `litara` (Litara (API))

#### `INGEST_DIR`

Expand Down Expand Up @@ -444,6 +447,36 @@ Grimmory library path for uploads.
- **Default:** _none_
- **Required:** Yes

#### `LITARA_HOST`

**Litara URL**

Base URL of your Litara instance

- **Type:** string
- **Default:** _none_
- **Required:** Yes

#### `LITARA_EMAIL`

**Email**

Litara account email address

- **Type:** string
- **Default:** _none_
- **Required:** Yes

#### `LITARA_PASSWORD`

**Password**

Litara account password

- **Type:** string (secret)
- **Default:** _none_
- **Required:** Yes

#### `EMAIL_RECIPIENT`

**Default Email Recipient**
Expand Down
2 changes: 1 addition & 1 deletion docs/users-and-requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ There are three categories of per-user settings:

Override where a user's downloads are sent. Options depend on the global output mode configuration:

- **Output mode** — Folder, Email (SMTP), or BookLore (API)
- **Output mode** — Folder, Email (SMTP), BookLore (API), or Litara (API)
- **Destination** — A custom folder path for this user's ebook downloads
- **Audiobook destination** — A custom folder path for audiobook downloads
- **BookLore library/path** — Per-user BookLore target (when using BookLore output mode)
Expand Down
55 changes: 55 additions & 0 deletions shelfmark/config/litara_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""Helpers for Litara settings validation and connection tests."""

from __future__ import annotations

from typing import Any

from shelfmark.core.config import config
from shelfmark.core.logger import setup_logger
from shelfmark.download.outputs.litara import (
LitaraConfig,
LitaraError,
litara_login,
)

logger = setup_logger(__name__)


def check_litara_connection(
current_values: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Test the Litara connection using current form values."""
current_values = current_values or {}

def _get_value(key: str, default: object = None) -> object:
value = current_values.get(key)
if value not in (None, ""):
return value
if default is None:
return config.get(key)
return config.get(key, default)

base_url = str(_get_value("LITARA_HOST", "") or "").strip().rstrip("/")
email = str(_get_value("LITARA_EMAIL", "") or "").strip()
password = str(_get_value("LITARA_PASSWORD", "") or "")

if not base_url:
return {"success": False, "message": "Litara URL is required"}
if not email:
return {"success": False, "message": "Litara email is required"}
if not password:
return {"success": False, "message": "Litara password is required"}

litara_config = LitaraConfig(
base_url=base_url,
email=email,
password=password,
verify_tls=True,
)

try:
litara_login(litara_config)
except LitaraError as exc:
return {"success": False, "message": str(exc)}
else:
return {"success": True, "message": "Connected to Litara"}
42 changes: 42 additions & 0 deletions shelfmark/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
check_books_destination,
)
from shelfmark.config.email_settings import check_email_connection
from shelfmark.config.litara_settings import check_litara_connection
from shelfmark.core.logger import setup_logger
from shelfmark.core.settings_registry import (
ActionButton,
Expand Down Expand Up @@ -875,6 +876,11 @@ def download_settings() -> list[SettingsField]:
"label": "Grimmory (API)",
"description": "Upload files directly to Grimmory",
},
{
"value": "litara",
"label": "Litara (API)",
"description": "Upload files directly to Litara",
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The output-mode option description says "Upload files directly to Litara", but this integration uploads to Litara Book Drop (admin approval flow) rather than directly into a library. Updating this description to explicitly mention Book Drop will prevent user confusion and better match the behavior/heading text below.

Suggested change
"description": "Upload files directly to Litara",
"description": "Upload files to Litara Book Drop",

Copilot uses AI. Check for mistakes.
},
],
default="folder",
user_overridable=True,
Expand Down Expand Up @@ -1045,6 +1051,42 @@ def download_settings() -> list[SettingsField]:
callback=check_booklore_connection,
show_when={"field": "BOOKS_OUTPUT_MODE", "value": "booklore"},
),
HeadingField(
key="litara_heading",
title="Litara",
description="Upload books directly to Litara Book Drop via API. Audiobooks are not supported and will use folder mode.",
show_when={"field": "BOOKS_OUTPUT_MODE", "value": "litara"},
),
TextField(
key="LITARA_HOST",
label="Litara URL",
description="Base URL of your Litara instance",
placeholder="http://litara:3000",
required=True,
show_when={"field": "BOOKS_OUTPUT_MODE", "value": "litara"},
),
TextField(
key="LITARA_EMAIL",
label="Email",
description="Litara account email address",
required=True,
show_when={"field": "BOOKS_OUTPUT_MODE", "value": "litara"},
),
PasswordField(
key="LITARA_PASSWORD",
label="Password",
description="Litara account password",
required=True,
show_when={"field": "BOOKS_OUTPUT_MODE", "value": "litara"},
),
ActionButton(
key="test_litara",
label="Test Connection",
description="Verify your Litara configuration",
style="primary",
callback=check_litara_connection,
show_when={"field": "BOOKS_OUTPUT_MODE", "value": "litara"},
),
HeadingField(
key="email_heading",
title="Email",
Expand Down
1 change: 1 addition & 0 deletions shelfmark/download/outputs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def load_output_handlers() -> None:
from . import booklore as booklore
from . import email as email
from . import folder as folder
from . import litara as litara

_OUTPUTS_LOADED = True

Expand Down
Loading
Loading