Chris Pomeroy
9 days ago 2bca657babf8bec3f9cee673c92eb21f68a68a68
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#!/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()