Skip to content

Commit 0fde233

Browse files
committedNov 18, 2020
extensive changes to Authenticator
* Introduced the new `Authenticator` class with the new classmethods `from_login` and `from_file`. * FileAuthenticator and LoginAuthenticator are deprecated. They will instantiate the new `Authenticator` instead a class of its own for now.
1 parent a4ddc85 commit 0fde233

16 files changed

+265
-201
lines changed
 

‎docs/source/auth/authentication.rst

+92-92
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,92 @@
1-
==============
2-
Authentication
3-
==============
4-
5-
API Authentication
6-
==================
7-
8-
Audible uses the `sign request` or the `bearer` method to authenticate the
9-
requests to the Audible API.
10-
11-
The authentication is done automatically when using the :class:`audible.LoginAuthenticator`
12-
or :class:`audible.FileAuthenticator`. Simply use an ``Authenticator`` with
13-
the :class:`audible.Client` or :class:`audible.AsyncClient` like so::
14-
15-
auth = audible.FileAuthenticator(...)
16-
client = audible.Client(auth=auth)
17-
18-
The Authenticator will try to use the sign request method if available.
19-
Otherwise the Authenticator will try the bearer method. If no method is
20-
available an exception is raised.
21-
22-
Sign request method
23-
-------------------
24-
25-
With the sign request method you gain unrestricted access to the Audible API.
26-
To use this method, you need the RSA private key and the adp_token from a
27-
*device registration*. This method is used by the Audible apps for iOS and
28-
Android too.
29-
30-
Request signing is fairly straight-forward and uses a signed SHA256 digest.
31-
Headers look like::
32-
33-
x-adp-alg: SHA256withRSA:1.0
34-
x-adp-signature: AAAAAAAA...:2019-02-16T00:00:01.000000000Z,
35-
x-adp-token: {enc:...}
36-
37-
Bearer method
38-
-------------
39-
40-
API requests with the bearer method are restricted. Some API call like the
41-
:http:post:`/1.0/content/(string:asin)/licenserequest` doesn't work. To use
42-
the bearer method you need an access token and a client id. You receive the
43-
token after an authorization or device registration. Which values are valid
44-
for the client-id is unknown but 0 does work. An access token expires after
45-
60 minutes. It can be renewed with a refresh token. A refresh token is obtained
46-
by a device registration only. Headers for the bearer method look like::
47-
48-
Authorization: Bearer Atna|...
49-
client-id: 0
50-
51-
Website Authentication
52-
======================
53-
54-
To authenticate website requests you need the website cookies received from an
55-
authorization or device registration.
56-
57-
You can use the website cookies from an ``Authenticator`` with a
58-
:class:`httpx.Client` or :class:`httpx.AsyncClient` like so::
59-
60-
auth = audible.FileAuthenticator(...)
61-
with httpx.Client(cookies=auth.website_cookies) as client:
62-
resp = client.get("https://www.amazon.com/cpe/yourpayments/wallet?ref_=ya_d_c_pmt_mpo")
63-
resp = client.get("https://www.audible.com")
64-
65-
.. note::
66-
67-
Website cookies are limited to the scope of a top level domain
68-
(e.g. com, de, ...). To set website cookies for another top level domain
69-
scope, you can call ``auth.set_website_cookies_for_country(COUNTRY_CODE)``.
70-
71-
.. warning::
72-
73-
Set website cookies for another country will override the old ones. If you
74-
want to keep the new cookies, please make sure to save your authentication data.
75-
76-
Using Postman for authentication
77-
================================
78-
79-
`Postman <https://www.postman.com>`_ is a helpful utility to test API's.
80-
81-
To use Postman with the Audible API, every request needs to be authenticated.
82-
You can use the bearer method (with his limitions) with Postman out of the box.
83-
84-
Using the sign request method with Postman is possible, but needs some extra work.
85-
86-
HOWTO:
87-
88-
1. Install the `postman_util_lib <https://joolfe.github.io/postman-util-lib/>`_
89-
2. Copy the content from the :download:`pre-request-script <../../../utils/postman/pm_pre_request.js>`
90-
into the `Pre-request Scripts` Tab for the Collection or request
91-
3. Create an Environment and define the variables `adp-token` and `private key`
92-
with the counterparts from the authentication data file
1+
==============
2+
Authentication
3+
==============
4+
5+
API Authentication
6+
==================
7+
8+
Audible uses the `sign request` or the `bearer` method to authenticate the
9+
requests to the Audible API.
10+
11+
The authentication is done automatically when using the
12+
:class:`audible.Authenticator`. Simply use the ``Authenticator`` with
13+
the :class:`audible.Client` or :class:`audible.AsyncClient` like so::
14+
15+
auth = audible.Authenticator.from_file(...)
16+
client = audible.Client(auth=auth)
17+
18+
The Authenticator will try to use the sign request method if available.
19+
Otherwise the Authenticator will try the bearer method. If no method is
20+
available an exception is raised.
21+
22+
Sign request method
23+
-------------------
24+
25+
With the sign request method you gain unrestricted access to the Audible API.
26+
To use this method, you need the RSA private key and the adp_token from a
27+
*device registration*. This method is used by the Audible apps for iOS and
28+
Android too.
29+
30+
Request signing is fairly straight-forward and uses a signed SHA256 digest.
31+
Headers look like::
32+
33+
x-adp-alg: SHA256withRSA:1.0
34+
x-adp-signature: AAAAAAAA...:2019-02-16T00:00:01.000000000Z,
35+
x-adp-token: {enc:...}
36+
37+
Bearer method
38+
-------------
39+
40+
API requests with the bearer method are restricted. Some API call like the
41+
:http:post:`/1.0/content/(string:asin)/licenserequest` doesn't work. To use
42+
the bearer method you need an access token and a client id. You receive the
43+
token after an authorization or device registration. Which values are valid
44+
for the client-id is unknown but 0 does work. An access token expires after
45+
60 minutes. It can be renewed with a refresh token. A refresh token is obtained
46+
by a device registration only. Headers for the bearer method look like::
47+
48+
Authorization: Bearer Atna|...
49+
client-id: 0
50+
51+
Website Authentication
52+
======================
53+
54+
To authenticate website requests you need the website cookies received from an
55+
authorization or device registration.
56+
57+
You can use the website cookies from an ``Authenticator`` with a
58+
:class:`httpx.Client` or :class:`httpx.AsyncClient` like so::
59+
60+
auth = audible.Authenticator.from_file(...)
61+
with httpx.Client(cookies=auth.website_cookies) as client:
62+
resp = client.get("https://www.amazon.com/cpe/yourpayments/wallet?ref_=ya_d_c_pmt_mpo")
63+
resp = client.get("https://www.audible.com")
64+
65+
.. note::
66+
67+
Website cookies are limited to the scope of a top level domain
68+
(e.g. com, de, ...). To set website cookies for another top level domain
69+
scope, you can call ``auth.set_website_cookies_for_country(COUNTRY_CODE)``.
70+
71+
.. warning::
72+
73+
Set website cookies for another country will override the old ones. If you
74+
want to keep the new cookies, please make sure to save your authentication data.
75+
76+
Using Postman for authentication
77+
================================
78+
79+
`Postman <https://www.postman.com>`_ is a helpful utility to test API's.
80+
81+
To use Postman with the Audible API, every request needs to be authenticated.
82+
You can use the bearer method (with his limitions) with Postman out of the box.
83+
84+
Using the sign request method with Postman is possible, but needs some extra work.
85+
86+
HOWTO:
87+
88+
1. Install the `postman_util_lib <https://joolfe.github.io/postman-util-lib/>`_
89+
2. Copy the content from the :download:`pre-request-script <../../../utils/postman/pm_pre_request.js>`
90+
into the `Pre-request Scripts` Tab for the Collection or request
91+
3. Create an Environment and define the variables `adp-token` and `private key`
92+
with the counterparts from the authentication data file

‎docs/source/auth/authorization.rst

+4-4
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ minutes. After expiration you have to authorize again to obtain a new one.
1818
Authorization
1919
=============
2020

21-
For an example how to authorization please take a look at :ref:`hello_library`.
21+
For an example how to authorize please take a look at :ref:`hello_library`.
2222

2323
CAPTCHA
2424
-------
@@ -40,7 +40,7 @@ external service), like so::
4040

4141
return "My answer for CAPTCHA"
4242

43-
auth = audible.LoginAuthenticator(
43+
auth = audible.Authenticator.from_login(
4444
...
4545
captcha_callback=custom_captcha_callback
4646
)
@@ -61,7 +61,7 @@ A custom callback can be provided, like so::
6161

6262
return "My answer for otp code"
6363

64-
auth = audible.LoginAuthenticator(
64+
auth = audible.Authenticator.from_login(
6565
...
6666
otp_callback=custom_otp_callback
6767
)
@@ -83,7 +83,7 @@ A custom callback can be provided, like so::
8383

8484
return "My answer for cvf code"
8585

86-
auth = audible.LoginAuthenticator(
86+
auth = audible.Authenticator.from_login(
8787
...
8888
cvf_callback=custom_cvf_callback
8989
)

‎docs/source/auth/register.rst

+3-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ successfully authorization.
1515

1616
To authorize and register a new device in one step you can do::
1717

18-
auth = audible.LoginAuthenticator(
18+
auth = audible.Authenticator.from_login(
1919
username,
2020
password,
2121
locale=country_code,
@@ -36,7 +36,8 @@ Deregister
3636
==========
3737

3838
Authentication data obtained by a device registration are valid until
39-
deregister. Call ``auth.deregister_device()`` the current used device.
39+
deregister. Call ``auth.deregister_device()`` to deregister the current used
40+
device.
4041

4142
Call ``auth.deregister_device(deregister_all=True)`` to deregister **ALL**
4243
Audible devices. This function is helpful to remove hanging slots. This can

‎docs/source/intro/getting_started.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ all known Audible marketplaces and associated country codes you can find at
2222
2323
import audible
2424
25-
auth = audible.LoginAuthenticator(
25+
auth = audible.Authenticator.from_login(
2626
USERNAME,
2727
PASSWORD,
2828
locale=COUNTRY_CODE
@@ -88,5 +88,5 @@ registration with::
8888

8989
And load the data from file to reuse it later with::
9090

91-
auth = audible.FileAuthenticator(FILENAME)
91+
auth = audible.Authenticator.from_file(FILENAME)
9292

‎docs/source/misc/advanced.rst

+26-13
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,10 @@ Switch User
8585
If you work with multiple users you can do this::
8686

8787
# instantiate 1st user
88-
auth = audible.FileAuthenticator(FILENAME)
88+
auth = audible.Authenticator.from_file(FILENAME)
8989

9090
# instantiate 2nd user
91-
auth2 = audible.FileAuthenticator(FILENAME2)
91+
auth2 = audible.Authenticator.from_file(FILENAME2)
9292

9393
# instantiate client with 1st user
9494
client = audible.AudibleAPI(auth)
@@ -104,19 +104,31 @@ If you work with multiple users you can do this::
104104
Misc
105105
----
106106

107-
The underlying Authenticator class can be accessed via the `auth` attribute.
107+
The underlying Authenticator can be accessed via the `auth` attribute.
108108

109109
Authenticator classes
110110
=====================
111111

112-
There are two Authenticator classes. The ``LoginAuthenticator``
113-
and the ``FileAuthenticator``. Both derive from ``BaseAuthenticator``.
112+
.. deprecated:: v0.5.0
114113

115-
The ``LoginAuthenticator`` is used to authorize an user and then authenticate
116-
requests with the received data. The ``FileAuthenticator`` is used to load
114+
The ``LoginAuthenticator`` and the ``FileAuthenticator``
115+
116+
.. versionchanged:: v0.5.0
117+
118+
The ``LoginAuthenticator`` and the ``FileAuthenticator`` now instantiate the
119+
the new :class:`Authenticator` instead a class of its own.
120+
121+
.. versionadded:: v0.5.0
122+
123+
The :class:`Authenticator` with the classmethods ``from_file`` and
124+
``from_login``
125+
126+
The :meth:`Authenticator.from_login` classmethod is used to authorize
127+
an user and then authenticate requests with the received data. The
128+
:meth:`Authenticator.from_file` classmethod is used to load
117129
previous saved authentication data.
118130

119-
With an Authenticator class you can:
131+
With an Authenticator you can:
120132

121133
- Save credentials to file with ``auth.to_file()``
122134
- Register a device with ``auth.register_device()`` after a fresh authorization.
@@ -138,10 +150,11 @@ Or to check the time left before token expires::
138150
Activation Bytes
139151
================
140152

141-
Since v0.4.0 this app can get activation bytes.
153+
.. versionadded:: v0.4.0
154+
155+
Get activation bytes
142156

143-
To retrieve activation bytes an authentication via :class:`LoginAuthenticator`
144-
or :class:`FileAuthenticator` is needed.
157+
To retrieve activation bytes an authentication :class:`Authenticator` is needed.
145158

146159
The Activation bytes can be obtained like so::
147160

@@ -170,7 +183,7 @@ are missing in the provided link. As a workaround you can do::
170183
import httpx
171184
172185
asin = ASIN_FROM_BOOK
173-
auth = audible.FileAuthenticator(...) # or LoginAuthenticator
186+
auth = audible.Authenticator.from_file(...) # or Authenticator.from_login
174187
tld = auth.locale.domain
175188

176189
with httpx.Client(auth=auth) as client:
@@ -200,4 +213,4 @@ To decrypt the license response you can do::
200213
to open servers, torrents, or other methods of mass distribution. No help
201214
will be given to people doing such things. Authors, retailers, and
202215
publishers all need to make a living, so that they can continue to produce
203-
audiobooks for us to hear, and enjoy. Don't be a parasite.
216+
audiobooks for us to hear, and enjoy. Don't be a parasite.

‎docs/source/misc/examples.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Print number of books for every marketplace::
99

1010
import audible
1111
12-
auth = audible.FileAuthenticator(filename)
12+
auth = audible.Authenticator.from_file(filename)
1313
client = audible.Client(auth)
1414
country_codes = ["de", "us", "ca", "uk", "au", "fr", "jp", "it", "in"]
1515

‎docs/source/misc/load_save.rst

+5-5
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Authentication data can be saved any time to file like so::
1111

1212
And can then be reused later like so::
1313

14-
auth = audible.FileAuthenticator(FILENAME)
14+
auth = audible.Authenticator.from_file(FILENAME)
1515

1616
.. note::
1717

@@ -44,7 +44,7 @@ Or in bytes style like so::
4444
When loading data from file, encryption style is auto detected. These files can
4545
be loaded like so::
4646

47-
auth = audible.FileAuthenticator(
47+
auth = audible.Authenticator.from_file(
4848
FILENAME,
4949
PASSWORD
5050
)
@@ -80,7 +80,7 @@ Advanced use of encryption/decryption
8080

8181
When saving authentication data, additional options can be provided with
8282
``auth.to_file(..., **kwargs)``. This data can be loaded with
83-
``auth = audible.FileAuthenticator(..., **kwargs)``.
83+
``auth = audible.Authenticator.from_file(..., **kwargs)``.
8484

8585
Following options are supported:
8686

@@ -115,8 +115,8 @@ Remove encryption
115115
=================
116116

117117
To remove encryption from file (or save as new file) simply load the encrypted
118-
file with the :class:`~audible.FileAuthenticator` and save the data
119-
unencrypted. If the `FileAuthenticator` can't load your data, you can try::
118+
file with :meth:`audible.Authenticator.from_file` and save the data
119+
unencrypted. If the `Authenticator` can't load your data, you can try::
120120

121121
from audible.aescipher import remove_file_encryption
122122

‎examples/async.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ async def main(auth):
5151
if __name__ == "__main__":
5252
# authenticate with login
5353
# don't stores any credentials on your system
54-
auth = audible.LoginAuthenticator(
54+
auth = audible.Authenticator.from_login(
5555
"USERNAME",
5656
"PASSWORD",
5757
locale="us"
@@ -73,7 +73,7 @@ async def main(auth):
7373
auth.to_file()
7474

7575
# load credentials from file
76-
auth = audible.FileAuthenticator(
76+
auth = audible.Authenticator.from_file(
7777
filename="FILENAME",
7878
password="PASSWORD"
7979
)

‎examples/download_books_aax.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def download_file(url):
5656
if __name__ == "__main__":
5757
password = input("Password for file: ")
5858

59-
auth = audible.FileAuthenticator(
59+
auth = audible.Authenticator.from_file(
6060
filename="FILENAME",
6161
password=password
6262
)

‎examples/download_books_aaxc.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def download_file(url, filename):
4545
if __name__ == "__main__":
4646
password = input("Password for file: ")
4747

48-
auth = audible.FileAuthenticator(
48+
auth = audible.Authenticator.from_file(
4949
filename="FILENAME",
5050
password=password
5151
)

‎examples/get_activation_bytes.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
import audible
1+
from audible import Authenticator
22

33
# needs at least audible v0.4.0
44

55
# if you have a valid auth file already
66
password = input("Password for file: ")
7-
auth = audible.FileAuthenticator(
7+
auth = Authenticator.from_file(
88
filename="FILENAME",
99
password=password
1010
)
1111

1212

1313
# or use LoginAuthenticator (without register)
14-
auth = audible.LoginAuthenticator(
14+
auth = Authenticator.from_login(
1515
username="USERNAME",
1616
password="PASSWORD",
1717
locale="YOUR_COUNTRY_CODE",

‎examples/sync.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,11 @@ def get_download_link(asin, quality="Extreme"):
7777

7878
if __name__ == "__main__":
7979
password = input("Password for file: ")
80-
81-
auth = audible.FileAuthenticator(
80+
auth = audible.Authenticator.from_file(
8281
filename="FILENAME",
8382
password=password
8483
)
85-
client = audible.AudibleAPI(auth)
84+
client = audible.Client(auth)
85+
8686
link = get_download_link("BOOK_ASIN")
8787
print(link)

‎src/audible/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# -*- coding: utf-8 -*-
22

33
from .client import Client, AsyncClient
4-
from .auth import LoginAuthenticator, FileAuthenticator
4+
from .auth import Authenticator, LoginAuthenticator, FileAuthenticator
55
from ._logging import log_helper
66
from audible._version import __version__
77

88

99
__all__ = [
10-
"__version__", "LoginAuthenticator", "FileAuthenticator",
10+
"__version__", "LoginAuthenticator", "FileAuthenticator", "Authenticator",
1111
"log_helper", "Client", "AsyncClient"
1212
]

‎src/audible/aescipher.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -260,8 +260,8 @@ def _decrypt_voucher(device_serial_number, customer_id, device_type, asin, vouch
260260
def decrypt_voucher_from_licenserequest(auth, license_response):
261261
"""Decrypt the voucher from licenserequest response
262262
263-
:param auth: An instance of an `Authenticator` clsss
264-
:type auth: audible.FileAuthenticator, audible.LoginAuthenticator
263+
:param auth: An instance of an `Authenticator`
264+
:type auth: audible.Authenticator
265265
:param license_response: The response content from a successful http
266266
`POST` request to api endpoint /1.0/content/{asin}/licenserequest
267267
:type license_response: dict

‎src/audible/auth.py

+114-63
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def sign_request(
130130
}
131131

132132

133-
class BaseAuthenticator(MutableMapping, httpx.Auth):
133+
class Authenticator(MutableMapping, httpx.Auth):
134134
"""Base Class for retrieve and handle credentials."""
135135

136136
requires_request_body = True
@@ -162,6 +162,88 @@ def __len__(self):
162162
def __repr__(self):
163163
return f"{type(self).__name__}({self.__dict__})"
164164

165+
@classmethod
166+
def from_file(
167+
cls, filename, password=None, locale=None, encryption=None, **kwargs
168+
) -> "Authenticator":
169+
"""Instantiate a new Authenticator with authentication data from file
170+
171+
.. versionadded:: v0.5.0
172+
"""
173+
cls = cls()
174+
cls.filename = filename
175+
cls.encryption = encryption or detect_file_encryption(cls.filename)
176+
177+
if cls.encryption:
178+
if password is None:
179+
message = "File is encrypted but no password provided."
180+
logger.critical(message)
181+
raise FileEncryptionError(message)
182+
cls.crypter = AESCipher(password, **kwargs)
183+
file_data = cls.crypter.from_file(cls.filename, cls.encryption)
184+
else:
185+
file_data = cls.filename.read_text()
186+
187+
json_data = json.loads(file_data)
188+
189+
locale_code = json_data.pop("locale_code", None)
190+
locale = locale or locale_code
191+
cls.locale = locale
192+
193+
# login cookies where renamed to website cookies
194+
# old names must be adjusted
195+
login_cookies = json_data.pop("login_cookies", None)
196+
if login_cookies:
197+
json_data["website_cookies"] = login_cookies
198+
199+
cls.update(**json_data)
200+
201+
logger.info(
202+
(
203+
f"load data from file {cls.filename} for "
204+
f"locale {cls.locale.country_code}"
205+
)
206+
)
207+
return cls
208+
209+
@classmethod
210+
def from_login(
211+
cls,
212+
username: str,
213+
password: str,
214+
locale,
215+
register=False,
216+
captcha_callback=None,
217+
otp_callback=None,
218+
cvf_callback=None
219+
) -> "Authenticator":
220+
"""Instantiate a new Authenticator with authentication data from login
221+
222+
.. versionadded:: v0.5.0
223+
"""
224+
cls = cls()
225+
cls.locale = locale
226+
227+
resp = login(
228+
username=username,
229+
password=password,
230+
country_code=cls.locale.country_code,
231+
domain=cls.locale.domain,
232+
market_place_id=cls.locale.market_place_id,
233+
captcha_callback=captcha_callback,
234+
otp_callback=otp_callback,
235+
cvf_callback=cvf_callback
236+
)
237+
238+
logger.info(f"logged in to audible as {username}")
239+
240+
if register:
241+
resp = register_(resp["access_token"], cls.locale.domain)
242+
logger.info("registered audible device")
243+
244+
cls.update(**resp)
245+
return cls
246+
165247
def auth_flow(self, request: httpx.Request):
166248
available_modes = self.available_auth_modes
167249

@@ -377,79 +459,48 @@ def access_token_expired(self):
377459
return datetime.fromtimestamp(self.expires) <= datetime.utcnow()
378460

379461

380-
class LoginAuthenticator(BaseAuthenticator):
381-
"""Authenticator class to retrieve credentials from login."""
462+
class LoginAuthenticator:
463+
"""Authenticator class to retrieve credentials from login.
464+
465+
.. deprecated:: v0.5.0
466+
467+
Use :classmethod:`audible.auth.Authenticator.from_login` instead.
382468
383-
def __init__(
384-
self,
469+
"""
470+
471+
def __new__(
472+
cls,
385473
username: str,
386474
password: str,
387475
locale,
388476
register=False,
389477
captcha_callback=None,
390478
otp_callback=None,
391479
cvf_callback=None
392-
):
393-
self.locale = locale
394-
395-
resp = login(
396-
username=username,
397-
password=password,
398-
country_code=self.locale.country_code,
399-
domain=self.locale.domain,
400-
market_place_id=self.locale.market_place_id,
401-
captcha_callback=captcha_callback,
402-
otp_callback=otp_callback,
403-
cvf_callback=cvf_callback
404-
)
405-
406-
logger.info(f"logged in to audible as {username}")
407-
408-
if register:
409-
resp = register_(resp["access_token"], self.locale.domain)
410-
logger.info("registered audible device")
480+
) -> Authenticator:
481+
return Authenticator.from_login(
482+
username,
483+
password,
484+
locale,
485+
register,
486+
captcha_callback,
487+
otp_callback,
488+
cvf_callback)
411489

412-
self.update(**resp)
413490

491+
class FileAuthenticator:
492+
"""Authenticator class to retrieve credentials from stored file.
493+
494+
.. deprecated:: v0.5.0
495+
496+
Use :classmethod:`audible.auth.Authenticator.from_file` instead.
414497
415-
class FileAuthenticator(BaseAuthenticator):
416-
"""Authenticator class to retrieve credentials from stored file."""
498+
"""
417499

418-
def __init__(
419-
self, filename, password=None, locale=None, encryption=None,
500+
def __new__(
501+
cls, filename, password=None, locale=None, encryption=None,
420502
**kwargs
421-
) -> None:
503+
) -> Authenticator:
422504

423-
self.filename = filename
424-
self.encryption = encryption or detect_file_encryption(self.filename)
425-
426-
if self.encryption:
427-
if password is None:
428-
message = "File is encrypted but no password provided."
429-
logger.critical(message)
430-
raise FileEncryptionError(message)
431-
self.crypter = AESCipher(password, **kwargs)
432-
file_data = self.crypter.from_file(self.filename, self.encryption)
433-
else:
434-
file_data = self.filename.read_text()
435-
436-
json_data = json.loads(file_data)
437-
438-
locale_code = json_data.pop("locale_code", None)
439-
locale = locale or locale_code
440-
self.locale = locale
441-
442-
# login cookies where renamed to website cookies
443-
# old names must be adjusted
444-
login_cookies = json_data.pop("login_cookies", None)
445-
if login_cookies:
446-
json_data["website_cookies"] = login_cookies
447-
448-
self.update(**json_data)
449-
450-
logger.info(
451-
(
452-
f"load data from file {self.filename} for "
453-
f"locale {self.locale.country_code}"
454-
)
455-
)
505+
return Authenticator.from_file(
506+
filename, password, locale, encryption, **kwargs)

‎src/audible/client.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import json
22
import logging
3-
from typing import Union, Optional
3+
from typing import Optional
44

55
import httpx
66
from httpx import URL
77

8-
from .auth import LoginAuthenticator, FileAuthenticator
8+
from .auth import Authenticator
99
from .exceptions import (
1010
BadRequest, NotFoundError, NotResponding, NetworkError, ServerError,
1111
Unauthorized, UnexpectedError, RatelimitError, RequestError
@@ -30,8 +30,7 @@ class Client:
3030

3131
def __init__(
3232
self,
33-
auth: Optional[
34-
Union[LoginAuthenticator, FileAuthenticator]] = None,
33+
auth: Authenticator = None,
3534
country_code: Optional[str] = None,
3635
timeout: int = 10
3736
):
@@ -78,7 +77,7 @@ def auth(self):
7877

7978
def switch_user(
8079
self,
81-
auth: Union[LoginAuthenticator, FileAuthenticator],
80+
auth: Authenticator,
8281
switch_to_default_marketplace: bool = False
8382
):
8483
if switch_to_default_marketplace:

0 commit comments

Comments
 (0)
Please sign in to comment.