예제 #1
0
    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()
예제 #2
0
    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
예제 #3
0
        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
예제 #4
0
def cli(ctx, fmt, config, debug):
    Globals.Settings = Settings(config)
    Globals.Debug = debug
    ctx.obj = {
        'fmt': fmt,
        'debug': debug,
    }
    debug_data(sys.argv)
예제 #5
0
    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
예제 #6
0
 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
예제 #7
0
    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)
예제 #8
0
 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
예제 #9
0
    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
예제 #10
0
    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()