#!/usr/local/bin/python3 import httpx import aaxConvert import argparse import audible import json import time from audible.aescipher import decrypt_voucher_from_licenserequest from audible.activation_bytes import ( extract_activation_bytes, fetch_activation_sign_auth ) _audible_auth = audible.Authenticator.from_file('./audible_auth_Jeremy') ab = fetch_activation_sign_auth(auth=_audible_auth) ab = extract_activation_bytes(ab) _actbytes = ab _bearer_token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjOWZlOGUyOS0xZjM3LTQ1OWEtOWJiOS02NDg3ZjNjNDNiNGUiLCJ1c2VybmFtZSI6InJvb3QiLCJpYXQiOjE3NTAwNDA1MDl9.kDueJYRlXna-IRZu5jDjTUjwGYp0y01P9TdNw4d5PeE' # noqa E501 _header = { "Authorization": f"Bearer {_bearer_token}" } _metadata = {} def get_args(): ''' Parses the args for runtime Returns: args class type ''' parser = argparse.ArgumentParser(exit_on_error=False) parser.add_argument("-a", "--asin", help="ASIN of the book to download and convert", default=None, required=False) return parser.parse_args() def get_library_ids() -> list: ''' Returns a list of library id's Args: url (str): url to query Returns: list: A list of libraries avaliable to query. ''' libs = [] with httpx.Client(base_url="https://bookshelf.darkurthe.net/api/", headers=_header) as c: libraries = c.get('libraries').json() for library in libraries['libraries']: libs.append(library["id"]) c.close() return libs def get_bookshelf_books(library_id, limit=0): with httpx.Client(base_url="https://bookshelf.darkurthe.net/api/", headers=_header) as c: items = c.get(f'libraries/{library_id}/items?limit={limit}', timeout=30.0).json() return items['results'] def search_local_books(a_book, l_books) -> list: search_title = [book for book in l_books if a_book['title'] in book['media']['metadata']['title']] search_asin = [ book for book in l_books if a_book['asin'] == book['media']['metadata']['asin'] ] if search_title or search_asin: return False else: return True def get_audible_books() -> list: ''' Downloads a list of audiobooks from audible. Returns: list: A list of books and details in dictionary format ''' with audible.Client(auth=_audible_auth) as client: library = client.get( "1.0/library/", num_results=1000, response_groups="product_desc, product_attrs", sort_by="-PurchaseDate" ) books = library['items'] return books def get_license_request(asin: str) -> str: ''' Gets the url for the book to download Args: asin (str): The audiobook asin("Amazon Standard Identification Number") Returns: dict: The dict of the license request ''' body = { "supported_drm_types": [ "Mpeg", "Adrm" ], "quality": "High", "consumption_type": "Download" } with audible.Client(auth=_audible_auth) as client: content_url = client.post( f"1.0/content/{asin}/licenserequest", body=body ) return content_url def get_content_url(licenserequest: dict) -> str: ''' Gets the url out of the license request. Arg: license request (dict): Ret: str: The url to download the file from ''' return licenserequest['content_license']['content_metadata'][ 'content_url']['offline_url'] def get_content(asin: str, content_url: str) -> None: '''Downloads the content from audible Args: asin (str): The audiobook asin("Amazon Standard Identification Number") content_url (str): The url to download the book from ''' headers = {"User-Agent": "Audible/671 CFNetwork/1240.0.4 Darwin/20.6.0"} dl = httpx.get(content_url, timeout=30, headers=headers) with open(f"/tmp/{asin}.aax", 'wb') as f: f.write(dl.content) def download_books(asin: str) -> None: ''' Download the book and hand it off to the converter Args: title (str): The title of the book to be downloaded asin (str): The amazon number of the book being downloaded ''' lr = get_license_request(asin) print(ab) get_content(asin, get_content_url(lr)) decrypted_voucher = decrypt_voucher_from_licenserequest(_audible_auth, lr) key_iv = { 'key': decrypted_voucher['key'], 'iv': decrypted_voucher['iv'] } print(f"{asin} ready for conversion.") aaxConvert.convert_aax(f"/tmp/{asin}.aax", _actbytes, key_iv) def main(): args = get_args() asin_manual = args.asin audible_books = get_audible_books() if asin_manual is not None: download_books(asin_manual) else: library_ids = get_library_ids() for id in library_ids: bookshelf_books = get_bookshelf_books(id) for book in audible_books: if book['content_type'] == 'Product': search = search_local_books(book, bookshelf_books) if search: print(f"{book['title']}, {book['asin']}") download_books(book['asin']) time.sleep(20) if __name__ == "__main__": main()