Book Display
This commit is contained in:
@@ -60,6 +60,13 @@
|
||||
}
|
||||
|
||||
venvVersionWarn
|
||||
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
source .env
|
||||
set +a
|
||||
echo "Loaded .env file"
|
||||
fi
|
||||
'';
|
||||
|
||||
packages = with python.pkgs; [
|
||||
|
||||
@@ -1,11 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
import curses
|
||||
import argparse
|
||||
import sys
|
||||
from curses import wrapper
|
||||
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()
|
||||
|
||||
def main() -> None:
|
||||
pass
|
||||
# stdscr: curses.window = curses.initscr()
|
||||
def main(stdscr: curses.window) -> None:
|
||||
terminal: Terminal = Terminal(stdscr)
|
||||
Terminal.startup(terminal)
|
||||
|
||||
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 json
|
||||
# from typing import List, dict, Optional
|
||||
import json
|
||||
from typing import Any, Optional
|
||||
from enum import Enum
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from dataclasses import dataclass, field
|
||||
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):
|
||||
BOOKS = "/api/v1/book"
|
||||
AUTHORS = "/api/v1/author"
|
||||
SERIES = "/api/v1/series"
|
||||
COVERS = "/api/v1/cover"
|
||||
GENRES = "/api/v1/genre"
|
||||
LISTS = "/api/v1/list"
|
||||
USERS = "/api/v1/user"
|
||||
SETTINGS = "/api/v1/settings"
|
||||
STATS = "/api/v1/stats"
|
||||
PLAYBACK_POSITIONS = "/api/v1/playback-position"
|
||||
REVIEWS = "/api/v1/review"
|
||||
TAGS = "/api/v1/tag"
|
||||
PLUGINS = "/api/v1/plugin"
|
||||
"""
|
||||
Enum for ABS API endpoints.
|
||||
https://api.audiobookshelf.org/#endpoints
|
||||
Parameters:
|
||||
item_id (str, optional): The ID of the item. Default is None.
|
||||
episode_id (str, optional): The ID of the episode. Default is None.
|
||||
DELETEALLITEMS "/api/items/all""
|
||||
DELETEITEM DELETE lambda item_id: f"/api/items/{item_id}"
|
||||
GETITEM GET lambda item_id: f"/api/items/{item_id}"
|
||||
UPDATEITEM PATCH lambda item_id: f"/api/items/{item_id}/media"
|
||||
PLAYITEM POST lambda item_id: f"/api/items/{item_id}/play"
|
||||
PLAYEPISODE POST lambda item_id, episode_id: f"/api/items/{item_id}/play/{episode_id}""
|
||||
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
|
||||
class ABSResponse:
|
||||
@@ -46,3 +197,134 @@ class ABSApi:
|
||||
ABSResponse: The response from the API.
|
||||
"""
|
||||
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
|
||||
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