mirror of
https://github.com/markbeep/AudioBookRequest.git
synced 2026-01-04 12:39:47 -06:00
mobile friendly + update readme
This commit is contained in:
10
README.md
10
README.md
@@ -1,8 +1,5 @@
|
||||
# AudioBookRequest
|
||||
|
||||
> [!CAUTION]
|
||||
> This project is in its very early stages. It's basically a weekend project at this state. There might be some bugs or unfinished parts.
|
||||
|
||||
Your tool for handling audiobook requests on a Plex/AudioBookShelf/Jellyfin instance.
|
||||
|
||||
If you've heard of Overseer, Ombi, or Jellyseer; this is in the similar vein, <ins>but for audiobooks</ins>.
|
||||
@@ -11,9 +8,10 @@ If you've heard of Overseer, Ombi, or Jellyseer; this is in the similar vein, <i
|
||||
|
||||
# Workflow
|
||||
|
||||
1. Admin creates user accounts for their friends. Each account's group is one of: `Admin`, `Trusted`, and `Untrusted`. All groups can request/remove book requests. Trusted and above can have downloads automatically start when they select a book in the search tab. This requires the download client to be set up correctly in Prowlarr.
|
||||
2. The requests/wishlist page shows a list of all books that have been requested. An admin can directly view the torrent sources gotten from Prowlarr and start any download.
|
||||
3. Settings page allows for admins to create new accounts and set the required Prowlarr configs.
|
||||
1. Admin creates user accounts for their friends. Each account's group is one of: `Admin`, `Trusted`, and `Untrusted`. All groups can request/remove book requests.
|
||||
2. When a Trusted and above requests a book, it'll automatically start downloading. This requires the download client to be set up correctly in Prowlarr and the "Auto Download" option to be on in the settings.
|
||||
3. If configured, a notification will be sent to Apprise.
|
||||
4. The requests/wishlist page shows a list of all books that have been requested. An admin can directly view the torrent sources gotten from Prowlarr and start downloading requests or reject them.
|
||||
|
||||
# Docker
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ from app.util.book_search import (
|
||||
list_audible_books,
|
||||
)
|
||||
from app.util.connection import get_connection
|
||||
from app.util.notifications import send_notification
|
||||
from app.util.notifications import send_manual_notification, send_notification
|
||||
from app.util.ranking.quality import quality_config
|
||||
from app.util.templates import template_response
|
||||
|
||||
@@ -188,6 +188,7 @@ async def add_manual(
|
||||
request: Request,
|
||||
user: Annotated[DetailedUser, Depends(get_authenticated_user())],
|
||||
session: Annotated[Session, Depends(get_session)],
|
||||
background_task: BackgroundTasks,
|
||||
title: Annotated[str, Form()],
|
||||
author: Annotated[str, Form()],
|
||||
narrator: Annotated[Optional[str], Form()] = None,
|
||||
@@ -205,8 +206,21 @@ async def add_manual(
|
||||
additional_info=info,
|
||||
)
|
||||
session.add(book_request)
|
||||
session.flush()
|
||||
session.expunge_all() # so that we can pass down the object without the session
|
||||
session.commit()
|
||||
|
||||
notifications = session.exec(
|
||||
select(Notification).where(Notification.event == EventEnum.on_new_request)
|
||||
).all()
|
||||
for notif in notifications:
|
||||
background_task.add_task(
|
||||
send_manual_notification,
|
||||
notification=notif,
|
||||
book=book_request,
|
||||
requester_username=user.username,
|
||||
)
|
||||
|
||||
return template_response(
|
||||
"manual.html",
|
||||
request,
|
||||
|
||||
@@ -3,7 +3,38 @@ from aiohttp import ClientSession
|
||||
from sqlmodel import select
|
||||
|
||||
from app.db import open_session
|
||||
from app.models import BookRequest, Notification
|
||||
from app.models import BookRequest, ManualBookRequest, Notification
|
||||
|
||||
|
||||
def create_title_body(
|
||||
title_template: str,
|
||||
body_template: str,
|
||||
username: Optional[str] = None,
|
||||
book_title: Optional[str] = None,
|
||||
book_authors: Optional[str] = None,
|
||||
book_narrators: Optional[str] = None,
|
||||
event_type: Optional[str] = None,
|
||||
):
|
||||
title = title_template
|
||||
body = body_template
|
||||
|
||||
if username:
|
||||
title = title.replace("{eventUser}", username)
|
||||
body = body.replace("{eventUser}", username)
|
||||
if book_title:
|
||||
title = title.replace("{bookTitle}", book_title)
|
||||
body = body.replace("{bookTitle}", book_title)
|
||||
if book_authors:
|
||||
title = title.replace("{bookAuthors}", book_authors)
|
||||
body = body.replace("{bookAuthors}", book_authors)
|
||||
if book_narrators:
|
||||
title = title.replace("{bookNarrators}", book_narrators)
|
||||
body = body.replace("{bookNarrators}", book_narrators)
|
||||
if event_type:
|
||||
title = title.replace("{eventType}", event_type)
|
||||
body = body.replace("{eventType}", event_type)
|
||||
|
||||
return title, body
|
||||
|
||||
|
||||
async def send_notification(
|
||||
@@ -13,27 +44,27 @@ async def send_notification(
|
||||
):
|
||||
with open_session() as session:
|
||||
async with ClientSession() as client_session:
|
||||
title = notification.title_template
|
||||
body = notification.body_template
|
||||
|
||||
if requester_username:
|
||||
title = title.replace("{eventUser}", requester_username)
|
||||
body = body.replace("{eventUser}", requester_username)
|
||||
|
||||
book_title = None
|
||||
book_authors = None
|
||||
book_narrators = None
|
||||
if book_asin:
|
||||
book = session.exec(
|
||||
select(BookRequest).where(BookRequest.asin == book_asin)
|
||||
).first()
|
||||
if book:
|
||||
title = title.replace("{bookTitle}", book.title)
|
||||
body = body.replace("{bookTitle}", book.title)
|
||||
title = title.replace("{bookAuthors}", ",".join(book.authors))
|
||||
body = body.replace("{bookAuthors}", ",".join(book.authors))
|
||||
title = title.replace("{bookNarrators}", ",".join(book.narrators))
|
||||
body = body.replace("{bookNarrators}", ",".join(book.narrators))
|
||||
book_title = book.title
|
||||
book_authors = ",".join(book.authors)
|
||||
book_narrators = ",".join(book.narrators)
|
||||
|
||||
title = title.replace("{eventType}", notification.event.value)
|
||||
body = body.replace("{eventType}", notification.event.value)
|
||||
title, body = create_title_body(
|
||||
notification.title_template,
|
||||
notification.body_template,
|
||||
requester_username,
|
||||
book_title,
|
||||
book_authors,
|
||||
book_narrators,
|
||||
notification.event.value,
|
||||
)
|
||||
|
||||
async with client_session.post(
|
||||
notification.apprise_url,
|
||||
@@ -45,3 +76,37 @@ async def send_notification(
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
return await response.json()
|
||||
|
||||
|
||||
async def send_manual_notification(
|
||||
notification: Notification,
|
||||
book: ManualBookRequest,
|
||||
requester_username: Optional[str] = None,
|
||||
):
|
||||
print("SENDING", "CALLED")
|
||||
try:
|
||||
async with ClientSession() as client_session:
|
||||
title, body = create_title_body(
|
||||
notification.title_template,
|
||||
notification.body_template,
|
||||
requester_username,
|
||||
book.title,
|
||||
",".join(book.authors),
|
||||
",".join(book.narrators),
|
||||
notification.event.value,
|
||||
)
|
||||
print("SENDING", title, body)
|
||||
|
||||
async with client_session.post(
|
||||
notification.apprise_url,
|
||||
json={
|
||||
"title": title,
|
||||
"body": body,
|
||||
},
|
||||
headers=notification.headers,
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
return await response.json()
|
||||
except Exception as e:
|
||||
print("SENDING", e)
|
||||
return None
|
||||
|
||||
@@ -79,7 +79,7 @@ class QualityProfile(StringConfigCache[QualityConfigKey]):
|
||||
self.delete(session, key)
|
||||
|
||||
def get_auto_download(self, session: Session) -> bool:
|
||||
return bool(self.get_int(session, "quality_auto_download", 1))
|
||||
return bool(self.get_int(session, "quality_auto_download", 0))
|
||||
|
||||
def set_auto_download(self, session: Session, auto_download: bool):
|
||||
self.set_int(session, "quality_auto_download", int(auto_download))
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 994 KiB After Width: | Height: | Size: 1.0 MiB |
@@ -22,7 +22,13 @@
|
||||
<header class="bg-base-100 text-neutral shadow-lg">
|
||||
<nav class="navbar">
|
||||
<div class="flex-1">
|
||||
<a preload href="/" class="btn btn-ghost text-lg">AudioBookRequest</a>
|
||||
<a
|
||||
preload
|
||||
href="/"
|
||||
class="btn btn-ghost text-lg hidden sm:inline-flex"
|
||||
>AudioBookRequest</a
|
||||
>
|
||||
<a preload href="/" class="btn btn-ghost text-lg sm:hidden">ABR</a>
|
||||
|
||||
<a
|
||||
preload
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</script>
|
||||
{% endblock %} {% block body %}
|
||||
<div
|
||||
class="w-screen flex flex-col items-center justify-center p-8 overflow-x-hidden gap-4"
|
||||
class="w-screen flex flex-col items-center justify-center p-6 sm:p-8 overflow-x-hidden gap-4"
|
||||
>
|
||||
<div class="flex w-full justify-between items-center">
|
||||
<h1 class="text-3xl font-bold text-left">Search</h1>
|
||||
@@ -40,7 +40,10 @@
|
||||
{% if not search_term %}autofocus{% endif %}
|
||||
value="{{ search_term }}"
|
||||
/>
|
||||
<select class="select join-item max-w-[5rem]" name="region">
|
||||
<select
|
||||
class="select join-item max-w-[4rem] sm:max-w-[5rem]"
|
||||
name="region"
|
||||
>
|
||||
{% for region in regions %}
|
||||
<!-- prettier-ignore -->
|
||||
<option
|
||||
@@ -60,12 +63,12 @@
|
||||
{% block book_results %}
|
||||
<div
|
||||
id="book-results"
|
||||
class="min-w-[60vw] max-w-[80vw] h-full grid gap-1 gap-y-2 sm:gap-y-4 sm:gap-2 p-1 grid-flow-row grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 2xl:grid-cols-7"
|
||||
class="min-w-[60vw] max-w-[90vw] sm:max-w-[80vw] h-full grid gap-1 gap-y-2 sm:gap-y-4 sm:gap-2 p-1 grid-flow-row grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 2xl:grid-cols-7"
|
||||
>
|
||||
{% for book in search_results %}
|
||||
<div class="flex flex-col">
|
||||
<div
|
||||
class="relative w-[10rem] h-[10rem] rounded-md overflow-hidden shadow shadow-black items-center justify-center flex"
|
||||
class="relative w-[8rem] h-[8rem] sm:w-[10rem] sm:h-[10rem] rounded-md overflow-hidden shadow shadow-black items-center justify-center flex"
|
||||
>
|
||||
{% if book.cover_image %}
|
||||
<img
|
||||
|
||||
Reference in New Issue
Block a user