Book Display
This commit is contained in:
@@ -60,6 +60,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
venvVersionWarn
|
venvVersionWarn
|
||||||
|
|
||||||
|
if [ -f .env ]; then
|
||||||
|
set -a
|
||||||
|
source .env
|
||||||
|
set +a
|
||||||
|
echo "Loaded .env file"
|
||||||
|
fi
|
||||||
'';
|
'';
|
||||||
|
|
||||||
packages = with python.pkgs; [
|
packages = with python.pkgs; [
|
||||||
|
|||||||
@@ -1,11 +1,35 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
import curses
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
from curses import wrapper
|
||||||
from .libs.config import Config
|
from .libs.config import Config
|
||||||
from .libs.absapi import ABSApi, Endpoint, ABSResponse
|
from .libs.absapi import ABSApi, Endpoint, ABSResponse, Library
|
||||||
|
from .libs.terminal import Terminal, ExitTerminal
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
_: argparse.Action = parser.add_argument("--debug", help="Debug the program", action="store_true")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
config: Config = Config()
|
config: Config = Config()
|
||||||
|
# stdscr: curses.window = curses.initscr()
|
||||||
def main() -> None:
|
def main(stdscr: curses.window) -> None:
|
||||||
pass
|
terminal: Terminal = Terminal(stdscr)
|
||||||
|
Terminal.startup(terminal)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
if args.debug:
|
||||||
|
_api = ABSApi()
|
||||||
|
_response: list[Library]|None = _api.get_libraries()
|
||||||
|
_id = _response[0].id if _response else ""
|
||||||
|
_books = _api.get_books(_id)
|
||||||
|
for book in _books if _books else []:
|
||||||
|
breakpoint()
|
||||||
|
print(book.media.metadata.title)
|
||||||
|
sys.exit(0)
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
wrapper(main)
|
||||||
|
except ExitTerminal:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,176 @@
|
|||||||
import urllib.request as req
|
import urllib.request as req
|
||||||
# import json
|
import json
|
||||||
# from typing import List, dict, Optional
|
from typing import Any, Optional
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from datetime import datetime
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
from .config import Config
|
from .config import Config
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Folder:
|
||||||
|
addedAt: float
|
||||||
|
fullPath: str
|
||||||
|
id: str
|
||||||
|
libraryId: str
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class LibrarySettings:
|
||||||
|
audiobooksOnly: bool = False
|
||||||
|
autoScanCronExpression: str|None = None
|
||||||
|
coverAspectRatio: int = 1
|
||||||
|
disableWatcher: bool = False
|
||||||
|
epubsAllowScriptedContent: bool = False
|
||||||
|
hideSingleBookSeries: bool = False
|
||||||
|
markAsFinishedPercentComplete: int|None = None
|
||||||
|
markAsFinishedTimeRemaining: int = 10
|
||||||
|
podcastSearchRegion: str = ""
|
||||||
|
metadataPrecedence: list[str] = field(default_factory=lambda: [
|
||||||
|
"folderStructure",
|
||||||
|
"audioMetatags",
|
||||||
|
"nfoFile",
|
||||||
|
"txtFiles",
|
||||||
|
"opfFile",
|
||||||
|
"absMetadata"
|
||||||
|
])
|
||||||
|
onlyShowLaterBooksInContinueSeries: bool = False
|
||||||
|
skipMatchingMediaWithAsin: bool = False
|
||||||
|
skipMatchingMediaWithIsbn: bool = False
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Library:
|
||||||
|
createdAt: float
|
||||||
|
displayOrder: int
|
||||||
|
folders: list[Folder]
|
||||||
|
icon: str = ""
|
||||||
|
id: str = ""
|
||||||
|
lastScan: float = 0
|
||||||
|
lastScanVersion: str = ""
|
||||||
|
lastUpdate: float = 0
|
||||||
|
mediaType: str = ""
|
||||||
|
name: str = ""
|
||||||
|
provider: str = ""
|
||||||
|
settings: LibrarySettings = field(default_factory=LibrarySettings)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Metadata:
|
||||||
|
abridged: bool = False
|
||||||
|
asin: str|None = None
|
||||||
|
authorName: str = ""
|
||||||
|
authorNameLF: str = ""
|
||||||
|
description: str = ""
|
||||||
|
explicit: bool = False
|
||||||
|
genres: list[str] = field(default_factory=list)
|
||||||
|
isbn: str|None = None
|
||||||
|
language: str|None = None
|
||||||
|
narratorName: str = ""
|
||||||
|
publishedDate: Any|None = None
|
||||||
|
publishedYear: Any|None = None
|
||||||
|
publisher: str|None = None
|
||||||
|
seriesName: str = ""
|
||||||
|
subtitle: Any|None = None
|
||||||
|
title: str = ""
|
||||||
|
titleIgnorePrefix: str = ""
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Media:
|
||||||
|
coverPath: str = ""
|
||||||
|
duration: int = 0
|
||||||
|
id: str = ""
|
||||||
|
metadata: Metadata = field(default_factory=Metadata)
|
||||||
|
numAudioFiles: int = 0
|
||||||
|
numChapters: int = 0
|
||||||
|
numTracks: int = 0
|
||||||
|
size: int = 0
|
||||||
|
tags: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Book:
|
||||||
|
addedAt: int = 0
|
||||||
|
birthtimeMs: int = 0
|
||||||
|
ctimeMs: int = 0
|
||||||
|
folderId: str = ""
|
||||||
|
id: str = ""
|
||||||
|
ino: str = ""
|
||||||
|
isFile: bool = False
|
||||||
|
isInvalid: bool = False
|
||||||
|
isMissing: bool = False
|
||||||
|
libraryId: str = ""
|
||||||
|
media: Media = field(default_factory=Media)
|
||||||
|
mediaType: str = ""
|
||||||
|
mtimeMs: int = 0
|
||||||
|
numFiles: int = 0
|
||||||
|
oldLibraryItemId: str = ""
|
||||||
|
path: str = ""
|
||||||
|
relPath: str = ""
|
||||||
|
size: int = 0
|
||||||
|
updatedAt: int = 0
|
||||||
|
|
||||||
|
|
||||||
class Endpoint(Enum):
|
class Endpoint(Enum):
|
||||||
BOOKS = "/api/v1/book"
|
"""
|
||||||
AUTHORS = "/api/v1/author"
|
Enum for ABS API endpoints.
|
||||||
SERIES = "/api/v1/series"
|
https://api.audiobookshelf.org/#endpoints
|
||||||
COVERS = "/api/v1/cover"
|
Parameters:
|
||||||
GENRES = "/api/v1/genre"
|
item_id (str, optional): The ID of the item. Default is None.
|
||||||
LISTS = "/api/v1/list"
|
episode_id (str, optional): The ID of the episode. Default is None.
|
||||||
USERS = "/api/v1/user"
|
DELETEALLITEMS "/api/items/all""
|
||||||
SETTINGS = "/api/v1/settings"
|
DELETEITEM DELETE lambda item_id: f"/api/items/{item_id}"
|
||||||
STATS = "/api/v1/stats"
|
GETITEM GET lambda item_id: f"/api/items/{item_id}"
|
||||||
PLAYBACK_POSITIONS = "/api/v1/playback-position"
|
UPDATEITEM PATCH lambda item_id: f"/api/items/{item_id}/media"
|
||||||
REVIEWS = "/api/v1/review"
|
PLAYITEM POST lambda item_id: f"/api/items/{item_id}/play"
|
||||||
TAGS = "/api/v1/tag"
|
PLAYEPISODE POST lambda item_id, episode_id: f"/api/items/{item_id}/play/{episode_id}""
|
||||||
PLUGINS = "/api/v1/plugin"
|
GETALLCOLLECTIONS GET "/api/collections"
|
||||||
|
GETCOLLECTION GET lambda item_id: f"/api/collections/{item_id}"
|
||||||
|
GETPLAYLISTS GET "/api/playlists"
|
||||||
|
GETPLAYLIST GET lambda item_id: f"/api/playlists/{item_id}"
|
||||||
|
GETMEDIAPROGRESS GET lambda item_id: f"/api/me/progress/{item_id}""
|
||||||
|
UPDATEMEDIAPROGRESS PATCH lambda item_id: f"/api/me/progress/{item_id}"
|
||||||
|
UPDATEPODCASTPROGRESS PATCH lambda item_id, episode_id: f"/api/me/progress/{item_id}/{episode_id}"
|
||||||
|
GETITEMSINPROGRESS GET "/api/me/items-in-prorgess"
|
||||||
|
"""
|
||||||
|
STATUS = "/status" # GET
|
||||||
|
PING = "/ping" # GET
|
||||||
|
HEALTHCHECK = "/healthcheck" # GET
|
||||||
|
LIBRARY = lambda item_id = None: f"/api/libraries/{item_id if item_id is not None else ''}" # GET
|
||||||
|
"""
|
||||||
|
GET Get All Libraries https://api.audiobookshelf.org/#get-all-libraries
|
||||||
|
POST Create Library https://api.audiobookshelf.org/#libraries
|
||||||
|
GET LIBRARY Get Specified Library https://api.audiobookshelf.org/#get-a-library
|
||||||
|
GET LIBRARY/items Get Items https://api.audiobookshelf.org/#get-a-library-39-s-items
|
||||||
|
GET LIBRARY/episode-downloads Get Podcast Downloads https://api.audiobookshelf.org/#get-a-library-39-s-podcast-episode-downloads
|
||||||
|
GET LIBRARY/series Get Series https://api.audiobookshelf.org/#get-a-library-39-s-series
|
||||||
|
GET LIBRARY/collections Get Collections https://api.audiobookshelf.org/#get-a-library-39-s-collections
|
||||||
|
GET LIBRARY/playlists Get User Playlists https://api.audiobookshelf.org/#get-a-library-39-s-user-playlists
|
||||||
|
GET LIBRARY/personalized Get Personalized View https://api.audiobookshelf.org/#get-a-library-39-s-personalized-view
|
||||||
|
GET LIBRARY/filterdata Get Filter Data https://api.audiobookshelf.org/#get-a-library-39-s-filter-data
|
||||||
|
GET LIBRARY/search?<q> Search https://api.audiobookshelf.org/#search-a-library
|
||||||
|
GET LIBRARY/stats Get Stats https://api.audiobookshelf.org/#get-a-library-39-s-stats
|
||||||
|
GET LIBRARY/authors Get Authors https://api.audiobookshelf.org/#get-a-library-39-s-authors
|
||||||
|
GET LIBRARY/matchall Get All Items
|
||||||
|
POST LIBRARY/scan Scan Folders
|
||||||
|
GET LIBRARYrecent-episodes Get Podcasts Newest Unfinished Episodes https://api.audiobookshelf.org/#get-a-library-39-s-recent-episodes
|
||||||
|
DELETE LIBRARY/issues Delete Items W/Issues https://api.audiobookshelf.org/#remove-a-library-39-s-items-with-issues
|
||||||
|
PATCH LIBRARY Update Library https://api.audiobookshelf.org/#update-a-library
|
||||||
|
DELETE LIBRARY Delete Library https://api.audiobookshelf.org/#delete-a-library
|
||||||
|
POST LIBRARY/order Reorder Libraries https://api.audiobookshelf.org/#reorder-library-list
|
||||||
|
"""
|
||||||
|
DELETEALLITEMS = "/api/items/all"# DELETE Delete all items from DB https://api.audiobookshelf.org/#library-items
|
||||||
|
DELETEITEM = lambda item_id: f"/api/items/{item_id}" # /api/items/<ID> DELETE Delete Item https://api.audiobookshelf.org/#delete-a-library-item
|
||||||
|
GETITEM = lambda item_id: f"/api/items/{item_id}" # /api/items/<ID> GET Get Item https://api.audiobookshelf.org/#get-a-library-item
|
||||||
|
UPDATEITEM = lambda item_id: f"/api/items/{item_id}/media" # PATCH Updates an items media & Return https://api.audiobookshelf.org/#update-a-library-item-39-s-media
|
||||||
|
PLAYITEM = lambda item_id: f"/api/items/{item_id}/play" # POST https://api.audiobookshelf.org/#play-a-library-item-or-podcast-episode
|
||||||
|
PLAYEPISODE = lambda item_id, episode_id: f"/api/items/{item_id}/play/{episode_id}" # POST https://api.audiobookshelf.org/#play-a-library-item-or-podcast-episode
|
||||||
|
GETALLCOLLECTIONS = "/api/collections" # GET Get All Collections https://api.audiobookshelf.org/#get-all-collections
|
||||||
|
GETCOLLECTION = lambda item_id: f"/api/collections/{item_id}" # GET Get Specified Collection https://api.audiobookshelf.org/#get-a-collectionA
|
||||||
|
GETPLAYLISTS = "/api/playlists" # GET Get All Playlists https://api.audiobookshelf.org/#get-all-user-playlists
|
||||||
|
GETPLAYLIST = lambda item_id: f"/api/playlists/{item_id}" # GET Get Specified Playlist https://api.audiobookshelf.org/#get-a-playlist
|
||||||
|
GETMEDIAPROGRESS = lambda item_id: f"/api/me/progress/{item_id}" # GET Gets progress for indicated library item id https://api.audiobookshelf.org/#get-a-media-progress
|
||||||
|
UPDATEMEDIAPROGRESS = lambda item_id: f"/api/me/progress/{item_id}" # PATCH Updates progress for indicated library item id https://api.audiobookshelf.org/#update-media-progress
|
||||||
|
UPDATEPODCASTPROGRESS = lambda item_id, episode_id: f"/api/me/progress/{item_id}/{episode_id}" # PATCH {libraryid} {EpisodeID} Updates progress for indicated podcast episode id https://api.audiobookshelf.org/#create-update-media-progress
|
||||||
|
GETITEMSINPROGRESS = "/api/me/items-in-prorgess" # GET Get Items in Progress https://api.audiobookshelf.org/#get-library-items-in-progress
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ABSResponse:
|
class ABSResponse:
|
||||||
@@ -46,3 +197,134 @@ class ABSApi:
|
|||||||
ABSResponse: The response from the API.
|
ABSResponse: The response from the API.
|
||||||
"""
|
"""
|
||||||
return f"{self.base_url}{endpoint.value}"
|
return f"{self.base_url}{endpoint.value}"
|
||||||
|
|
||||||
|
def request(self, endpoint:str, method:str="GET", data:Any|None=None):
|
||||||
|
"""
|
||||||
|
Build a request for the ABS API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
endpoint (Endpoint): The API endpoint to request.
|
||||||
|
method (str): The HTTP method to use. Default is "GET".
|
||||||
|
data (Any, optional): The data to send with the request. Default is None.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
req.Request: The built request.
|
||||||
|
"""
|
||||||
|
url = f"{self.base_url}{endpoint}"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {Config().abs_api_key}",
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
request: req.Request = req.Request(url=url, headers=headers, method=method, data=data)
|
||||||
|
return req.urlopen(request)
|
||||||
|
|
||||||
|
def get_libraries(self) -> list[Library]|None:
|
||||||
|
"""
|
||||||
|
Get all libraries from the ABS API.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[Library]: A list of libraries.
|
||||||
|
"""
|
||||||
|
endpoint = Endpoint.LIBRARY()
|
||||||
|
_r = self.request(endpoint)
|
||||||
|
libraries: list[Library] = []
|
||||||
|
folders: list[Folder] = []
|
||||||
|
if _r.status == 200:
|
||||||
|
data = _r.read().decode("utf-8")
|
||||||
|
j = json.loads(str(data))
|
||||||
|
for lib in j["libraries"]:
|
||||||
|
for folder in lib["folders"]:
|
||||||
|
f = Folder(
|
||||||
|
addedAt=folder["addedAt"],
|
||||||
|
fullPath=folder["fullPath"],
|
||||||
|
id=folder["id"],
|
||||||
|
libraryId=folder["libraryId"]
|
||||||
|
)
|
||||||
|
folders.append(f)
|
||||||
|
settings = LibrarySettings(**lib["settings"])
|
||||||
|
library = Library(
|
||||||
|
createdAt=lib["createdAt"],
|
||||||
|
displayOrder=lib["displayOrder"],
|
||||||
|
folders=folders,
|
||||||
|
icon=lib["icon"],
|
||||||
|
id=lib["id"],
|
||||||
|
lastScan=lib["lastScan"],
|
||||||
|
lastScanVersion=lib["lastScanVersion"],
|
||||||
|
lastUpdate=lib["lastUpdate"],
|
||||||
|
mediaType=lib["mediaType"],
|
||||||
|
name=lib["name"],
|
||||||
|
provider=lib["provider"],
|
||||||
|
settings=settings
|
||||||
|
)
|
||||||
|
libraries.append(library)
|
||||||
|
return libraries
|
||||||
|
|
||||||
|
def get_books(self, library_id: str) -> list[Book]|None:
|
||||||
|
"""
|
||||||
|
Get all books from a specific library.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
library_id (str): The ID of the library.
|
||||||
|
Returns:
|
||||||
|
ABSResponse: The response from the API.
|
||||||
|
"""
|
||||||
|
endpoint = Endpoint.LIBRARY(library_id) + "/items"
|
||||||
|
_r = self.request(endpoint)
|
||||||
|
books: list[Book] = []
|
||||||
|
if _r.status == 200:
|
||||||
|
data = _r.read().decode("utf-8")
|
||||||
|
j = json.loads(str(data))
|
||||||
|
data = None
|
||||||
|
for book in j['results']:
|
||||||
|
_book: Book = Book()
|
||||||
|
_book.addedAt = book.get("addedAt", 0)
|
||||||
|
_book.birthtimeMs = book.get("birthtimeMs", 0)
|
||||||
|
_book.ctimeMs = book.get("ctimeMs", 0)
|
||||||
|
_book.folderId = book.get("folderId", "")
|
||||||
|
_book.id = book.get("id", "")
|
||||||
|
_book.ino = book.get("ino", "")
|
||||||
|
_book.isFile = book.get("isFile", False)
|
||||||
|
_book.isInvalid = book.get("isInvalid", False)
|
||||||
|
_book.isMissing = book.get("isMissing", False)
|
||||||
|
_book.libraryId = book.get("libraryId", "")
|
||||||
|
_media: Media = Media()
|
||||||
|
_media.coverPath = book["media"].get("coverPath", "")
|
||||||
|
_media.duration = book["media"].get("duration", 0)
|
||||||
|
_media.id = book["media"].get("id", "")
|
||||||
|
_metadata: Metadata = Metadata()
|
||||||
|
_metadata.abridged = book["media"]["metadata"].get("abridged", False)
|
||||||
|
_metadata.asin = book["media"]["metadata"].get("asin", None)
|
||||||
|
_metadata.authorName = book["media"]["metadata"].get("authorName", "")
|
||||||
|
_metadata.authorNameLF = book["media"]["metadata"].get("authorNameLF", "")
|
||||||
|
_metadata.description = book["media"]["metadata"].get("description", "")
|
||||||
|
_metadata.explicit = book["media"]["metadata"].get("explicit", False)
|
||||||
|
_metadata.genres = book["media"]["metadata"].get("genres", [])
|
||||||
|
_metadata.isbn = book["media"]["metadata"].get("isbn", None)
|
||||||
|
_metadata.language = book["media"]["metadata"].get("language", None)
|
||||||
|
_metadata.narratorName = book["media"]["metadata"].get("narratorName", "")
|
||||||
|
_metadata.publishedDate = book["media"]["metadata"].get("publishedDate", None)
|
||||||
|
_metadata.publishedYear = book["media"]["metadata"].get("publishedYear", None)
|
||||||
|
_metadata.publisher = book["media"]["metadata"].get("publisher", None)
|
||||||
|
_metadata.seriesName = book["media"]["metadata"].get("seriesName", "")
|
||||||
|
_metadata.subtitle = book["media"]["metadata"].get("subtitle", None)
|
||||||
|
_metadata.title = book["media"]["metadata"].get("title", "")
|
||||||
|
_metadata.titleIgnorePrefix = book["media"]["metadata"].get("titleIgnorePrefix", "")
|
||||||
|
_media.metadata = _metadata
|
||||||
|
_media.numAudioFiles = book["media"].get("numAudioFiles", 0)
|
||||||
|
_media.numChapters = book["media"].get("numChapters", 0)
|
||||||
|
_media.numTracks = book["media"].get("numTracks", 0)
|
||||||
|
_media.size = book["media"].get("size", 0)
|
||||||
|
_media.tags = book["media"].get("tags", [])
|
||||||
|
_book.media = _media
|
||||||
|
_book.mediaType = book.get("mediaType", "")
|
||||||
|
_book.mtimeMs = book.get("mtimeMs", 0)
|
||||||
|
_book.numFiles = book.get("numFiles", 0)
|
||||||
|
_book.oldLibraryItemId = book.get("oldLibraryItemId", "")
|
||||||
|
_book.path = book.get("path", "")
|
||||||
|
_book.relPath = book.get("relPath", "")
|
||||||
|
_book.size = book.get("size", 0)
|
||||||
|
_book.updatedAt = book.get("updatedAt", 0)
|
||||||
|
books.append(_book)
|
||||||
|
return books
|
||||||
|
return None
|
||||||
|
|||||||
@@ -1,3 +1,160 @@
|
|||||||
import curses
|
import curses
|
||||||
|
from .absapi import ABSApi, Library, Book, Media, Metadata
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
TERMINAL_HEIGHT, TERMINAL_WIDTH = curses.window().getmaxyx()
|
class ExitTerminal(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Terminal:
|
||||||
|
def __init__(self, stdscr: curses.window, running: bool = True) -> None:
|
||||||
|
self.stdscr: curses.window = stdscr
|
||||||
|
self.running: bool = running
|
||||||
|
self.api: ABSApi = ABSApi()
|
||||||
|
self.height: int = self.stdscr.getmaxyx()[0]
|
||||||
|
self.width: int = self.stdscr.getmaxyx()[1]
|
||||||
|
self.hSep: str = "-" * self.width
|
||||||
|
self.title: str = "Audiobookshelf CLI"
|
||||||
|
self.main_content_begin_x: int = 0
|
||||||
|
self.main_content_begin_y: int = 3
|
||||||
|
self.main_content_height: int = self.height - 5
|
||||||
|
self.main_content_width: int = self.width
|
||||||
|
self.main_menu: list[str] = [
|
||||||
|
"F1: View Libraries",
|
||||||
|
"F2: Search Books",
|
||||||
|
"F3: Now Playing",
|
||||||
|
"F4: Settings",
|
||||||
|
"F5: Help",
|
||||||
|
"F6: Exit",
|
||||||
|
]
|
||||||
|
self.active_window: str = "library"
|
||||||
|
self.playing: bool = False
|
||||||
|
|
||||||
|
def alignC(self, text: str) -> int:
|
||||||
|
return (self.width // 2) - (len(text) // 2)
|
||||||
|
|
||||||
|
def alignR(self, text: str) -> int:
|
||||||
|
return (self.height // 2) - (len(text) // 2)
|
||||||
|
|
||||||
|
def startup(self) -> None:
|
||||||
|
self.stdscr.clear()
|
||||||
|
self.menu()
|
||||||
|
self.stdscr.refresh()
|
||||||
|
self.library_window()
|
||||||
|
self.footer()
|
||||||
|
self.key_handler()
|
||||||
|
|
||||||
|
def menu(self) -> None:
|
||||||
|
self.stdscr.addstr(0, self.alignC(self.title), self.title)
|
||||||
|
_pos: int = 1
|
||||||
|
_chars: int = 0
|
||||||
|
_menu_start_pos: int = 0
|
||||||
|
for item in self.main_menu:
|
||||||
|
_chars += len(item) + 2
|
||||||
|
_menu_start_pos = (self.width // 2) - (_chars // 2)
|
||||||
|
_pos = _menu_start_pos
|
||||||
|
for item in self.main_menu:
|
||||||
|
self.stdscr.addstr(1, _pos + 1, item)
|
||||||
|
_pos += len(item) + 2
|
||||||
|
self.stdscr.addstr(2, 0, self.hSep)
|
||||||
|
|
||||||
|
def footer(self, text: Optional[str] = None) -> None:
|
||||||
|
_win = self.new_footer_window()
|
||||||
|
if text is None:
|
||||||
|
text = "Audiobookshelf CLI by: th3r00t > github.com/th3r00t/abstui"
|
||||||
|
_win.addstr(0, 0, self.hSep)
|
||||||
|
_win.addstr(1, self.alignC(text), text)
|
||||||
|
_win.refresh()
|
||||||
|
|
||||||
|
def new_main_window(self) -> curses.window:
|
||||||
|
return curses.newwin(self.main_content_height, self.main_content_width,
|
||||||
|
self.main_content_begin_y, self.main_content_begin_x)
|
||||||
|
|
||||||
|
def new_main_pad(self, item_count: int) -> curses.window:
|
||||||
|
return curses.newpad(item_count, self.main_content_width)
|
||||||
|
|
||||||
|
def new_footer_window(self) -> curses.window:
|
||||||
|
return curses.newwin(self.main_content_height, self.main_content_width,
|
||||||
|
self.main_content_height + 3, 0)
|
||||||
|
|
||||||
|
def library_window(self) -> None:
|
||||||
|
_y: int = 0
|
||||||
|
libraries: list[Library]|None = self.api.get_libraries()
|
||||||
|
books: list[Book]|None = self.api.get_books(libraries[0].id) if libraries else None
|
||||||
|
if books is not None:
|
||||||
|
_pad: curses.window = self.new_main_pad(len(books) if libraries else 1)
|
||||||
|
for book in books:
|
||||||
|
_pad.addstr(_y, 0, f"- {book.media.metadata.title}\t {book.media.metadata.authorName} \t {book.media.duration}")
|
||||||
|
_y += 1
|
||||||
|
_pad.refresh(0, 0, self.main_content_begin_y, self.main_content_begin_x,
|
||||||
|
self.main_content_begin_y + self.main_content_height - 1,
|
||||||
|
self.main_content_begin_x + self.main_content_width - 1)
|
||||||
|
else:
|
||||||
|
self.help_window("No books found in the library.")
|
||||||
|
self.active_window = "library"
|
||||||
|
|
||||||
|
def search_window(self) -> None:
|
||||||
|
self.active_window = "search"
|
||||||
|
_win: curses.window = self.new_main_window()
|
||||||
|
_win.addstr(0, 0, "Search Books - Feature not implemented yet.")
|
||||||
|
_win.refresh()
|
||||||
|
|
||||||
|
def now_playing_window(self) -> None:
|
||||||
|
self.active_window = "now_playing"
|
||||||
|
_win: curses.window = self.new_main_window()
|
||||||
|
_win.addstr(0, 0, "Now Playing - Feature not implemented yet.")
|
||||||
|
_win.refresh()
|
||||||
|
|
||||||
|
def settings_window(self) -> None:
|
||||||
|
self.active_window = "settings"
|
||||||
|
_win: curses.window = self.new_main_window()
|
||||||
|
_win.addstr(0, 0, "Settings - Feature not implemented yet.")
|
||||||
|
_win.refresh()
|
||||||
|
|
||||||
|
def help_window(self, error: str|None) -> None:
|
||||||
|
self.active_window = "help"
|
||||||
|
_win: curses.window = self.new_main_window()
|
||||||
|
help_text: list[str] = [
|
||||||
|
"Help Menu",
|
||||||
|
"",
|
||||||
|
"F1: View Libraries - Displays the list of available libraries.",
|
||||||
|
"F2: Search Books - Search for books in the libraries.",
|
||||||
|
"F3: Now Playing - View the currently playing book.",
|
||||||
|
"F4: Settings - Configure application settings.",
|
||||||
|
"F5: Help - Display this help menu.",
|
||||||
|
"F6: Exit - Exit the application.",
|
||||||
|
"",
|
||||||
|
f"Error: {error}" if error else "",
|
||||||
|
]
|
||||||
|
_y: int = 0
|
||||||
|
for line in help_text:
|
||||||
|
_win.addstr(_y, 0, line)
|
||||||
|
_y += 1
|
||||||
|
_win.refresh()
|
||||||
|
self.key_handler()
|
||||||
|
|
||||||
|
def play_toggle(self) -> None:
|
||||||
|
# Placeholder for play/pause functionality
|
||||||
|
if self.playing:
|
||||||
|
self.playing = False
|
||||||
|
else:
|
||||||
|
self.playing = True
|
||||||
|
self.footer(f"Playing {self.playing} - Feature not implemented yet.")
|
||||||
|
|
||||||
|
def key_handler(self) -> None:
|
||||||
|
while True:
|
||||||
|
key = self.stdscr.getch()
|
||||||
|
# Handle Menu Shortcuts
|
||||||
|
if key == curses.KEY_F1:
|
||||||
|
self.library_window()
|
||||||
|
elif key == curses.KEY_F2:
|
||||||
|
self.search_window()
|
||||||
|
elif key == curses.KEY_F3:
|
||||||
|
self.now_playing_window()
|
||||||
|
elif key == curses.KEY_F4:
|
||||||
|
self.settings_window()
|
||||||
|
elif key == curses.KEY_F5:
|
||||||
|
self.help_window(None)
|
||||||
|
elif key == curses.KEY_F6:
|
||||||
|
raise ExitTerminal()
|
||||||
|
elif key == ord(' '):
|
||||||
|
self.play_toggle()
|
||||||
|
|||||||
Reference in New Issue
Block a user