mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 13:40:27 +02:00
feat: Add Folder ID support for Audiobookshelf integration in settings and configuration
This commit is contained in:
@@ -147,6 +147,50 @@ Need machine-readable status updates? The dashboard calls a small set of helper
|
|||||||
|
|
||||||
More automation hooks are planned; contributions are very welcome if you need additional routes.
|
More automation hooks are planned; contributions are very welcome if you need additional routes.
|
||||||
|
|
||||||
|
## Audiobookshelf integration
|
||||||
|
Abogen can push finished audiobooks directly into Audiobookshelf. Configure this under **Settings → Integrations → Audiobookshelf** by providing:
|
||||||
|
|
||||||
|
- **Base URL** – the HTTPS origin (and optional path prefix) where your Audiobookshelf server is reachable, for example `https://abs.example.com` or `https://media.example.com/abs`. Do **not** append `/api`.
|
||||||
|
- **Library ID** – the identifier of the target Audiobookshelf library (copy it from the library’s settings page in ABS).
|
||||||
|
- **API token** – a personal access token generated in Audiobookshelf under *Account → API tokens*.
|
||||||
|
|
||||||
|
You can enable automatic uploads for future jobs or trigger individual uploads from the queue once the connection succeeds.
|
||||||
|
|
||||||
|
### Reverse proxy checklist (Nginx Proxy Manager)
|
||||||
|
When Audiobookshelf sits behind Nginx Proxy Manager (NPM), make sure the API paths and headers reach the backend untouched:
|
||||||
|
|
||||||
|
1. Create a **Proxy Host** that points to your ABS container or host (default forward port `13378`).
|
||||||
|
2. Under the **SSL** tab, enable your certificate and tick **Force SSL** if you want HTTPS only.
|
||||||
|
3. In the **Advanced** tab, append the snippet below so bearer tokens, client IPs, and large uploads survive the proxy hop:
|
||||||
|
```nginx
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Port $server_port;
|
||||||
|
proxy_set_header Authorization $http_authorization;
|
||||||
|
client_max_body_size 5g;
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
proxy_connect_timeout 300s;
|
||||||
|
```
|
||||||
|
4. Disable **Block Common Exploits** (it strips Authorization headers in some NPM builds).
|
||||||
|
5. Enable **Websockets Support** on the main proxy screen (Audiobookshelf uses it for the web UI, and it keeps the reverse proxy configuration consistent).
|
||||||
|
6. If you publish Audiobookshelf under a path prefix (for example `/abs`), add a **Custom Location** with `Location: /abs/` and set the **Forward Path** to `/`. That rewrite strips the `/abs` prefix before traffic reaches Audiobookshelf so `/abs/api/...` on the internet becomes `/api/...` on the backend. Use the same prefixed URL in Abogen’s “Base URL” field.
|
||||||
|
|
||||||
|
After saving the proxy host, test the API from the machine running Abogen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -i "https://abs.example.com/api/uploads" \
|
||||||
|
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"libraryId":"YOUR_LIBRARY_ID","mediaType":"audiobook"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
If you still receive `Cannot GET /api/...`, the proxy is rewriting paths. Double-check the **Custom Locations** table (the `Forward Path` column should be empty for `/abs/`) and review the NPM access/error logs while issuing the curl request to confirm the backend sees the full `/api/uploads` URL.
|
||||||
|
|
||||||
|
A JSON response containing an `id` or `uploadId` confirms the proxy is routing API calls correctly. You can then use **Test connection** in Abogen’s settings and the “Send to Audiobookshelf” button on completed jobs.
|
||||||
|
|
||||||
## Configuration reference
|
## Configuration reference
|
||||||
Most behaviour is controlled through the UI, but a few environment variables are helpful for automation:
|
Most behaviour is controlled through the UI, but a few environment variables are helpful for automation:
|
||||||
- `ABOGEN_SECRET_KEY` – provide your own random secret when deploying across multiple replicas.
|
- `ABOGEN_SECRET_KEY` – provide your own random secret when deploying across multiple replicas.
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class AudiobookshelfConfig:
|
|||||||
api_token: str
|
api_token: str
|
||||||
library_id: str
|
library_id: str
|
||||||
collection_id: Optional[str] = None
|
collection_id: Optional[str] = None
|
||||||
|
folder_id: Optional[str] = None
|
||||||
verify_ssl: bool = True
|
verify_ssl: bool = True
|
||||||
send_cover: bool = True
|
send_cover: bool = True
|
||||||
send_chapters: bool = True
|
send_chapters: bool = True
|
||||||
|
|||||||
@@ -2129,6 +2129,7 @@ def _integration_defaults() -> Dict[str, Dict[str, Any]]:
|
|||||||
"api_token": "",
|
"api_token": "",
|
||||||
"library_id": "",
|
"library_id": "",
|
||||||
"collection_id": "",
|
"collection_id": "",
|
||||||
|
"folder_id": "",
|
||||||
"verify_ssl": True,
|
"verify_ssl": True,
|
||||||
"send_cover": True,
|
"send_cover": True,
|
||||||
"send_chapters": True,
|
"send_chapters": True,
|
||||||
@@ -2433,6 +2434,12 @@ def _audiobookshelf_settings_from_payload(payload: Mapping[str, Any]) -> Dict[st
|
|||||||
or stored.get("collection_id")
|
or stored.get("collection_id")
|
||||||
or ""
|
or ""
|
||||||
).strip()
|
).strip()
|
||||||
|
folder_id = str(
|
||||||
|
payload.get("folder_id")
|
||||||
|
or payload.get("audiobookshelf_folder_id")
|
||||||
|
or stored.get("folder_id")
|
||||||
|
or ""
|
||||||
|
).strip()
|
||||||
token_input = str(
|
token_input = str(
|
||||||
payload.get("api_token")
|
payload.get("api_token")
|
||||||
or payload.get("audiobookshelf_api_token")
|
or payload.get("audiobookshelf_api_token")
|
||||||
@@ -2502,6 +2509,7 @@ def _audiobookshelf_settings_from_payload(payload: Mapping[str, Any]) -> Dict[st
|
|||||||
"base_url": base_url,
|
"base_url": base_url,
|
||||||
"library_id": library_id,
|
"library_id": library_id,
|
||||||
"collection_id": collection_id,
|
"collection_id": collection_id,
|
||||||
|
"folder_id": folder_id,
|
||||||
"api_token": api_token,
|
"api_token": api_token,
|
||||||
"verify_ssl": verify_ssl,
|
"verify_ssl": verify_ssl,
|
||||||
"send_cover": send_cover,
|
"send_cover": send_cover,
|
||||||
@@ -2527,6 +2535,7 @@ def _build_audiobookshelf_config(settings: Mapping[str, Any]) -> Optional[Audiob
|
|||||||
api_token=api_token,
|
api_token=api_token,
|
||||||
library_id=library_id,
|
library_id=library_id,
|
||||||
collection_id=(str(settings.get("collection_id") or "").strip() or None),
|
collection_id=(str(settings.get("collection_id") or "").strip() or None),
|
||||||
|
folder_id=(str(settings.get("folder_id") or "").strip() or None),
|
||||||
verify_ssl=_coerce_bool(settings.get("verify_ssl"), True),
|
verify_ssl=_coerce_bool(settings.get("verify_ssl"), True),
|
||||||
send_cover=_coerce_bool(settings.get("send_cover"), True),
|
send_cover=_coerce_bool(settings.get("send_cover"), True),
|
||||||
send_chapters=_coerce_bool(settings.get("send_chapters"), True),
|
send_chapters=_coerce_bool(settings.get("send_chapters"), True),
|
||||||
@@ -2608,6 +2617,7 @@ def _apply_integration_form(cfg: Dict[str, Any], form: Mapping[str, Any]) -> Non
|
|||||||
abs_base = str(form.get("audiobookshelf_base_url") or current_abs.get("base_url") or "").strip()
|
abs_base = str(form.get("audiobookshelf_base_url") or current_abs.get("base_url") or "").strip()
|
||||||
abs_library = str(form.get("audiobookshelf_library_id") or current_abs.get("library_id") or "").strip()
|
abs_library = str(form.get("audiobookshelf_library_id") or current_abs.get("library_id") or "").strip()
|
||||||
abs_collection = str(form.get("audiobookshelf_collection_id") or current_abs.get("collection_id") or "").strip()
|
abs_collection = str(form.get("audiobookshelf_collection_id") or current_abs.get("collection_id") or "").strip()
|
||||||
|
abs_folder = str(form.get("audiobookshelf_folder_id") or current_abs.get("folder_id") or "").strip()
|
||||||
abs_token_input = str(form.get("audiobookshelf_api_token") or "")
|
abs_token_input = str(form.get("audiobookshelf_api_token") or "")
|
||||||
abs_token_clear = _coerce_bool(form.get("audiobookshelf_api_token_clear"), False)
|
abs_token_clear = _coerce_bool(form.get("audiobookshelf_api_token_clear"), False)
|
||||||
if abs_token_input:
|
if abs_token_input:
|
||||||
@@ -2632,6 +2642,7 @@ def _apply_integration_form(cfg: Dict[str, Any], form: Mapping[str, Any]) -> Non
|
|||||||
"api_token": abs_token,
|
"api_token": abs_token,
|
||||||
"library_id": abs_library,
|
"library_id": abs_library,
|
||||||
"collection_id": abs_collection,
|
"collection_id": abs_collection,
|
||||||
|
"folder_id": abs_folder,
|
||||||
"verify_ssl": abs_verify,
|
"verify_ssl": abs_verify,
|
||||||
"send_cover": abs_send_cover,
|
"send_cover": abs_send_cover,
|
||||||
"send_chapters": abs_send_chapters,
|
"send_chapters": abs_send_chapters,
|
||||||
@@ -3095,6 +3106,7 @@ def test_audiobookshelf() -> ResponseReturnValue:
|
|||||||
"message": f"Connected to Audiobookshelf library '{library_name}'.",
|
"message": f"Connected to Audiobookshelf library '{library_name}'.",
|
||||||
"library_id": library_id,
|
"library_id": library_id,
|
||||||
"collection_id": collection_id or None,
|
"collection_id": collection_id or None,
|
||||||
|
"folder_id": settings.get("folder_id") or None,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -956,6 +956,7 @@ class ConversionService:
|
|||||||
api_token=api_token,
|
api_token=api_token,
|
||||||
library_id=library_id,
|
library_id=library_id,
|
||||||
collection_id=(str(integration_cfg.get("collection_id") or "").strip() or None),
|
collection_id=(str(integration_cfg.get("collection_id") or "").strip() or None),
|
||||||
|
folder_id=(str(integration_cfg.get("folder_id") or "").strip() or None),
|
||||||
verify_ssl=self._coerce_bool(integration_cfg.get("verify_ssl"), True),
|
verify_ssl=self._coerce_bool(integration_cfg.get("verify_ssl"), True),
|
||||||
send_cover=self._coerce_bool(integration_cfg.get("send_cover"), True),
|
send_cover=self._coerce_bool(integration_cfg.get("send_cover"), True),
|
||||||
send_chapters=self._coerce_bool(integration_cfg.get("send_chapters"), True),
|
send_chapters=self._coerce_bool(integration_cfg.get("send_chapters"), True),
|
||||||
|
|||||||
@@ -305,6 +305,7 @@ function collectAudiobookshelfFields() {
|
|||||||
const baseUrl = form.querySelector('#audiobookshelf_base_url')?.value?.trim() || '';
|
const baseUrl = form.querySelector('#audiobookshelf_base_url')?.value?.trim() || '';
|
||||||
const libraryId = form.querySelector('#audiobookshelf_library_id')?.value?.trim() || '';
|
const libraryId = form.querySelector('#audiobookshelf_library_id')?.value?.trim() || '';
|
||||||
const collectionId = form.querySelector('#audiobookshelf_collection_id')?.value?.trim() || '';
|
const collectionId = form.querySelector('#audiobookshelf_collection_id')?.value?.trim() || '';
|
||||||
|
const folderId = form.querySelector('#audiobookshelf_folder_id')?.value?.trim() || '';
|
||||||
const tokenInput = form.querySelector('#audiobookshelf_api_token');
|
const tokenInput = form.querySelector('#audiobookshelf_api_token');
|
||||||
const apiToken = tokenInput?.value?.trim() || '';
|
const apiToken = tokenInput?.value?.trim() || '';
|
||||||
const hasSecret = tokenInput?.dataset.hasSecret === 'true';
|
const hasSecret = tokenInput?.dataset.hasSecret === 'true';
|
||||||
@@ -319,6 +320,7 @@ function collectAudiobookshelfFields() {
|
|||||||
base_url: baseUrl,
|
base_url: baseUrl,
|
||||||
library_id: libraryId,
|
library_id: libraryId,
|
||||||
collection_id: collectionId,
|
collection_id: collectionId,
|
||||||
|
folder_id: folderId,
|
||||||
api_token: apiToken,
|
api_token: apiToken,
|
||||||
use_saved_token: useSavedToken,
|
use_saved_token: useSavedToken,
|
||||||
clear_saved_token: clearToken,
|
clear_saved_token: clearToken,
|
||||||
|
|||||||
@@ -444,6 +444,10 @@
|
|||||||
<label for="audiobookshelf_collection_id">Collection ID (optional)</label>
|
<label for="audiobookshelf_collection_id">Collection ID (optional)</label>
|
||||||
<input type="text" id="audiobookshelf_collection_id" name="audiobookshelf_collection_id" value="{{ integrations.audiobookshelf.collection_id }}">
|
<input type="text" id="audiobookshelf_collection_id" name="audiobookshelf_collection_id" value="{{ integrations.audiobookshelf.collection_id }}">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field__group">
|
||||||
|
<label for="audiobookshelf_folder_id">Folder ID (optional)</label>
|
||||||
|
<input type="text" id="audiobookshelf_folder_id" name="audiobookshelf_folder_id" value="{{ integrations.audiobookshelf.folder_id }}">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field field--inline">
|
<div class="field field--inline">
|
||||||
<div class="field__group">
|
<div class="field__group">
|
||||||
|
|||||||
Reference in New Issue
Block a user