Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 17 additions & 11 deletions kobo-book-downloader/Commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def __GetBook( revisionId: str, outputPath: str ) -> None:
raise KoboException( "The parent directory ('%s') of the output file must exist." % parentPath )

print( "Downloading book to '%s'." % outputPath )
Globals.Kobo.Download( revisionId, Kobo.DisplayProfile, outputPath )
Globals.Kobo.Download( revisionId, None, Kobo.DisplayProfile, outputPath )

@staticmethod
def __GetAllBooks( outputPath: str ) -> None:
Expand All @@ -128,7 +128,11 @@ def __GetAllBooks( outputPath: str ) -> None:
if newEntitlement is None:
continue

bookMetadata = newEntitlement[ "BookMetadata" ]
# Only process e-books, no audio-books etc.
bookMetadata = newEntitlement.get ( "BookMetadata" )
if bookMetadata is None:
continue

fileName = Commands.__MakeFileNameForBook( bookMetadata )
outputFilePath = os.path.join( outputPath, fileName )

Expand All @@ -143,7 +147,8 @@ def __GetAllBooks( outputPath: str ) -> None:
continue

print( "Downloading book to '%s'." % outputFilePath )
Globals.Kobo.Download( bookMetadata[ "RevisionId" ], Kobo.DisplayProfile, outputFilePath )

Globals.Kobo.Download(bookMetadata["RevisionId"], bookMetadata["DownloadUrls"], Kobo.DisplayProfile, outputFilePath)

@staticmethod
def GetBookOrBooks( revisionId: str, outputPath: str, getAll: bool ) -> None:
Expand Down Expand Up @@ -193,15 +198,16 @@ def __GetBookList( listAll: bool ) -> list:
if bookEntitlement.get( "IsLocked" ):
continue

if ( not listAll ) and Commands.__IsBookRead( newEntitlement ):
continue
bookMetadata = newEntitlement["BookMetadata"]
book = [bookMetadata["RevisionId"],
bookMetadata["Title"],
Commands.__GetBookAuthor(bookMetadata),
Commands.__IsBookArchived(newEntitlement)]

if ( not listAll ) and Commands.__IsBookRead( newEntitlement ):
continue

bookMetadata = newEntitlement[ "BookMetadata" ]
book = [ bookMetadata[ "RevisionId" ],
bookMetadata[ "Title" ],
Commands.__GetBookAuthor( bookMetadata ),
Commands.__IsBookArchived( newEntitlement ) ]
rows.append( book )
rows.append( book )

rows = sorted( rows, key = lambda columns: columns[ 1 ].lower() )
return rows
Expand Down
48 changes: 29 additions & 19 deletions kobo-book-downloader/Kobo.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ def GetMyWishList( self ) -> list:

return items

def __GetContentAccessBook( self, productId: str, displayProfile: str ) -> dict:
def __GetContentAccessBook( self, productId: str, bookMetadata: str, displayProfile: str ) -> dict:
url = self.InitializationSettings[ "content_access_book" ].replace( "{ProductId}", productId )
params = { "DisplayProfile": displayProfile }
headers = Kobo.GetHeaderWithAccessToken()
Expand All @@ -288,18 +288,17 @@ def __GetContentKeys( contentAccessBookResponse: dict ) -> Dict[ str, str ]:

@staticmethod
def __GetDownloadInfo( productId: str, contentAccessBookResponse: dict ) -> Tuple[ str, bool ]:
jsonContentUrls = contentAccessBookResponse.get( "ContentUrls" )
jsonContentUrls = contentAccessBookResponse.get("ContentUrls")
if jsonContentUrls is None:
raise KoboException( "Download URL can't be found for product '%s'." % productId )

if len( jsonContentUrls ) == 0:
raise KoboException( "Download URL list is empty for product '%s'. If this is an archived book then it must be unarchived first on the Kobo website (https://www.kobo.com/help/en-US/article/1799/restoring-deleted-books-or-magazines)." % productId )

for jsonContentUrl in jsonContentUrls:
if ( jsonContentUrl[ "DRMType" ] == "KDRM" or jsonContentUrl[ "DRMType" ] == "SignedNoDrm" ) and \
( jsonContentUrl[ "UrlFormat" ] == "EPUB3" or jsonContentUrl[ "UrlFormat" ] == "KEPUB" ):
hasDrm = jsonContentUrl[ "DRMType" ] == "KDRM"
return jsonContentUrl[ "DownloadUrl" ], hasDrm
if jsonContentUrl[ "DrmType" ] == "SocialDrm" and (jsonContentUrl["Format"] == "WATERMARK"):
hasDrm = False
return jsonContentUrl["Url"], hasDrm

message = "Download URL for supported formats can't be found for product '%s'.\n" % productId
message += "Available formats:"
Expand All @@ -317,26 +316,37 @@ def __DownloadToFile( self, url, outputPath: str ) -> None:

# Downloading archived books is not possible, the "content_access_book" API endpoint returns with empty ContentKeys
# and ContentUrls for them.
def Download( self, productId: str, displayProfile: str, outputPath: str ) -> None:
jsonResponse = self.__GetContentAccessBook( productId, displayProfile )
contentKeys = Kobo.__GetContentKeys( jsonResponse )
downloadUrl, hasDrm = Kobo.__GetDownloadInfo( productId, jsonResponse )
def Download(self, productId: str, bookMetaData: str, displayProfile: str, outputPath: str) -> None:

# If the book has SocialDrm, its only watermarked and can be used on all devices you own.
# This download cannot be resolved when the client presents itself as 'Android'
downloadUrl = None
if bookMetaData is not None:
for book in bookMetaData:
if book["DrmType"] == "SocialDrm":
downloadUrl = book["Url"];
hasDrm = False

if downloadUrl is None:
jsonResponse = self.__GetContentAccessBook(productId, bookMetaData, displayProfile)
contentKeys = Kobo.__GetContentKeys(jsonResponse)
downloadUrl, hasDrm = Kobo.__GetDownloadInfo(productId, jsonResponse)

temporaryOutputPath = outputPath + ".downloading"

try:
self.__DownloadToFile( downloadUrl, temporaryOutputPath )
self.__DownloadToFile(downloadUrl, temporaryOutputPath)

if hasDrm:
drmRemover = KoboDrmRemover( Globals.Settings.DeviceId, Globals.Settings.UserId )
drmRemover.RemoveDrm( temporaryOutputPath, outputPath, contentKeys )
os.remove( temporaryOutputPath )
drmRemover = KoboDrmRemover(Globals.Settings.DeviceId, Globals.Settings.UserId)
drmRemover.RemoveDrm(temporaryOutputPath, outputPath, contentKeys)
os.remove(temporaryOutputPath)
else:
os.rename( temporaryOutputPath, outputPath )
os.rename(temporaryOutputPath, outputPath)
except:
if os.path.isfile( temporaryOutputPath ):
os.remove( temporaryOutputPath )
if os.path.isfile( outputPath ):
os.remove( outputPath )
if os.path.isfile(temporaryOutputPath):
os.remove(temporaryOutputPath)
if os.path.isfile(outputPath):
os.remove(outputPath)

raise