diff --git a/audible-api-test.py b/audible-api-test.py deleted file mode 100644 index 38aca0b..0000000 --- a/audible-api-test.py +++ /dev/null @@ -1,285 +0,0 @@ -#!/usr/bin/env python3 - -import sys -from getpass import getpass -from pathlib import Path - -try: - import audible -except ImportError: - print("Error: audible library not found. Install it with: pip install audible") - sys.exit(1) - - -class Auditui: - AUTH_PATH = Path.home() / ".config" / "auditui" / "auth.json" - - def __init__(self): - self.auth = None - self.client = None - - def login_to_audible(self): - auth_file = self.AUTH_PATH - auth_file.parent.mkdir(parents=True, exist_ok=True) - - if auth_file.exists(): - try: - self.auth = audible.Authenticator.from_file(str(auth_file)) - print("Loaded existing authentication.") - self.client = audible.Client(auth=self.auth) - return - except Exception as e: - print(f"Failed to load existing auth: {e}") - print("Please re-authenticate.") - - print("Please authenticate with your Audible account.") - print("You will need to provide:") - print(" - Your Audible email/username") - print(" - Your password") - print(" - Your marketplace locale (e.g., 'US', 'UK', 'DE', 'FR')") - - email = input("Email: ") - password = getpass("Password: ") - marketplace = ( - input("Marketplace locale (default: US): ").strip().upper() or "US" - ) - - try: - self.auth = audible.Authenticator.from_login( - username=email, password=password, locale=marketplace - ) - - auth_file.parent.mkdir(parents=True, exist_ok=True) - self.auth.to_file(str(auth_file)) - print("Authentication successful! Credentials saved.") - self.client = audible.Client(auth=self.auth) - except Exception as e: - print(f"Authentication failed: {e}") - sys.exit(1) - - def format_duration(self, value, unit="minutes", default_none=None): - if value is None or value <= 0: - return default_none - - if unit == "seconds": - total_minutes = int(value) // 60 - else: - total_minutes = int(value) - - if total_minutes < 60: - return f"{total_minutes} minute{'s' if total_minutes != 1 else ''}" - - hours = total_minutes // 60 - mins = total_minutes % 60 - if mins == 0: - return f"{hours} hour{'s' if hours != 1 else ''}" - return f"{hours} hour{'s' if hours != 1 else ''} {mins} minute{'s' if mins != 1 else ''}" - - def _extract_title(self, item): - product = item.get("product", {}) - return ( - product.get("title") - or item.get("title") - or product.get("asin", "Unknown Title") - ) - - def _extract_authors(self, item): - product = item.get("product", {}) - authors = product.get("authors") or product.get("contributors") or [] - if not authors and "authors" in item: - authors = item.get("authors", []) - return ", ".join([a.get("name", "") for a in authors if isinstance(a, dict)]) - - def _extract_runtime_minutes(self, item): - product = item.get("product", {}) - runtime_fields = [ - "runtime_length_min", - "runtime_length", - "vLength", - "length", - "duration", - ] - - runtime = None - for field in runtime_fields: - runtime = product.get(field) - if runtime is None: - runtime = item.get(field) - if runtime is not None: - break - - if runtime is None: - return None - - if isinstance(runtime, dict): - if "min" in runtime: - return int(runtime.get("min", 0)) - elif isinstance(runtime, (int, float)): - return int(runtime) - - return None - - def _extract_progress_info(self, item): - percent_complete = item.get("percent_complete") - listening_status = item.get("listening_status", {}) - - if isinstance(listening_status, dict): - if percent_complete is None: - percent_complete = listening_status.get("percent_complete") - time_remaining_seconds = listening_status.get( - "time_remaining_seconds") - else: - time_remaining_seconds = None - - return percent_complete, time_remaining_seconds - - def _display_items(self, items): - if not items: - print("No books found.") - return - - print("-" * 80) - - for idx, item in enumerate(items, 1): - title = self._extract_title(item) - author_names = self._extract_authors(item) - minutes = self._extract_runtime_minutes(item) - runtime_str = self.format_duration( - minutes, unit="minutes", default_none="Unknown length" - ) - percent_complete, time_remaining_seconds = self._extract_progress_info( - item) - - print(f"{idx}. {title}") - if author_names: - print(f" Author: {author_names}") - print(f" Length: {runtime_str}") - - if percent_complete is not None and percent_complete > 0: - percent_str = f"{percent_complete:.1f}%" - print(f" Progress: {percent_str} read") - - if time_remaining_seconds: - time_remaining_str = self.format_duration( - time_remaining_seconds, unit="seconds" - ) - if time_remaining_str: - print(f" Time remaining: {time_remaining_str}") - - print() - - print("-" * 80) - print(f"Total: {len(items)} books") - - def _fetch_all_pages(self, response_groups): - all_items = [] - page = 1 - page_size = 50 - - while True: - library = self.client.get( - path="library", - num_results=page_size, - page=page, - response_groups=response_groups, - ) - - items = library.get("items", []) - - if not items: - break - - all_items.extend(items) - print(f"Fetched page {page} ({len(items)} items)...", end="\r") - - if len(items) < page_size: - break - - page += 1 - - return all_items - - def list_library(self): - try: - print("\nFetching your library...") - - all_items = self._fetch_all_pages( - "contributors,media,product_attrs,product_desc,product_details,rating" - ) - - print(f"\nFetched {len(all_items)} books total.\n") - - if not all_items: - print("Your library is empty.") - return - - self._display_items(all_items) - - except Exception as e: - print(f"Error fetching library: {e}") - - def list_unfinished(self): - try: - print("\nFetching your library...") - - all_items = self._fetch_all_pages( - "contributors,media,product_attrs,product_desc,product_details,rating,is_finished,listening_status,percent_complete" - ) - - print(f"\nFetched {len(all_items)} books total.\n") - - unfinished_items = [] - finished_count = 0 - for item in all_items: - is_finished_flag = item.get("is_finished") - percent_complete = item.get("percent_complete") - listening_status = item.get("listening_status") - - if isinstance(listening_status, dict): - is_finished_flag = is_finished_flag or listening_status.get( - "is_finished", False - ) - percent_complete = ( - percent_complete - if percent_complete is not None - else listening_status.get("percent_complete", 0) - ) - - is_finished = False - if is_finished_flag is True: - is_finished = True - elif ( - isinstance(percent_complete, (int, float)) - and percent_complete >= 100 - ): - is_finished = True - - if is_finished: - finished_count += 1 - else: - unfinished_items.append(item) - - print( - f"Found {len(unfinished_items)} unfinished books (filtered out { - finished_count} finished books).\n" - ) - - if not unfinished_items: - print("No unfinished books found.") - return - - self._display_items(unfinished_items) - - except Exception as e: - print(f"Error fetching library: {e}") - - -def main(): - client = Auditui() - client.login_to_audible() - # client.list_library() - client.list_unfinished() - - -if __name__ == "__main__": - main() diff --git a/tui-try.py b/tui-try.py deleted file mode 100644 index c909356..0000000 --- a/tui-try.py +++ /dev/null @@ -1,56 +0,0 @@ -from textual.app import App, ComposeResult -from textual.widgets import Footer, Header, DataTable - - -class AudituiApp(App): - BINDINGS = [ - ("d", "toggle_dark", "Toggle dark mode"), - ("s", "sort", "Sort by title"), - ("r", "reverse_sort", "Reverse sort"), - ("q", "quit", "Quit application") - ] - - def compose(self) -> ComposeResult: - yield Header() - table = DataTable() - table.zebra_stripes = True - table.cursor_type = "row" - yield table - yield Footer() - - def on_mount(self) -> None: - table = self.query_one(DataTable) - table.add_columns("Title", "Author", "Length", "Progress") - self.title_column_key = list(table.columns.keys())[0] - - sample_books = [ - ("The Great Gatsby", "F. Scott Fitzgerald", "4h 30m", "100%"), - ("1984", "George Orwell", "11h 25m", "75%"), - ("To Kill a Mockingbird", "Harper Lee", "12h 17m", "50%"), - ("Pride and Prejudice", "Jane Austen", "11h 35m", "0%"), - ("The Catcher in the Rye", "J.D. Salinger", "7h 20m", "25%"), - ("Lord of the Flies", "William Golding", "6h 35m", "100%"), - ("Animal Farm", "George Orwell", "3h 15m", "90%"), - ("Brave New World", "Aldous Huxley", "10h 45m", "60%"), - ] - - for title, author, length, progress in sample_books: - table.add_row(title, author, length, progress, key=title) - - def action_toggle_dark(self) -> None: - self.theme = ( - "textual-dark" if self.theme == "textual-light" else "textual-light" - ) - - def action_sort(self) -> None: - table = self.query_one(DataTable) - table.sort(self.title_column_key) - - def action_reverse_sort(self) -> None: - table = self.query_one(DataTable) - table.sort(self.title_column_key, reverse=True) - - -if __name__ == "__main__": - app = AudituiApp() - app.run()