def __RefreshAuthentication(self) -> None: headers = self.__GetHeaderWithAccessToken() postData = { "AppVersion": Kobo.ApplicationVersion, "ClientKey": base64.b64encode(Kobo.DefaultPlatformId.encode()).decode(), "PlatformId": Kobo.DefaultPlatformId, "RefreshToken": self.user.RefreshToken, } # The reauthentication hook is intentionally not set. response = self.Session.post( "https://storeapi.kobo.com/v1/auth/refresh", json=postData, headers=headers ) debug_data("RefreshAuth", postData, response.text) response.raise_for_status() jsonResponse = response.json() if jsonResponse["TokenType"] != "Bearer": raise KoboException( "Authentication refresh returned with an unsupported token type: '%s'" % jsonResponse["TokenType"] ) self.user.AccessToken = jsonResponse["AccessToken"] self.user.RefreshToken = jsonResponse["RefreshToken"] if not self.user.AreAuthenticationSettingsSet(): raise KoboException("Authentication settings are not set after authentication refresh.") Globals.Settings.Save()
def GetMyWishList(self) -> list: items = [] currentPageIndex = 0 while True: url = self.InitializationSettings["user_wishlist"] headers = self.__GetHeaderWithAccessToken() hooks = self.__GetReauthenticationHook() params = { "PageIndex": currentPageIndex, "PageSize": 100, # 100 is the default if PageSize is not specified. } debug_data("GetMyWishList") response = self.Session.get(url, params=params, headers=headers, hooks=hooks) response.raise_for_status() wishList = response.json() items.extend(wishList["Items"]) currentPageIndex += 1 if currentPageIndex >= wishList["TotalPageCount"]: break return items
def ReauthenticationHook(r, *args, **kwargs): debug_data("Response", r.text) if r.status_code != requests.codes.unauthorized: # 401 return print("Refreshing expired authentication token...", file=sys.stderr) # Consume content and release the original connection to allow our new request to reuse the same one. r.content r.close() prep = r.request.copy() # Refresh the authentication token and use it. self.__RefreshAuthentication() headers = self.__GetHeaderWithAccessToken() prep.headers["Authorization"] = headers["Authorization"] # Don't retry to reauthenticate this request again. prep.deregister_hook("response", ReauthenticationHook) # Resend the failed request. _r = r.connection.send(prep, **kwargs) _r.history.append(r) _r.request = prep return _r
def cli(ctx, fmt, config, debug): Globals.Settings = Settings(config) Globals.Debug = debug ctx.obj = { 'fmt': fmt, 'debug': debug, } debug_data(sys.argv)
def __GetContentAccessBook(self, productId: str, displayProfile: str) -> dict: url = self.InitializationSettings["content_access_book"].replace("{ProductId}", productId) params = {"DisplayProfile": displayProfile} headers = self.__GetHeaderWithAccessToken() hooks = self.__GetReauthenticationHook() debug_data("GetContentAccessBook") response = self.Session.get(url, params=params, headers=headers, hooks=hooks) response.raise_for_status() jsonResponse = response.json() return jsonResponse
def GetBookInfo(self, productId: str) -> dict: audiobook_url = self.InitializationSettings["audiobook"].replace("{ProductId}", productId) ebook_url = self.InitializationSettings["book"].replace("{ProductId}", productId) headers = self.__GetHeaderWithAccessToken() hooks = self.__GetReauthenticationHook() debug_data("GetBookInfo") try: response = self.Session.get(ebook_url, headers=headers, hooks=hooks) response.raise_for_status() except requests.HTTPError as err: response = self.Session.get(audiobook_url, headers=headers, hooks=hooks) response.raise_for_status() jsonResponse = response.json() return jsonResponse
def Login(self, email: str, password: str, captcha: str) -> None: ( signInUrl, workflowId, requestVerificationToken, ) = self.__GetExtraLoginParameters() postData = { "LogInModel.WorkflowId": workflowId, "LogInModel.Provider": Kobo.Affiliate, "ReturnUrl": "", "__RequestVerificationToken": requestVerificationToken, "LogInModel.UserName": email, "LogInModel.Password": password, "g-recaptcha-response": captcha, } response = self.Session.post(signInUrl, data=postData) debug_data("Login", response.text) response.raise_for_status() htmlResponse = response.text match = re.search(r"'(kobo://UserAuthenticated\?[^']+)';", htmlResponse) if match is None: soup = BeautifulSoup(htmlResponse, 'html.parser') errors = soup.find(class_='validation-summary-errors') or soup.find( class_='field-validation-error' ) if errors: raise KoboException('Login Failure! ' + errors.text) else: with open('loginpage_error.html', 'w') as loginpagefile: loginpagefile.write(htmlResponse) raise KoboException( "Authenticated user URL can't be found. The page format might have changed!\n\n" "The bad page has been written to file 'loginpage_error.html'. \n" "You should open an issue on GitHub and attach this file for help: https://github.com/subdavis/kobo-book-downloader/issues\n" "Please be sure to remove any personally identifying information from the file." ) url = match.group(1) parsed = urllib.parse.urlparse(url) parsedQueries = urllib.parse.parse_qs(parsed.query) self.user.UserId = parsedQueries["userId"][ 0 ] # We don't call self.Settings.Save here, AuthenticateDevice will do that if it succeeds. userKey = parsedQueries["userKey"][0] self.AuthenticateDevice(userKey)
def LoadInitializationSettings(self) -> None: """ to be called when authentication has been done """ headers = self.__GetHeaderWithAccessToken() hooks = self.__GetReauthenticationHook() debug_data("LoadInitializationSettings") response = self.Session.get( "https://storeapi.kobo.com/v1/initialization", headers=headers, hooks=hooks ) try: response.raise_for_status() jsonResponse = response.json() self.InitializationSettings = jsonResponse["Resources"] except requests.HTTPError as err: print(response.reason, response.text) raise err
def __GetMyBookListPage(self, syncToken: str) -> Tuple[list, str]: url = self.InitializationSettings["library_sync"] headers = self.__GetHeaderWithAccessToken() hooks = self.__GetReauthenticationHook() if len(syncToken) > 0: headers["x-kobo-synctoken"] = syncToken debug_data("GetMyBookListPage") response = self.Session.get(url, headers=headers, hooks=hooks) response.raise_for_status() bookList = response.json() syncToken = "" syncResult = response.headers.get("x-kobo-sync") if syncResult == "continue": syncToken = response.headers.get("x-kobo-synctoken", "") return bookList, syncToken
def AuthenticateDevice(self, userKey: str = "") -> None: if len(self.user.DeviceId) == 0: self.user.DeviceId = str(uuid.uuid4()) self.user.AccessToken = "" self.user.RefreshToken = "" postData = { "AffiliateName": Kobo.Affiliate, "AppVersion": Kobo.ApplicationVersion, "ClientKey": base64.b64encode(Kobo.DefaultPlatformId.encode()).decode(), "DeviceId": self.user.DeviceId, "PlatformId": Kobo.DefaultPlatformId, } if len(userKey) > 0: postData["UserKey"] = userKey response = self.Session.post("https://storeapi.kobo.com/v1/auth/device", json=postData) debug_data("AuthenticateDevice", response.text) response.raise_for_status() jsonResponse = response.json() if jsonResponse["TokenType"] != "Bearer": raise KoboException( "Device authentication returned with an unsupported token type: '%s'" % jsonResponse["TokenType"] ) self.user.AccessToken = jsonResponse["AccessToken"] self.user.RefreshToken = jsonResponse["RefreshToken"] if not self.user.AreAuthenticationSettingsSet(): raise KoboException("Authentication settings are not set after device authentication.") if len(userKey) > 0: self.user.UserKey = jsonResponse["UserKey"] Globals.Settings.Save()