feat: add a method to list unfinished books with percentage and duration
This commit is contained in:
215
main.py
215
main.py
@@ -58,20 +58,111 @@ class Auditui:
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_runtime(minutes):
|
def format_duration(value, unit='minutes', default_none=None):
|
||||||
if minutes is None or minutes == 0:
|
if value is None or value <= 0:
|
||||||
return "Unknown length"
|
return default_none
|
||||||
|
|
||||||
minutes = int(minutes)
|
if unit == 'seconds':
|
||||||
if minutes < 60:
|
total_minutes = int(value) // 60
|
||||||
return f"{minutes} minutes"
|
else:
|
||||||
|
total_minutes = int(value)
|
||||||
|
|
||||||
hours = minutes // 60
|
if total_minutes < 60:
|
||||||
mins = minutes % 60
|
return f"{total_minutes} minute{'s' if total_minutes != 1 else ''}"
|
||||||
|
|
||||||
|
hours = total_minutes // 60
|
||||||
|
mins = total_minutes % 60
|
||||||
if mins == 0:
|
if mins == 0:
|
||||||
return f"{hours} hour{'s' if hours != 1 else ''}"
|
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 ''}"
|
return f"{hours} hour{'s' if hours != 1 else ''} {mins} minute{'s' if mins != 1 else ''}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _display_items(items):
|
||||||
|
if not items:
|
||||||
|
print("No books found.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("-" * 80)
|
||||||
|
|
||||||
|
for idx, item in enumerate(items, 1):
|
||||||
|
product = item.get("product", {})
|
||||||
|
|
||||||
|
title = product.get("title")
|
||||||
|
if not title:
|
||||||
|
title = item.get("title")
|
||||||
|
if not title:
|
||||||
|
title = product.get("asin", "Unknown Title")
|
||||||
|
|
||||||
|
authors = product.get("authors", [])
|
||||||
|
if not authors:
|
||||||
|
authors = product.get("contributors", [])
|
||||||
|
if not authors and "authors" in item:
|
||||||
|
authors = item.get("authors", [])
|
||||||
|
author_names = ", ".join([a.get("name", "")
|
||||||
|
for a in authors if isinstance(a, dict)])
|
||||||
|
|
||||||
|
runtime = None
|
||||||
|
runtime_fields = [
|
||||||
|
"runtime_length_min",
|
||||||
|
"runtime_length",
|
||||||
|
"vLength",
|
||||||
|
"length",
|
||||||
|
"duration"
|
||||||
|
]
|
||||||
|
|
||||||
|
for field in runtime_fields:
|
||||||
|
runtime = product.get(field)
|
||||||
|
if runtime is None:
|
||||||
|
runtime = item.get(field)
|
||||||
|
if runtime is not None:
|
||||||
|
break
|
||||||
|
|
||||||
|
minutes = None
|
||||||
|
if isinstance(runtime, dict):
|
||||||
|
if "min" in runtime:
|
||||||
|
minutes = int(runtime.get("min", 0))
|
||||||
|
elif isinstance(runtime, (int, float)):
|
||||||
|
minutes = int(runtime)
|
||||||
|
|
||||||
|
runtime_str = Auditui.format_duration(
|
||||||
|
minutes, unit='minutes', default_none="Unknown length")
|
||||||
|
|
||||||
|
asin = product.get("asin") or item.get("asin", "")
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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 = Auditui.format_duration(
|
||||||
|
time_remaining_seconds, unit='seconds')
|
||||||
|
if time_remaining_str:
|
||||||
|
print(f" Time remaining: {time_remaining_str}")
|
||||||
|
|
||||||
|
if asin:
|
||||||
|
print(f" ASIN: {asin}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("-" * 80)
|
||||||
|
print(f"Total: {len(items)} books")
|
||||||
|
|
||||||
def list_library(self):
|
def list_library(self):
|
||||||
client = audible.Client(auth=self.auth)
|
client = audible.Client(auth=self.auth)
|
||||||
|
|
||||||
@@ -109,63 +200,76 @@ class Auditui:
|
|||||||
print("Your library is empty.")
|
print("Your library is empty.")
|
||||||
return
|
return
|
||||||
|
|
||||||
print("-" * 80)
|
self._display_items(all_items)
|
||||||
|
|
||||||
for idx, item in enumerate(all_items, 1):
|
except Exception as e:
|
||||||
product = item.get("product", {})
|
print(f"Error fetching library: {e}")
|
||||||
|
|
||||||
title = product.get("title")
|
def list_unfinished(self):
|
||||||
if not title:
|
client = audible.Client(auth=self.auth)
|
||||||
title = item.get("title")
|
|
||||||
if not title:
|
|
||||||
title = product.get("asin", "Unknown Title")
|
|
||||||
|
|
||||||
authors = product.get("authors", [])
|
try:
|
||||||
if not authors:
|
print("\nFetching your library...")
|
||||||
authors = product.get("contributors", [])
|
|
||||||
if not authors and "authors" in item:
|
|
||||||
authors = item.get("authors", [])
|
|
||||||
author_names = ", ".join([a.get("name", "")
|
|
||||||
for a in authors if isinstance(a, dict)])
|
|
||||||
|
|
||||||
runtime = None
|
all_items = []
|
||||||
runtime_fields = [
|
page = 1
|
||||||
"runtime_length_min",
|
page_size = 50
|
||||||
"runtime_length",
|
|
||||||
"vLength",
|
|
||||||
"length",
|
|
||||||
"duration"
|
|
||||||
]
|
|
||||||
|
|
||||||
for field in runtime_fields:
|
while True:
|
||||||
runtime = product.get(field)
|
library = client.get(
|
||||||
if runtime is None:
|
path="library",
|
||||||
runtime = item.get(field)
|
num_results=page_size,
|
||||||
if runtime is not None:
|
page=page,
|
||||||
break
|
response_groups="contributors,media,product_attrs,product_desc,product_details,rating,is_finished,listening_status,percent_complete"
|
||||||
|
)
|
||||||
|
|
||||||
minutes = None
|
items = library.get("items", [])
|
||||||
if isinstance(runtime, dict):
|
|
||||||
if "min" in runtime:
|
|
||||||
minutes = int(runtime.get("min", 0))
|
|
||||||
elif isinstance(runtime, (int, float)):
|
|
||||||
minutes = int(runtime)
|
|
||||||
|
|
||||||
runtime_str = self.format_runtime(minutes)
|
if not items:
|
||||||
|
break
|
||||||
|
|
||||||
asin = product.get("asin") or item.get("asin", "")
|
all_items.extend(items)
|
||||||
|
print(f"Fetched page {page} ({len(items)} items)...", end="\r")
|
||||||
|
|
||||||
print(f"{idx}. {title}")
|
if len(items) < page_size:
|
||||||
if author_names:
|
break
|
||||||
print(f" Author: {author_names}")
|
|
||||||
print(f" Length: {runtime_str}")
|
|
||||||
if asin:
|
|
||||||
print(f" ASIN: {asin}")
|
|
||||||
|
|
||||||
print()
|
page += 1
|
||||||
|
|
||||||
print("-" * 80)
|
print(f"\nFetched {len(all_items)} books total.\n")
|
||||||
print(f"Total: {len(all_items)} books")
|
|
||||||
|
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:
|
except Exception as e:
|
||||||
print(f"Error fetching library: {e}")
|
print(f"Error fetching library: {e}")
|
||||||
@@ -174,7 +278,8 @@ class Auditui:
|
|||||||
def main():
|
def main():
|
||||||
client = Auditui()
|
client = Auditui()
|
||||||
client.login_to_audible()
|
client.login_to_audible()
|
||||||
client.list_library()
|
# client.list_library()
|
||||||
|
client.list_unfinished()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user