Add initial implementation of Audible Series Checker with API connectors and configuration

This commit is contained in:
Yunn Xairou 2025-08-23 14:57:12 +02:00
commit 223bfbf6bc
10 changed files with 630 additions and 0 deletions

12
connectors/__init__.py Normal file
View file

@ -0,0 +1,12 @@
from .abs_connector import ABSConnector, ABSConnectorMock
from .audible_connector import AudibleConnector, AudibleConnectorMock
from .audnexus_connector import AudNexusConnector, AudNexusConnectorMock
__all__ = [
ABSConnector,
ABSConnectorMock,
AudibleConnector,
AudibleConnectorMock,
AudNexusConnector,
AudNexusConnectorMock,
]

View file

@ -0,0 +1,61 @@
import requests
import json
class ABSConnector:
def __init__(self, abs_url, token=None):
self.abs_url = abs_url
self.requests = requests.Session()
self.requests.headers = {"Authorization": f"Bearer {token}"}
def get_library_ids(self):
endpoint = f"{self.abs_url}/api/libraries"
response = self.requests.get(endpoint)
response.raise_for_status()
data = response.json()
return data["libraries"]
def get_series_by_library_id(self, library_id, page_size=100):
endpoint = f"{self.abs_url}/api/libraries/{library_id}/series"
page = 0
while True:
response = self.requests.get(
endpoint,
params={
"limit": page_size,
"page": page,
"minified": 1,
"sort": "name",
},
)
response.raise_for_status()
data = response.json()
yield from data["results"]
page += 1
if data["total"] < page_size * page: # Stop if no more data
break
class ABSConnectorMock(ABSConnector):
def get_library_ids(self):
with open("dumps/libraries.json", "r") as f:
data = json.load(f)
return data["libraries"]
def get_series_by_library_id(self, library_id, page_size=100):
page = 0
while True:
with open(f"dumps/library_{library_id}.page{page}.json", "r") as f:
data = json.load(f)
yield from data["results"]
page += 1
if data["total"] < page_size * page: # Stop if no more data
break

View file

@ -0,0 +1,56 @@
import os
import audible
import json
from getpass import getpass
class AudibleConnector:
def __init__(self, authFile):
self.client: audible.Client = None
self._setup_auth(authFile)
def __del__(self):
if self.client:
self.client.close()
def _setup_auth(self, authFile=None):
try:
if authFile and os.path.exists(authFile):
self.auth = audible.Authenticator.from_file(authFile)
else:
self.auth = audible.Authenticator.from_login(
input("Username "),
getpass("Password "),
locale="us",
with_username=False,
)
if authFile:
self.auth.to_file(authFile)
except (
OSError,
audible.exceptions.AuthFlowError,
) as e:
print(f"Authentication failed: {e}")
raise ConnectionError(f"Failed to authenticate: {e}")
self.client = audible.Client(self.auth)
def get_produce_from_asin(self, asin):
endpoint = f"1.0/catalog/products/{asin}"
response = self.client.get(
endpoint, response_groups="series, relationships, product_attrs"
)
return response["product"]
class AudibleConnectorMock(AudibleConnector):
def get_produce_from_asin(self, asin):
try:
with open(f"dumps/products_{asin}.json", "r") as f:
data = json.load(f)
return data["product"]
except FileNotFoundError:
data = AudibleConnector.get_produce_from_asin(self, asin)
with open(f"dumps/products_{asin}.json", "w+") as f:
json.dump({"product": data}, f, indent=4)
return data

View file

@ -0,0 +1,29 @@
from ratelimit import limits
import requests
import json
class AudNexusConnector:
@limits(calls=100, period=60)
def request(self, url):
return requests.get(url, {"update": 0, "seedAuthors": 0})
def get_book_from_asin(self, book_asin):
endpoint = f"https://api.audnex.us/books/{book_asin}"
response = self.request(endpoint)
response.raise_for_status()
return response.json()
class AudNexusConnectorMock(AudNexusConnector):
def get_book_from_asin(self, book_asin):
try:
with open(f"dumps/book_{book_asin}.json", "r") as f:
data = json.load(f)
return data
except FileNotFoundError:
data = AudNexusConnector.get_book_from_asin(self, book_asin)
with open(f"dumps/book_{book_asin}.json", "w+") as f:
json.dump(data, f, indent=4)
return data