More generic 3.0 changes, to be tested.

pull/1265/head
Apprentice Harper 4 years ago
parent 6920f79a26
commit de50a02af9

@ -1,8 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement
# __init__.py for DeDRM_plugin # __init__.py for DeDRM_plugin
# Copyright © 2008-2020 Apprentice Harper et al. # Copyright © 2008-2020 Apprentice Harper et al.
@ -77,9 +75,9 @@ __docformat__ = 'restructuredtext en'
Decrypt DRMed ebooks. Decrypt DRMed ebooks.
""" """
PLUGIN_NAME = u"DeDRM" PLUGIN_NAME = "DeDRM"
PLUGIN_VERSION_TUPLE = (7, 0, 0) PLUGIN_VERSION_TUPLE = (7, 0, 0)
PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE]) PLUGIN_VERSION = ".".join([str(x)for x in PLUGIN_VERSION_TUPLE])
# Include an html helpfile in the plugin's zipfile with the following name. # Include an html helpfile in the plugin's zipfile with the following name.
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm' RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
@ -109,11 +107,11 @@ class SafeUnbuffered:
if self.encoding == None: if self.encoding == None:
self.encoding = "utf-8" self.encoding = "utf-8"
def write(self, data): def write(self, data):
if isinstance(data,bytes): if isinstance(data,str):
data = data.encode(self.encoding,"replace") data = data.encode(self.encoding,"replace")
try: try:
self.stream.write(data) self.stream.buffer.write(data)
self.stream.flush() self.stream.buffer.flush()
except: except:
# We can do nothing if a write fails # We can do nothing if a write fails
pass pass
@ -122,11 +120,11 @@ class SafeUnbuffered:
class DeDRM(FileTypePlugin): class DeDRM(FileTypePlugin):
name = PLUGIN_NAME name = PLUGIN_NAME
description = u"Removes DRM from Amazon Kindle, Adobe Adept (including Kobo), Barnes & Noble, Mobipocket and eReader ebooks. Credit given to i♥cabbages and The Dark Reverser for the original stand-alone scripts." description = "Removes DRM from Amazon Kindle, Adobe Adept (including Kobo), Barnes & Noble, Mobipocket and eReader ebooks. Credit given to i♥cabbages and The Dark Reverser for the original stand-alone scripts."
supported_platforms = ['linux', 'osx', 'windows'] supported_platforms = ['linux', 'osx', 'windows']
author = u"Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages" author = "Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
version = PLUGIN_VERSION_TUPLE version = PLUGIN_VERSION_TUPLE
minimum_calibre_version = (1, 0, 0) # Compiled python libraries cannot be imported in earlier versions. minimum_calibre_version = (5, 0, 0) # Python 3.
file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','azw8','tpz','kfx','kfx-zip']) file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','azw8','tpz','kfx','kfx-zip'])
on_import = True on_import = True
on_preprocess = True on_preprocess = True
@ -146,27 +144,27 @@ class DeDRM(FileTypePlugin):
Also perform upgrade of preferences once per version Also perform upgrade of preferences once per version
""" """
try: try:
self.pluginsdir = os.path.join(config_dir,u"plugins") self.pluginsdir = os.path.join(config_dir,"plugins")
if not os.path.exists(self.pluginsdir): if not os.path.exists(self.pluginsdir):
os.mkdir(self.pluginsdir) os.mkdir(self.pluginsdir)
self.maindir = os.path.join(self.pluginsdir,u"DeDRM") self.maindir = os.path.join(self.pluginsdir,"DeDRM")
if not os.path.exists(self.maindir): if not os.path.exists(self.maindir):
os.mkdir(self.maindir) os.mkdir(self.maindir)
self.helpdir = os.path.join(self.maindir,u"help") self.helpdir = os.path.join(self.maindir,"help")
if not os.path.exists(self.helpdir): if not os.path.exists(self.helpdir):
os.mkdir(self.helpdir) os.mkdir(self.helpdir)
self.alfdir = os.path.join(self.maindir,u"libraryfiles") self.alfdir = os.path.join(self.maindir,"libraryfiles")
if not os.path.exists(self.alfdir): if not os.path.exists(self.alfdir):
os.mkdir(self.alfdir) os.mkdir(self.alfdir)
# only continue if we've never run this version of the plugin before # only continue if we've never run this version of the plugin before
self.verdir = os.path.join(self.maindir,PLUGIN_VERSION) self.verdir = os.path.join(self.maindir,PLUGIN_VERSION)
if not os.path.exists(self.verdir): if not os.path.exists(self.verdir):
if iswindows: if iswindows:
names = [u"alfcrypto.dll",u"alfcrypto64.dll"] names = ["alfcrypto.dll","alfcrypto64.dll"]
elif isosx: elif isosx:
names = [u"libalfcrypto.dylib"] names = ["libalfcrypto.dylib"]
else: else:
names = [u"libalfcrypto32.so",u"libalfcrypto64.so",u"kindlekey.py",u"adobekey.py",u"subasyncio.py"] names = ["libalfcrypto32.so","libalfcrypto64.so","kindlekey.py","adobekey.py","subasyncio.py"]
lib_dict = self.load_resources(names) lib_dict = self.load_resources(names)
print("{0} v{1}: Copying needed library files from plugin's zip".format(PLUGIN_NAME, PLUGIN_VERSION)) print("{0} v{1}: Copying needed library files from plugin's zip".format(PLUGIN_NAME, PLUGIN_VERSION))
@ -199,13 +197,13 @@ class DeDRM(FileTypePlugin):
# Check original epub archive for zip errors. # Check original epub archive for zip errors.
import calibre_plugins.dedrm.zipfix import calibre_plugins.dedrm.zipfix
inf = self.temporary_file(u".epub") inf = self.temporary_file(".epub")
try: try:
print("{0} v{1}: Verifying zip archive integrity".format(PLUGIN_NAME, PLUGIN_VERSION)) print("{0} v{1}: Verifying zip archive integrity".format(PLUGIN_NAME, PLUGIN_VERSION))
fr = zipfix.fixZip(path_to_ebook, inf.name) fr = zipfix.fixZip(path_to_ebook, inf.name)
fr.fix() fr.fix()
except Exception as e: except Exception as e:
print(u"{0} v{1}: Error \'{2}\' when checking zip archive".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])) print("{0} v{1}: Error \'{2}\' when checking zip archive".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0]))
raise Exception(e) raise Exception(e)
# import the decryption keys # import the decryption keys
@ -222,9 +220,9 @@ class DeDRM(FileTypePlugin):
# Attempt to decrypt epub with each encryption key (generated or provided). # Attempt to decrypt epub with each encryption key (generated or provided).
for keyname, userkey in dedrmprefs['bandnkeys'].items(): for keyname, userkey in dedrmprefs['bandnkeys'].items():
keyname_masked = u"".join((u'X' if (x.isdigit()) else x) for x in keyname) keyname_masked = u"".join(("X" if (x.isdigit()) else x) for x in keyname)
print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked)) print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked))
of = self.temporary_file(u".epub") of = self.temporary_file(".epub")
# Give the user key, ebook and TemporaryPersistent file to the decryption function. # Give the user key, ebook and TemporaryPersistent file to the decryption function.
try: try:
@ -255,10 +253,10 @@ class DeDRM(FileTypePlugin):
defaultkeys = nookkeys() defaultkeys = nookkeys()
else: # linux else: # linux
from wineutils import WineGetKeys from .wineutils import WineGetKeys
scriptpath = os.path.join(self.alfdir,u"ignoblekey.py") scriptpath = os.path.join(self.alfdir,"ignoblekey.py")
defaultkeys = WineGetKeys(scriptpath, u".b64",dedrmprefs['adobewineprefix']) defaultkeys = WineGetKeys(scriptpath, ".b64",dedrmprefs['adobewineprefix'])
except: except:
print("{0} v{1}: Exception when getting default NOOK Study Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)) print("{0} v{1}: Exception when getting default NOOK Study Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
@ -274,7 +272,7 @@ class DeDRM(FileTypePlugin):
for i,userkey in enumerate(newkeys): for i,userkey in enumerate(newkeys):
print("{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)) print("{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
of = self.temporary_file(u".epub") of = self.temporary_file(".epub")
# Give the user key, ebook and TemporaryPersistent file to the decryption function. # Give the user key, ebook and TemporaryPersistent file to the decryption function.
try: try:
@ -300,12 +298,12 @@ class DeDRM(FileTypePlugin):
# Return the modified PersistentTemporary file to calibre. # Return the modified PersistentTemporary file to calibre.
return of.name return of.name
print(u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)) print("{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
except Exception as e: except Exception as e:
pass pass
print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)) print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)) raise DeDRMError("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
# import the Adobe Adept ePub handler # import the Adobe Adept ePub handler
import calibre_plugins.dedrm.ineptepub as ineptepub import calibre_plugins.dedrm.ineptepub as ineptepub
@ -316,8 +314,8 @@ class DeDRM(FileTypePlugin):
# Attempt to decrypt epub with each encryption key (generated or provided). # Attempt to decrypt epub with each encryption key (generated or provided).
for keyname, userkeyhex in dedrmprefs['adeptkeys'].items(): for keyname, userkeyhex in dedrmprefs['adeptkeys'].items():
userkey = codecs.decode(userkeyhex, 'hex') userkey = codecs.decode(userkeyhex, 'hex')
print(u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname)) print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname))
of = self.temporary_file(u".epub") of = self.temporary_file(".epub")
# Give the user key, ebook and TemporaryPersistent file to the decryption function. # Give the user key, ebook and TemporaryPersistent file to the decryption function.
try: try:
@ -352,10 +350,10 @@ class DeDRM(FileTypePlugin):
defaultkeys = adeptkeys() defaultkeys = adeptkeys()
else: # linux else: # linux
from wineutils import WineGetKeys from .wineutils import WineGetKeys
scriptpath = os.path.join(self.alfdir,u"adobekey.py") scriptpath = os.path.join(self.alfdir,"adobekey.py")
defaultkeys = WineGetKeys(scriptpath, u".der",dedrmprefs['adobewineprefix']) defaultkeys = WineGetKeys(scriptpath, ".der",dedrmprefs['adobewineprefix'])
self.default_key = defaultkeys[0] self.default_key = defaultkeys[0]
except: except:
@ -365,14 +363,14 @@ class DeDRM(FileTypePlugin):
newkeys = [] newkeys = []
for keyvalue in defaultkeys: for keyvalue in defaultkeys:
if keyvalue.encode('hex') not in dedrmprefs['adeptkeys'].values(): if codecs.encode(keyvalue, 'hex').decode('ascii') not in dedrmprefs['adeptkeys'].values():
newkeys.append(keyvalue) newkeys.append(keyvalue)
if len(newkeys) > 0: if len(newkeys) > 0:
try: try:
for i,userkey in enumerate(newkeys): for i,userkey in enumerate(newkeys):
print("{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)) print("{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
of = self.temporary_file(u".epub") of = self.temporary_file(".epub")
# Give the user key, ebook and TemporaryPersistent file to the decryption function. # Give the user key, ebook and TemporaryPersistent file to the decryption function.
try: try:
@ -389,7 +387,7 @@ class DeDRM(FileTypePlugin):
# Store the new successful key in the defaults # Store the new successful key in the defaults
print("{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)) print("{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
try: try:
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex')) dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',codecs.encode(keyvalue, 'hex').decode('ascii'))
dedrmprefs.writeprefs() dedrmprefs.writeprefs()
print("{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)) print("{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
except: except:
@ -399,20 +397,20 @@ class DeDRM(FileTypePlugin):
# Return the modified PersistentTemporary file to calibre. # Return the modified PersistentTemporary file to calibre.
return of.name return of.name
print(u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)) print("{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
except Exception as e: except Exception as e:
print(u"{0} v{1}: Unexpected Exception trying a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)) print("{0} v{1}: Unexpected Exception trying a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
traceback.print_exc() traceback.print_exc()
pass pass
# Something went wrong with decryption. # Something went wrong with decryption.
print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)) print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)) raise DeDRMError("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
# Not a Barnes & Noble nor an Adobe Adept # Not a Barnes & Noble nor an Adobe Adept
# Import the fixed epub. # Import the fixed epub.
print("{0} v{1}: “{2}” is neither an Adobe Adept nor a Barnes & Noble encrypted ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))) print("{0} v{1}: “{2}” is neither an Adobe Adept nor a Barnes & Noble encrypted ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)))
raise DeDRMError(u"{0} v{1}: Couldn't decrypt after {2:.1f} seconds. DRM free perhaps?".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)) raise DeDRMError("{0} v{1}: Couldn't decrypt after {2:.1f} seconds. DRM free perhaps?".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
def PDFDecrypt(self,path_to_ebook): def PDFDecrypt(self,path_to_ebook):
import calibre_plugins.dedrm.prefs as prefs import calibre_plugins.dedrm.prefs as prefs
@ -422,9 +420,9 @@ class DeDRM(FileTypePlugin):
# Attempt to decrypt epub with each encryption key (generated or provided). # Attempt to decrypt epub with each encryption key (generated or provided).
print("{0} v{1}: {2} is a PDF ebook".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))) print("{0} v{1}: {2} is a PDF ebook".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)))
for keyname, userkeyhex in dedrmprefs['adeptkeys'].items(): for keyname, userkeyhex in dedrmprefs['adeptkeys'].items():
userkey = codecs.decode(userkeyhex, 'hex') userkey = userkeyhex.decode('hex')
print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname)) print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname))
of = self.temporary_file(u".pdf") of = self.temporary_file(".pdf")
# Give the user key, ebook and TemporaryPersistent file to the decryption function. # Give the user key, ebook and TemporaryPersistent file to the decryption function.
try: try:
@ -455,10 +453,10 @@ class DeDRM(FileTypePlugin):
defaultkeys = adeptkeys() defaultkeys = adeptkeys()
else: # linux else: # linux
from wineutils import WineGetKeys from .wineutils import WineGetKeys
scriptpath = os.path.join(self.alfdir,u"adobekey.py") scriptpath = os.path.join(self.alfdir,"adobekey.py")
defaultkeys = WineGetKeys(scriptpath, u".der",dedrmprefs['adobewineprefix']) defaultkeys = WineGetKeys(scriptpath, ".der",dedrmprefs['adobewineprefix'])
self.default_key = defaultkeys[0] self.default_key = defaultkeys[0]
except: except:
@ -475,7 +473,7 @@ class DeDRM(FileTypePlugin):
try: try:
for i,userkey in enumerate(newkeys): for i,userkey in enumerate(newkeys):
print("{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)) print("{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
of = self.temporary_file(u".pdf") of = self.temporary_file(".pdf")
# Give the user key, ebook and TemporaryPersistent file to the decryption function. # Give the user key, ebook and TemporaryPersistent file to the decryption function.
try: try:
@ -501,13 +499,13 @@ class DeDRM(FileTypePlugin):
# Return the modified PersistentTemporary file to calibre. # Return the modified PersistentTemporary file to calibre.
return of.name return of.name
print(u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)) print("{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
except Exception as e: except Exception as e:
pass pass
# Something went wrong with decryption. # Something went wrong with decryption.
print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)) print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)) raise DeDRMError("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
def KindleMobiDecrypt(self,path_to_ebook): def KindleMobiDecrypt(self,path_to_ebook):
@ -529,7 +527,7 @@ class DeDRM(FileTypePlugin):
serials.extend(android_serials_list) serials.extend(android_serials_list)
#print serials #print serials
androidFiles = [] androidFiles = []
kindleDatabases = dedrmprefs['kindlekeys'].items() kindleDatabases = list(dedrmprefs['kindlekeys'].items())
try: try:
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,kindleDatabases,androidFiles,serials,pids,self.starttime) book = k4mobidedrm.GetDecryptedBook(path_to_ebook,kindleDatabases,androidFiles,serials,pids,self.starttime)
@ -546,10 +544,10 @@ class DeDRM(FileTypePlugin):
defaultkeys = kindlekeys() defaultkeys = kindlekeys()
else: # linux else: # linux
from wineutils import WineGetKeys from .wineutils import WineGetKeys
scriptpath = os.path.join(self.alfdir,u"kindlekey.py") scriptpath = os.path.join(self.alfdir,"kindlekey.py")
defaultkeys = WineGetKeys(scriptpath, u".k4i",dedrmprefs['kindlewineprefix']) defaultkeys = WineGetKeys(scriptpath, ".k4i",dedrmprefs['kindlewineprefix'])
except: except:
print("{0} v{1}: Exception when getting default Kindle Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)) print("{0} v{1}: Exception when getting default Kindle Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
traceback.print_exc() traceback.print_exc()
@ -557,16 +555,16 @@ class DeDRM(FileTypePlugin):
newkeys = {} newkeys = {}
for i,keyvalue in enumerate(defaultkeys): for i,keyvalue in enumerate(defaultkeys):
keyname = u"default_key_{0:d}".format(i+1) keyname = "default_key_{0:d}".format(i+1)
if keyvalue not in dedrmprefs['kindlekeys'].values(): if keyvalue not in dedrmprefs['kindlekeys'].values():
newkeys[keyname] = keyvalue newkeys[keyname] = keyvalue
if len(newkeys) > 0: if len(newkeys) > 0:
print("{0} v{1}: Found {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")) print("{0} v{1}: Found {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), "key" if len(newkeys)==1 else "keys"))
try: try:
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],[],self.starttime) book = k4mobidedrm.GetDecryptedBook(path_to_ebook,list(newkeys.items()),[],[],[],self.starttime)
decoded = True decoded = True
# store the new successful keys in the defaults # store the new successful keys in the defaults
print("{0} v{1}: Saving {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")) print("{0} v{1}: Saving {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), "key" if len(newkeys)==1 else "keys"))
for keyvalue in newkeys.values(): for keyvalue in newkeys.values():
dedrmprefs.addnamedvaluetoprefs('kindlekeys','default_key',keyvalue) dedrmprefs.addnamedvaluetoprefs('kindlekeys','default_key',keyvalue)
dedrmprefs.writeprefs() dedrmprefs.writeprefs()
@ -575,7 +573,7 @@ class DeDRM(FileTypePlugin):
if not decoded: if not decoded:
#if you reached here then no luck raise and exception #if you reached here then no luck raise and exception
print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)) print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)) raise DeDRMError("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
of = self.temporary_file(book.getBookExtension()) of = self.temporary_file(book.getBookExtension())
book.getFile(of.name) book.getFile(of.name)
@ -592,9 +590,9 @@ class DeDRM(FileTypePlugin):
dedrmprefs = prefs.DeDRM_Prefs() dedrmprefs = prefs.DeDRM_Prefs()
# Attempt to decrypt epub with each encryption key (generated or provided). # Attempt to decrypt epub with each encryption key (generated or provided).
for keyname, userkey in dedrmprefs['ereaderkeys'].items(): for keyname, userkey in dedrmprefs['ereaderkeys'].items():
keyname_masked = u"".join((u'X' if (x.isdigit()) else x) for x in keyname) keyname_masked = u"".join(("X" if (x.isdigit()) else x) for x in keyname)
print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked)) print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked))
of = self.temporary_file(u".pmlz") of = self.temporary_file(".pmlz")
# Give the userkey, ebook and TemporaryPersistent file to the decryption function. # Give the userkey, ebook and TemporaryPersistent file to the decryption function.
result = erdr2pml.decryptBook(path_to_ebook, of.name, True, userkey.decode('hex')) result = erdr2pml.decryptBook(path_to_ebook, of.name, True, userkey.decode('hex'))
@ -610,7 +608,7 @@ class DeDRM(FileTypePlugin):
print("{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime)) print("{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime))
print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)) print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)) raise DeDRMError("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
def run(self, path_to_ebook): def run(self, path_to_ebook):

@ -1,30 +1,12 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# adobekey.pyw, version 6.0 # adobekey.pyw, version 6.0
# Copyright © 2009-2010 i♥cabbages # Copyright © 2009-2020 i♥cabbages, Apprentice Harper et al.
# Released under the terms of the GNU General Public Licence, version 3 # Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/> # <http://www.gnu.org/licenses/>
# Modified 20102016 by several people
# Windows users: Before running this program, you must first install Python.
# We recommend ActiveState Python 2.7.X for Windows (x86) from
# http://www.activestate.com/activepython/downloads.
# You must also install PyCrypto from
# http://www.voidspace.org.uk/python/modules.shtml#pycrypto
# (make certain to install the version for Python 2.7).
# Then save this script file as adobekey.pyw and double-click on it to run it.
# It will create a file named adobekey_1.der in in the same directory as the script.
# This is your Adobe Digital Editions user key.
#
# Mac OS X users: Save this script file as adobekey.pyw. You can run this
# program from the command line (python adobekey.pyw) or by double-clicking
# it when it has been associated with PythonLauncher. It will create a file
# named adobekey_1.der in the same directory as the script.
# This is your Adobe Digital Editions user key.
# Revision history: # Revision history:
# 1 - Initial release, for Adobe Digital Editions 1.7 # 1 - Initial release, for Adobe Digital Editions 1.7
# 2 - Better algorithm for finding pLK; improved error handling # 2 - Better algorithm for finding pLK; improved error handling
@ -46,7 +28,7 @@
# 5.8 - Added getkey interface for Windows DeDRM application # 5.8 - Added getkey interface for Windows DeDRM application
# 5.9 - moved unicode_argv call inside main for Windows DeDRM compatibility # 5.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
# 6.0 - Work if TkInter is missing # 6.0 - Work if TkInter is missing
# 7.0 - Python 3 compatible for calibre 5 # 7.0 - Python 3 for calibre 5
""" """
Retrieve Adobe ADEPT user key. Retrieve Adobe ADEPT user key.

@ -1,4 +1,5 @@
#! /usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*-
""" """
Routines for doing AES CBC in one file Routines for doing AES CBC in one file
@ -14,7 +15,7 @@
See the wonderful pure python package cryptopy-1.2.5 See the wonderful pure python package cryptopy-1.2.5
and read its LICENSE.txt for complete license details. and read its LICENSE.txt for complete license details.
Adjusted for Python 3 compatibility, September 2020 Adjusted for Python 3, September 2020
""" """
class CryptoError(Exception): class CryptoError(Exception):

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# crypto library mainly by some_updates # crypto library mainly by some_updates
@ -8,7 +8,6 @@
# pbkdf2.py Copyright © 2009 Daniel Holth <dholth@fastmail.fm> # pbkdf2.py Copyright © 2009 Daniel Holth <dholth@fastmail.fm>
# pbkdf2.py This code may be freely used and modified for any purpose. # pbkdf2.py This code may be freely used and modified for any purpose.
from __future__ import print_function
import sys, os import sys, os
import hmac import hmac
from struct import pack from struct import pack
@ -159,7 +158,7 @@ def _load_libalfcrypto():
topazCryptoDecrypt(ctx, data, out, len(data)) topazCryptoDecrypt(ctx, data, out, len(data))
return out.raw return out.raw
print(u"Using Library AlfCrypto DLL/DYLIB/SO") print("Using Library AlfCrypto DLL/DYLIB/SO")
return (AES_CBC, Pukall_Cipher, Topaz_Cipher) return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
@ -245,7 +244,7 @@ def _load_python_alfcrypto():
cleartext = self.aes.decrypt(iv + data) cleartext = self.aes.decrypt(iv + data)
return cleartext return cleartext
print(u"Using Library AlfCrypto Python") print("Using Library AlfCrypto Python")
return (AES_CBC, Pukall_Cipher, Topaz_Cipher) return (AES_CBC, Pukall_Cipher, Topaz_Cipher)

@ -1,13 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement
from __future__ import print_function
# androidkindlekey.py # androidkindlekey.py
# Copyright © 2013-15 by Thom and Apprentice Harper # Copyright © 2010-20 by Thom, Apprentice et al.
# Some portions Copyright © 2010-15 by some_updates and Apprentice Alf
#
# Revision history: # Revision history:
# 1.0 - AmazonSecureStorage.xml decryption to serial number # 1.0 - AmazonSecureStorage.xml decryption to serial number
@ -18,7 +13,7 @@ from __future__ import print_function
# 1.3 - added in TkInter interface, output to a file # 1.3 - added in TkInter interface, output to a file
# 1.4 - Fix some problems identified by Aldo Bleeker # 1.4 - Fix some problems identified by Aldo Bleeker
# 1.5 - Fix another problem identified by Aldo Bleeker # 1.5 - Fix another problem identified by Aldo Bleeker
# 2.0 - Add Python 3 compatibility # 2.0 - Python 3 compatibility
""" """
Retrieve Kindle for Android Serial Number. Retrieve Kindle for Android Serial Number.
@ -53,10 +48,11 @@ class SafeUnbuffered:
if self.encoding == None: if self.encoding == None:
self.encoding = "utf-8" self.encoding = "utf-8"
def write(self, data): def write(self, data):
if isinstance(data,bytes): if isinstance(data,str):
data = data.encode(self.encoding,"replace") data = data.encode(self.encoding,"replace")
self.stream.write(data) self.stream.buffer.write(data)
self.stream.flush() self.stream.buffer.flush()
def __getattr__(self, attr): def __getattr__(self, attr):
return getattr(self.stream, attr) return getattr(self.stream, attr)
@ -97,7 +93,7 @@ def unicode_argv():
range(start, argc.value)] range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name # if we don't have any arguments at all, just pass back script name
# this should never happen # this should never happen
return [u"kindlekey.py"] return ["kindlekey.py"]
else: else:
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
@ -107,9 +103,9 @@ def unicode_argv():
class DrmException(Exception): class DrmException(Exception):
pass pass
STORAGE = u"backup.ab" STORAGE = "backup.ab"
STORAGE1 = u"AmazonSecureStorage.xml" STORAGE1 = "AmazonSecureStorage.xml"
STORAGE2 = u"map_data_storage.db" STORAGE2 = "map_data_storage.db"
class AndroidObfuscation(object): class AndroidObfuscation(object):
'''AndroidObfuscation '''AndroidObfuscation
@ -326,13 +322,13 @@ def getkey(outfile, inpath):
def usage(progname): def usage(progname):
print(u"Decrypts the serial number(s) of Kindle For Android from Android backup or file") print("Decrypts the serial number(s) of Kindle For Android from Android backup or file")
print(u"Get backup.ab file using adb backup com.amazon.kindle for Android 4.0+.") print("Get backup.ab file using adb backup com.amazon.kindle for Android 4.0+.")
print(u"Otherwise extract AmazonSecureStorage.xml from /data/data/com.amazon.kindle/shared_prefs/AmazonSecureStorage.xml") print("Otherwise extract AmazonSecureStorage.xml from /data/data/com.amazon.kindle/shared_prefs/AmazonSecureStorage.xml")
print(u"Or map_data_storage.db from /data/data/com.amazon.kindle/databases/map_data_storage.db") print("Or map_data_storage.db from /data/data/com.amazon.kindle/databases/map_data_storage.db")
print(u"") print(u"")
print(u"Usage:") print("Usage:")
print(u" {0:s} [-h] [-b <backup.ab>] [<outfile.k4a>]".format(progname)) print(" {0:s} [-h] [-b <backup.ab>] [<outfile.k4a>]".format(progname))
def cli_main(): def cli_main():
@ -340,13 +336,13 @@ def cli_main():
sys.stderr=SafeUnbuffered(sys.stderr) sys.stderr=SafeUnbuffered(sys.stderr)
argv=unicode_argv() argv=unicode_argv()
progname = os.path.basename(argv[0]) progname = os.path.basename(argv[0])
print(u"{0} v{1}\nCopyright © 2010-2015 Thom, some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__)) print("{0} v{1}\nCopyright © 2010-2015 Thom, some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__))
try: try:
opts, args = getopt.getopt(argv[1:], "hb:") opts, args = getopt.getopt(argv[1:], "hb:")
except getopt.GetoptError as err: except getopt.GetoptError as err:
usage(progname) usage(progname)
print(u"\nError in options or arguments: {0}".format(err.args[0])) print("\nError in options or arguments: {0}".format(err.args[0]))
return 2 return 2
inpath = "" inpath = ""
@ -378,13 +374,13 @@ def cli_main():
if not os.path.isfile(inpath): if not os.path.isfile(inpath):
usage(progname) usage(progname)
print(u"\n{0:s} file not found".format(inpath)) print("\n{0:s} file not found".format(inpath))
return 2 return 2
if getkey(outfile, inpath): if getkey(outfile, inpath):
print(u"\nSaved Kindle for Android key to {0}".format(outfile)) print("\nSaved Kindle for Android key to {0}".format(outfile))
else: else:
print(u"\nCould not retrieve Kindle for Android key.") print("\nCould not retrieve Kindle for Android key.")
return 0 return 0
@ -401,32 +397,32 @@ def gui_main():
class DecryptionDialog(Tkinter.Frame): class DecryptionDialog(Tkinter.Frame):
def __init__(self, root): def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5) Tkinter.Frame.__init__(self, root, border=5)
self.status = Tkinter.Label(self, text=u"Select backup.ab file") self.status = Tkinter.Label(self, text="Select backup.ab file")
self.status.pack(fill=Tkconstants.X, expand=1) self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self) body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1) body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2) body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text=u"Backup file").grid(row=0, column=0) Tkinter.Label(body, text="Backup file").grid(row=0, column=0)
self.keypath = Tkinter.Entry(body, width=40) self.keypath = Tkinter.Entry(body, width=40)
self.keypath.grid(row=0, column=1, sticky=sticky) self.keypath.grid(row=0, column=1, sticky=sticky)
self.keypath.insert(2, u"backup.ab") self.keypath.insert(2, "backup.ab")
button = Tkinter.Button(body, text=u"...", command=self.get_keypath) button = Tkinter.Button(body, text="...", command=self.get_keypath)
button.grid(row=0, column=2) button.grid(row=0, column=2)
buttons = Tkinter.Frame(self) buttons = Tkinter.Frame(self)
buttons.pack() buttons.pack()
button2 = Tkinter.Button( button2 = Tkinter.Button(
buttons, text=u"Extract", width=10, command=self.generate) buttons, text="Extract", width=10, command=self.generate)
button2.pack(side=Tkconstants.LEFT) button2.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button3 = Tkinter.Button( button3 = Tkinter.Button(
buttons, text=u"Quit", width=10, command=self.quit) buttons, text="Quit", width=10, command=self.quit)
button3.pack(side=Tkconstants.RIGHT) button3.pack(side=Tkconstants.RIGHT)
def get_keypath(self): def get_keypath(self):
keypath = tkFileDialog.askopenfilename( keypath = tkFileDialog.askopenfilename(
parent=None, title=u"Select backup.ab file", parent=None, title="Select backup.ab file",
defaultextension=u".ab", defaultextension=".ab",
filetypes=[('adb backup com.amazon.kindle', '.ab'), filetypes=[('adb backup com.amazon.kindle', '.ab'),
('All Files', '.*')]) ('All Files', '.*')])
if keypath: if keypath:
@ -437,30 +433,30 @@ def gui_main():
def generate(self): def generate(self):
inpath = self.keypath.get() inpath = self.keypath.get()
self.status['text'] = u"Getting key..." self.status['text'] = "Getting key..."
try: try:
keys = get_serials(inpath) keys = get_serials(inpath)
keycount = 0 keycount = 0
for key in keys: for key in keys:
while True: while True:
keycount += 1 keycount += 1
outfile = os.path.join(progpath,u"kindlekey{0:d}.k4a".format(keycount)) outfile = os.path.join(progpath,"kindlekey{0:d}.k4a".format(keycount))
if not os.path.exists(outfile): if not os.path.exists(outfile):
break break
with open(outfile, 'w') as keyfileout: with open(outfile, 'w') as keyfileout:
keyfileout.write(key) keyfileout.write(key)
success = True success = True
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile)) tkMessageBox.showinfo(progname, "Key successfully retrieved to {0}".format(outfile))
except Exception as e: except Exception as e:
self.status['text'] = u"Error: {0}".format(e.args[0]) self.status['text'] = "Error: {0}".format(e.args[0])
return return
self.status['text'] = u"Select backup.ab file" self.status['text'] = "Select backup.ab file"
argv=unicode_argv() argv=unicode_argv()
progpath, progname = os.path.split(argv[0]) progpath, progname = os.path.split(argv[0])
root = Tkinter.Tk() root = Tkinter.Tk()
root.title(u"Kindle for Android Key Extraction v.{0}".format(__version__)) root.title("Kindle for Android Key Extraction v.{0}".format(__version__))
root.resizable(True, False) root.resizable(True, False)
root.minsize(300, 0) root.minsize(300, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import sys, os import sys, os
@ -37,7 +37,7 @@ def unicode_argv():
xrange(start, argc.value)] xrange(start, argc.value)]
# if we don't have any arguments at all, just pass back script name # if we don't have any arguments at all, just pass back script name
# this should never happen # this should never happen
return [u"DeDRM.py"] return ["DeDRM.py"]
else: else:
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
@ -202,7 +202,7 @@ def AskFolder(
if not pidl: if not pidl:
result = None result = None
else: else:
path = LPCWSTR(u" " * (MAX_PATH+1)) path = LPCWSTR(" " * (MAX_PATH+1))
shell32.SHGetPathFromIDListW(pidl, path) shell32.SHGetPathFromIDListW(pidl, path)
ole32.CoTaskMemFree(pidl) ole32.CoTaskMemFree(pidl)
result = path.value result = path.value

@ -1,30 +1,18 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement
from __future__ import print_function
__license__ = 'GPL v3' __license__ = 'GPL v3'
# Added Python 3 compatibility, September 2020 # Python 3, September 2020
# Standard Python modules. # Standard Python modules.
import os, traceback, json import os, traceback, json
# PyQT4 modules (part of calibre). from PyQt5.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
try:
from PyQt5.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
QGroupBox, QPushButton, QListWidget, QListWidgetItem,
QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl)
except ImportError:
from PyQt4.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
QGroupBox, QPushButton, QListWidget, QListWidgetItem, QGroupBox, QPushButton, QListWidget, QListWidgetItem,
QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl) QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl)
try:
from PyQt5 import Qt as QtGui
except ImportError:
from PyQt4 import QtGui
from PyQt5 import Qt as QtGui
from zipfile import ZipFile from zipfile import ZipFile
# calibre modules and constants. # calibre modules and constants.
@ -85,32 +73,32 @@ class ConfigWidget(QWidget):
button_layout = QVBoxLayout() button_layout = QVBoxLayout()
keys_group_box_layout.addLayout(button_layout) keys_group_box_layout.addLayout(button_layout)
self.bandn_button = QtGui.QPushButton(self) self.bandn_button = QtGui.QPushButton(self)
self.bandn_button.setToolTip(_(u"Click to manage keys for Barnes and Noble ebooks")) self.bandn_button.setToolTip(_("Click to manage keys for Barnes and Noble ebooks"))
self.bandn_button.setText(u"Barnes and Noble ebooks") self.bandn_button.setText("Barnes and Noble ebooks")
self.bandn_button.clicked.connect(self.bandn_keys) self.bandn_button.clicked.connect(self.bandn_keys)
self.kindle_android_button = QtGui.QPushButton(self) self.kindle_android_button = QtGui.QPushButton(self)
self.kindle_android_button.setToolTip(_(u"Click to manage keys for Kindle for Android ebooks")) self.kindle_android_button.setToolTip(_("Click to manage keys for Kindle for Android ebooks"))
self.kindle_android_button.setText(u"Kindle for Android ebooks") self.kindle_android_button.setText("Kindle for Android ebooks")
self.kindle_android_button.clicked.connect(self.kindle_android) self.kindle_android_button.clicked.connect(self.kindle_android)
self.kindle_serial_button = QtGui.QPushButton(self) self.kindle_serial_button = QtGui.QPushButton(self)
self.kindle_serial_button.setToolTip(_(u"Click to manage eInk Kindle serial numbers for Kindle ebooks")) self.kindle_serial_button.setToolTip(_("Click to manage eInk Kindle serial numbers for Kindle ebooks"))
self.kindle_serial_button.setText(u"eInk Kindle ebooks") self.kindle_serial_button.setText("eInk Kindle ebooks")
self.kindle_serial_button.clicked.connect(self.kindle_serials) self.kindle_serial_button.clicked.connect(self.kindle_serials)
self.kindle_key_button = QtGui.QPushButton(self) self.kindle_key_button = QtGui.QPushButton(self)
self.kindle_key_button.setToolTip(_(u"Click to manage keys for Kindle for Mac/PC ebooks")) self.kindle_key_button.setToolTip(_("Click to manage keys for Kindle for Mac/PC ebooks"))
self.kindle_key_button.setText(u"Kindle for Mac/PC ebooks") self.kindle_key_button.setText("Kindle for Mac/PC ebooks")
self.kindle_key_button.clicked.connect(self.kindle_keys) self.kindle_key_button.clicked.connect(self.kindle_keys)
self.adept_button = QtGui.QPushButton(self) self.adept_button = QtGui.QPushButton(self)
self.adept_button.setToolTip(_(u"Click to manage keys for Adobe Digital Editions ebooks")) self.adept_button.setToolTip(_("Click to manage keys for Adobe Digital Editions ebooks"))
self.adept_button.setText(u"Adobe Digital Editions ebooks") self.adept_button.setText("Adobe Digital Editions ebooks")
self.adept_button.clicked.connect(self.adept_keys) self.adept_button.clicked.connect(self.adept_keys)
self.mobi_button = QtGui.QPushButton(self) self.mobi_button = QtGui.QPushButton(self)
self.mobi_button.setToolTip(_(u"Click to manage PIDs for Mobipocket ebooks")) self.mobi_button.setToolTip(_("Click to manage PIDs for Mobipocket ebooks"))
self.mobi_button.setText(u"Mobipocket ebooks") self.mobi_button.setText("Mobipocket ebooks")
self.mobi_button.clicked.connect(self.mobi_keys) self.mobi_button.clicked.connect(self.mobi_keys)
self.ereader_button = QtGui.QPushButton(self) self.ereader_button = QtGui.QPushButton(self)
self.ereader_button.setToolTip(_(u"Click to manage keys for eReader ebooks")) self.ereader_button.setToolTip(_("Click to manage keys for eReader ebooks"))
self.ereader_button.setText(u"eReader ebooks") self.ereader_button.setText("eReader ebooks")
self.ereader_button.clicked.connect(self.ereader_keys) self.ereader_button.clicked.connect(self.ereader_keys)
button_layout.addWidget(self.kindle_serial_button) button_layout.addWidget(self.kindle_serial_button)
button_layout.addWidget(self.kindle_android_button) button_layout.addWidget(self.kindle_android_button)
@ -123,48 +111,48 @@ class ConfigWidget(QWidget):
self.resize(self.sizeHint()) self.resize(self.sizeHint())
def kindle_serials(self): def kindle_serials(self):
d = ManageKeysDialog(self,u"EInk Kindle Serial Number",self.tempdedrmprefs['serials'], AddSerialDialog) d = ManageKeysDialog(self,"EInk Kindle Serial Number",self.tempdedrmprefs['serials'], AddSerialDialog)
d.exec_() d.exec_()
def kindle_android(self): def kindle_android(self):
d = ManageKeysDialog(self,u"Kindle for Android Key",self.tempdedrmprefs['androidkeys'], AddAndroidDialog, 'k4a') d = ManageKeysDialog(self,"Kindle for Android Key",self.tempdedrmprefs['androidkeys'], AddAndroidDialog, 'k4a')
d.exec_() d.exec_()
def kindle_keys(self): def kindle_keys(self):
if isosx or iswindows: if isosx or iswindows:
d = ManageKeysDialog(self,u"Kindle for Mac and PC Key",self.tempdedrmprefs['kindlekeys'], AddKindleDialog, 'k4i') d = ManageKeysDialog(self,"Kindle for Mac and PC Key",self.tempdedrmprefs['kindlekeys'], AddKindleDialog, 'k4i')
else: else:
# linux # linux
d = ManageKeysDialog(self,u"Kindle for Mac and PC Key",self.tempdedrmprefs['kindlekeys'], AddKindleDialog, 'k4i', self.tempdedrmprefs['kindlewineprefix']) d = ManageKeysDialog(self,"Kindle for Mac and PC Key",self.tempdedrmprefs['kindlekeys'], AddKindleDialog, 'k4i', self.tempdedrmprefs['kindlewineprefix'])
d.exec_() d.exec_()
self.tempdedrmprefs['kindlewineprefix'] = d.getwineprefix() self.tempdedrmprefs['kindlewineprefix'] = d.getwineprefix()
def adept_keys(self): def adept_keys(self):
if isosx or iswindows: if isosx or iswindows:
d = ManageKeysDialog(self,u"Adobe Digital Editions Key",self.tempdedrmprefs['adeptkeys'], AddAdeptDialog, 'der') d = ManageKeysDialog(self,"Adobe Digital Editions Key",self.tempdedrmprefs['adeptkeys'], AddAdeptDialog, 'der')
else: else:
# linux # linux
d = ManageKeysDialog(self,u"Adobe Digital Editions Key",self.tempdedrmprefs['adeptkeys'], AddAdeptDialog, 'der', self.tempdedrmprefs['adobewineprefix']) d = ManageKeysDialog(self,"Adobe Digital Editions Key",self.tempdedrmprefs['adeptkeys'], AddAdeptDialog, 'der', self.tempdedrmprefs['adobewineprefix'])
d.exec_() d.exec_()
self.tempdedrmprefs['adobewineprefix'] = d.getwineprefix() self.tempdedrmprefs['adobewineprefix'] = d.getwineprefix()
def mobi_keys(self): def mobi_keys(self):
d = ManageKeysDialog(self,u"Mobipocket PID",self.tempdedrmprefs['pids'], AddPIDDialog) d = ManageKeysDialog(self,"Mobipocket PID",self.tempdedrmprefs['pids'], AddPIDDialog)
d.exec_() d.exec_()
def bandn_keys(self): def bandn_keys(self):
d = ManageKeysDialog(self,u"Barnes and Noble Key",self.tempdedrmprefs['bandnkeys'], AddBandNKeyDialog, 'b64') d = ManageKeysDialog(self,"Barnes and Noble Key",self.tempdedrmprefs['bandnkeys'], AddBandNKeyDialog, 'b64')
d.exec_() d.exec_()
def ereader_keys(self): def ereader_keys(self):
d = ManageKeysDialog(self,u"eReader Key",self.tempdedrmprefs['ereaderkeys'], AddEReaderDialog, 'b63') d = ManageKeysDialog(self,"eReader Key",self.tempdedrmprefs['ereaderkeys'], AddEReaderDialog, 'b63')
d.exec_() d.exec_()
def help_link_activated(self, url): def help_link_activated(self, url):
def get_help_file_resource(): def get_help_file_resource():
# Copy the HTML helpfile to the plugin directory each time the # Copy the HTML helpfile to the plugin directory each time the
# link is clicked in case the helpfile is updated in newer plugins. # link is clicked in case the helpfile is updated in newer plugins.
file_path = os.path.join(config_dir, u"plugins", u"DeDRM", u"help", help_file_name) file_path = os.path.join(config_dir, "plugins", "DeDRM", "help", help_file_name)
with open(file_path,'w') as f: with open(file_path,'w') as f:
f.write(self.load_resource(help_file_name)) f.write(self.load_resource(help_file_name))
return file_path return file_path
@ -201,9 +189,9 @@ class ManageKeysDialog(QDialog):
self.create_key = create_key self.create_key = create_key
self.keyfile_ext = keyfile_ext self.keyfile_ext = keyfile_ext
self.import_key = (keyfile_ext != u"") self.import_key = (keyfile_ext != u"")
self.binary_file = (keyfile_ext == u"der") self.binary_file = (keyfile_ext == "der")
self.json_file = (keyfile_ext == u"k4i") self.json_file = (keyfile_ext == "k4i")
self.android_file = (keyfile_ext == u"k4a") self.android_file = (keyfile_ext == "k4a")
self.wineprefix = wineprefix self.wineprefix = wineprefix
self.setWindowTitle("{0} {1}: Manage {2}s".format(PLUGIN_NAME, PLUGIN_VERSION, self.key_type_name)) self.setWindowTitle("{0} {1}: Manage {2}s".format(PLUGIN_NAME, PLUGIN_VERSION, self.key_type_name))
@ -221,13 +209,13 @@ class ManageKeysDialog(QDialog):
help_label.linkActivated.connect(self.help_link_activated) help_label.linkActivated.connect(self.help_link_activated)
help_layout.addWidget(help_label) help_layout.addWidget(help_label)
keys_group_box = QGroupBox(_(u"{0}s".format(self.key_type_name)), self) keys_group_box = QGroupBox(_("{0}s".format(self.key_type_name)), self)
layout.addWidget(keys_group_box) layout.addWidget(keys_group_box)
keys_group_box_layout = QHBoxLayout() keys_group_box_layout = QHBoxLayout()
keys_group_box.setLayout(keys_group_box_layout) keys_group_box.setLayout(keys_group_box_layout)
self.listy = QListWidget(self) self.listy = QListWidget(self)
self.listy.setToolTip(u"{0}s that will be used to decrypt ebooks".format(self.key_type_name)) self.listy.setToolTip("{0}s that will be used to decrypt ebooks".format(self.key_type_name))
self.listy.setSelectionMode(QAbstractItemView.SingleSelection) self.listy.setSelectionMode(QAbstractItemView.SingleSelection)
self.populate_list() self.populate_list()
keys_group_box_layout.addWidget(self.listy) keys_group_box_layout.addWidget(self.listy)
@ -236,25 +224,25 @@ class ManageKeysDialog(QDialog):
keys_group_box_layout.addLayout(button_layout) keys_group_box_layout.addLayout(button_layout)
self._add_key_button = QtGui.QToolButton(self) self._add_key_button = QtGui.QToolButton(self)
self._add_key_button.setIcon(QIcon(I('plus.png'))) self._add_key_button.setIcon(QIcon(I('plus.png')))
self._add_key_button.setToolTip(u"Create new {0}".format(self.key_type_name)) self._add_key_button.setToolTip("Create new {0}".format(self.key_type_name))
self._add_key_button.clicked.connect(self.add_key) self._add_key_button.clicked.connect(self.add_key)
button_layout.addWidget(self._add_key_button) button_layout.addWidget(self._add_key_button)
self._delete_key_button = QtGui.QToolButton(self) self._delete_key_button = QtGui.QToolButton(self)
self._delete_key_button.setToolTip(_(u"Delete highlighted key")) self._delete_key_button.setToolTip(_("Delete highlighted key"))
self._delete_key_button.setIcon(QIcon(I('list_remove.png'))) self._delete_key_button.setIcon(QIcon(I('list_remove.png')))
self._delete_key_button.clicked.connect(self.delete_key) self._delete_key_button.clicked.connect(self.delete_key)
button_layout.addWidget(self._delete_key_button) button_layout.addWidget(self._delete_key_button)
if type(self.plugin_keys) == dict and self.import_key: if type(self.plugin_keys) == dict and self.import_key:
self._rename_key_button = QtGui.QToolButton(self) self._rename_key_button = QtGui.QToolButton(self)
self._rename_key_button.setToolTip(_(u"Rename highlighted key")) self._rename_key_button.setToolTip(_("Rename highlighted key"))
self._rename_key_button.setIcon(QIcon(I('edit-select-all.png'))) self._rename_key_button.setIcon(QIcon(I('edit-select-all.png')))
self._rename_key_button.clicked.connect(self.rename_key) self._rename_key_button.clicked.connect(self.rename_key)
button_layout.addWidget(self._rename_key_button) button_layout.addWidget(self._rename_key_button)
self.export_key_button = QtGui.QToolButton(self) self.export_key_button = QtGui.QToolButton(self)
self.export_key_button.setToolTip(u"Save highlighted key to a .{0} file".format(self.keyfile_ext)) self.export_key_button.setToolTip("Save highlighted key to a .{0} file".format(self.keyfile_ext))
self.export_key_button.setIcon(QIcon(I('save.png'))) self.export_key_button.setIcon(QIcon(I('save.png')))
self.export_key_button.clicked.connect(self.export_key) self.export_key_button.clicked.connect(self.export_key)
button_layout.addWidget(self.export_key_button) button_layout.addWidget(self.export_key_button)
@ -266,7 +254,7 @@ class ManageKeysDialog(QDialog):
wineprefix_layout = QHBoxLayout() wineprefix_layout = QHBoxLayout()
layout.addLayout(wineprefix_layout) layout.addLayout(wineprefix_layout)
wineprefix_layout.setAlignment(Qt.AlignCenter) wineprefix_layout.setAlignment(Qt.AlignCenter)
self.wp_label = QLabel(u"WINEPREFIX:") self.wp_label = QLabel("WINEPREFIX:")
wineprefix_layout.addWidget(self.wp_label) wineprefix_layout.addWidget(self.wp_label)
self.wp_lineedit = QLineEdit(self) self.wp_lineedit = QLineEdit(self)
wineprefix_layout.addWidget(self.wp_lineedit) wineprefix_layout.addWidget(self.wp_lineedit)
@ -278,8 +266,8 @@ class ManageKeysDialog(QDialog):
layout.addLayout(migrate_layout) layout.addLayout(migrate_layout)
if self.import_key: if self.import_key:
migrate_layout.setAlignment(Qt.AlignJustify) migrate_layout.setAlignment(Qt.AlignJustify)
self.migrate_btn = QPushButton(u"Import Existing Keyfiles", self) self.migrate_btn = QPushButton("Import Existing Keyfiles", self)
self.migrate_btn.setToolTip(u"Import *.{0} files (created using other tools).".format(self.keyfile_ext)) self.migrate_btn.setToolTip("Import *.{0} files (created using other tools).".format(self.keyfile_ext))
self.migrate_btn.clicked.connect(self.migrate_wrapper) self.migrate_btn.clicked.connect(self.migrate_wrapper)
migrate_layout.addWidget(self.migrate_btn) migrate_layout.addWidget(self.migrate_btn)
migrate_layout.addStretch() migrate_layout.addStretch()
@ -314,13 +302,13 @@ class ManageKeysDialog(QDialog):
if new_key_value in self.plugin_keys.values(): if new_key_value in self.plugin_keys.values():
old_key_name = [name for name, value in self.plugin_keys.iteritems() if value == new_key_value][0] old_key_name = [name for name, value in self.plugin_keys.iteritems() if value == new_key_value][0]
info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name), info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
u"The new {1} is the same as the existing {1} named <strong>{0}</strong> and has not been added.".format(old_key_name,self.key_type_name), show=True) "The new {1} is the same as the existing {1} named <strong>{0}</strong> and has not been added.".format(old_key_name,self.key_type_name), show=True)
return return
self.plugin_keys[d.key_name] = new_key_value self.plugin_keys[d.key_name] = new_key_value
else: else:
if new_key_value in self.plugin_keys: if new_key_value in self.plugin_keys:
info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name), info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
u"This {0} is already in the list of {0}s has not been added.".format(self.key_type_name), show=True) "This {0} is already in the list of {0}s has not been added.".format(self.key_type_name), show=True)
return return
self.plugin_keys.append(d.key_value) self.plugin_keys.append(d.key_value)
@ -329,7 +317,7 @@ class ManageKeysDialog(QDialog):
def rename_key(self): def rename_key(self):
if not self.listy.currentItem(): if not self.listy.currentItem():
errmsg = u"No {0} selected to rename. Highlight a keyfile first.".format(self.key_type_name) errmsg = "No {0} selected to rename. Highlight a keyfile first.".format(self.key_type_name)
r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
_(errmsg), show=True, show_copy_button=False) _(errmsg), show=True, show_copy_button=False)
return return
@ -341,7 +329,7 @@ class ManageKeysDialog(QDialog):
# rename cancelled or moot. # rename cancelled or moot.
return return
keyname = self.listy.currentItem().text() keyname = self.listy.currentItem().text()
if not question_dialog(self, "{0} {1}: Confirm Rename".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to rename the {2} named <strong>{0}</strong> to <strong>{1}</strong>?".format(keyname,d.key_name,self.key_type_name), show_copy_button=False, default_yes=False): if not question_dialog(self, "{0} {1}: Confirm Rename".format(PLUGIN_NAME, PLUGIN_VERSION), "Do you really want to rename the {2} named <strong>{0}</strong> to <strong>{1}</strong>?".format(keyname,d.key_name,self.key_type_name), show_copy_button=False, default_yes=False):
return return
self.plugin_keys[d.key_name] = self.plugin_keys[keyname] self.plugin_keys[d.key_name] = self.plugin_keys[keyname]
del self.plugin_keys[keyname] del self.plugin_keys[keyname]
@ -353,7 +341,7 @@ class ManageKeysDialog(QDialog):
if not self.listy.currentItem(): if not self.listy.currentItem():
return return
keyname = self.listy.currentItem().text() keyname = self.listy.currentItem().text()
if not question_dialog(self, "{0} {1}: Confirm Delete".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to delete the {1} <strong>{0}</strong>?".format(keyname, self.key_type_name), show_copy_button=False, default_yes=False): if not question_dialog(self, "{0} {1}: Confirm Delete".format(PLUGIN_NAME, PLUGIN_VERSION), "Do you really want to delete the {1} <strong>{0}</strong>?".format(keyname, self.key_type_name), show_copy_button=False, default_yes=False):
return return
if type(self.plugin_keys) == dict: if type(self.plugin_keys) == dict:
del self.plugin_keys[keyname] del self.plugin_keys[keyname]
@ -367,8 +355,8 @@ class ManageKeysDialog(QDialog):
def get_help_file_resource(): def get_help_file_resource():
# Copy the HTML helpfile to the plugin directory each time the # Copy the HTML helpfile to the plugin directory each time the
# link is clicked in case the helpfile is updated in newer plugins. # link is clicked in case the helpfile is updated in newer plugins.
help_file_name = u"{0}_{1}_Help.htm".format(PLUGIN_NAME, self.key_type_name) help_file_name = "{0}_{1}_Help.htm".format(PLUGIN_NAME, self.key_type_name)
file_path = os.path.join(config_dir, u"plugins", u"DeDRM", u"help", help_file_name) file_path = os.path.join(config_dir, "plugins", "DeDRM", "help", help_file_name)
with open(file_path,'w') as f: with open(file_path,'w') as f:
f.write(self.parent.load_resource(help_file_name)) f.write(self.parent.load_resource(help_file_name))
return file_path return file_path
@ -376,9 +364,9 @@ class ManageKeysDialog(QDialog):
open_url(QUrl(url)) open_url(QUrl(url))
def migrate_files(self): def migrate_files(self):
unique_dlg_name = PLUGIN_NAME + u"import {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory unique_dlg_name = PLUGIN_NAME + "import {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory
caption = u"Select {0} files to import".format(self.key_type_name) caption = "Select {0} files to import".format(self.key_type_name)
filters = [(u"{0} files".format(self.key_type_name), [self.keyfile_ext])] filters = [("{0} files".format(self.key_type_name), [self.keyfile_ext])]
files = choose_files(self, unique_dlg_name, caption, filters, all_files=False) files = choose_files(self, unique_dlg_name, caption, filters, all_files=False)
counter = 0 counter = 0
skipped = 0 skipped = 0
@ -400,7 +388,7 @@ class ManageKeysDialog(QDialog):
for key in self.plugin_keys.keys(): for key in self.plugin_keys.keys():
if uStrCmp(new_key_name, key, True): if uStrCmp(new_key_name, key, True):
skipped += 1 skipped += 1
msg = u"A key with the name <strong>{0}</strong> already exists!\nSkipping key file <strong>{1}</strong>.\nRename the existing key and import again".format(new_key_name,filename) msg = "A key with the name <strong>{0}</strong> already exists!\nSkipping key file <strong>{1}</strong>.\nRename the existing key and import again".format(new_key_name,filename)
inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
_(msg), show_copy_button=False, show=True) _(msg), show_copy_button=False, show=True)
match = True match = True
@ -410,7 +398,7 @@ class ManageKeysDialog(QDialog):
old_key_name = [name for name, value in self.plugin_keys.iteritems() if value == new_key_value][0] old_key_name = [name for name, value in self.plugin_keys.iteritems() if value == new_key_value][0]
skipped += 1 skipped += 1
info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
u"The key in file {0} is the same as the existing key <strong>{1}</strong> and has been skipped.".format(filename,old_key_name), show_copy_button=False, show=True) "The key in file {0} is the same as the existing key <strong>{1}</strong> and has been skipped.".format(filename,old_key_name), show_copy_button=False, show=True)
else: else:
counter += 1 counter += 1
self.plugin_keys[new_key_name] = new_key_value self.plugin_keys[new_key_name] = new_key_value
@ -418,9 +406,9 @@ class ManageKeysDialog(QDialog):
msg = u"" msg = u""
if counter+skipped > 1: if counter+skipped > 1:
if counter > 0: if counter > 0:
msg += u"Imported <strong>{0:d}</strong> key {1}. ".format(counter, u"file" if counter == 1 else u"files") msg += "Imported <strong>{0:d}</strong> key {1}. ".format(counter, "file" if counter == 1 else "files")
if skipped > 0: if skipped > 0:
msg += u"Skipped <strong>{0:d}</strong> key {1}.".format(skipped, u"file" if counter == 1 else u"files") msg += "Skipped <strong>{0:d}</strong> key {1}.".format(skipped, "file" if counter == 1 else "files")
inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
_(msg), show_copy_button=False, show=True) _(msg), show_copy_button=False, show=True)
return counter > 0 return counter > 0
@ -432,15 +420,15 @@ class ManageKeysDialog(QDialog):
def export_key(self): def export_key(self):
if not self.listy.currentItem(): if not self.listy.currentItem():
errmsg = u"No keyfile selected to export. Highlight a keyfile first." errmsg = "No keyfile selected to export. Highlight a keyfile first."
r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
_(errmsg), show=True, show_copy_button=False) _(errmsg), show=True, show_copy_button=False)
return return
keyname = self.listy.currentItem().text() keyname = self.listy.currentItem().text()
unique_dlg_name = PLUGIN_NAME + u"export {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory unique_dlg_name = PLUGIN_NAME + "export {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory
caption = u"Save {0} File as...".format(self.key_type_name) caption = "Save {0} File as...".format(self.key_type_name)
filters = [(u"{0} Files".format(self.key_type_name), [u"{0}".format(self.keyfile_ext)])] filters = [("{0} Files".format(self.key_type_name), ["{0}".format(self.keyfile_ext)])]
defaultname = u"{0}.{1}".format(keyname, self.keyfile_ext) defaultname = "{0}.{1}".format(keyname, self.keyfile_ext)
filename = choose_save_file(self, unique_dlg_name, caption, filters, all_files=False, initial_filename=defaultname) filename = choose_save_file(self, unique_dlg_name, caption, filters, all_files=False, initial_filename=defaultname)
if filename: if filename:
with file(filename, 'wb') as fname: with file(filename, 'wb') as fname:
@ -474,7 +462,7 @@ class RenameKeyDialog(QDialog):
data_group_box_layout.addWidget(QLabel('New Key Name:', self)) data_group_box_layout.addWidget(QLabel('New Key Name:', self))
self.key_ledit = QLineEdit(self.parent.listy.currentItem().text(), self) self.key_ledit = QLineEdit(self.parent.listy.currentItem().text(), self)
self.key_ledit.setToolTip(u"Enter a new name for this existing {0}.".format(parent.key_type_name)) self.key_ledit.setToolTip("Enter a new name for this existing {0}.".format(parent.key_type_name))
data_group_box_layout.addWidget(self.key_ledit) data_group_box_layout.addWidget(self.key_ledit)
layout.addSpacing(20) layout.addSpacing(20)
@ -488,11 +476,11 @@ class RenameKeyDialog(QDialog):
def accept(self): def accept(self):
if not self.key_ledit.text() or self.key_ledit.text().isspace(): if not self.key_ledit.text() or self.key_ledit.text().isspace():
errmsg = u"Key name field cannot be empty!" errmsg = "Key name field cannot be empty!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
_(errmsg), show=True, show_copy_button=False) _(errmsg), show=True, show_copy_button=False)
if len(self.key_ledit.text()) < 4: if len(self.key_ledit.text()) < 4:
errmsg = u"Key name must be at <i>least</i> 4 characters long!" errmsg = "Key name must be at <i>least</i> 4 characters long!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
_(errmsg), show=True, show_copy_button=False) _(errmsg), show=True, show_copy_button=False)
if uStrCmp(self.key_ledit.text(), self.parent.listy.currentItem().text()): if uStrCmp(self.key_ledit.text(), self.parent.listy.currentItem().text()):
@ -501,7 +489,7 @@ class RenameKeyDialog(QDialog):
for k in self.parent.plugin_keys.keys(): for k in self.parent.plugin_keys.keys():
if (uStrCmp(self.key_ledit.text(), k, True) and if (uStrCmp(self.key_ledit.text(), k, True) and
not uStrCmp(k, self.parent.listy.currentItem().text(), True)): not uStrCmp(k, self.parent.listy.currentItem().text(), True)):
errmsg = u"The key name <strong>{0}</strong> is already being used.".format(self.key_ledit.text()) errmsg = "The key name <strong>{0}</strong> is already being used.".format(self.key_ledit.text())
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
_(errmsg), show=True, show_copy_button=False) _(errmsg), show=True, show_copy_button=False)
QDialog.accept(self) QDialog.accept(self)
@ -521,7 +509,7 @@ class AddBandNKeyDialog(QDialog):
def __init__(self, parent=None,): def __init__(self, parent=None,):
QDialog.__init__(self, parent) QDialog.__init__(self, parent)
self.parent = parent self.parent = parent
self.setWindowTitle(u"{0} {1}: Create New Barnes & Noble Key".format(PLUGIN_NAME, PLUGIN_VERSION)) self.setWindowTitle("{0} {1}: Create New Barnes & Noble Key".format(PLUGIN_NAME, PLUGIN_VERSION))
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
self.setLayout(layout) self.setLayout(layout)
@ -532,37 +520,37 @@ class AddBandNKeyDialog(QDialog):
key_group = QHBoxLayout() key_group = QHBoxLayout()
data_group_box_layout.addLayout(key_group) data_group_box_layout.addLayout(key_group)
key_group.addWidget(QLabel(u"Unique Key Name:", self)) key_group.addWidget(QLabel("Unique Key Name:", self))
self.key_ledit = QLineEdit("", self) self.key_ledit = QLineEdit("", self)
self.key_ledit.setToolTip(_(u"<p>Enter an identifying name for this new key.</p>" + self.key_ledit.setToolTip(_("<p>Enter an identifying name for this new key.</p>" +
u"<p>It should be something that will help you remember " + "<p>It should be something that will help you remember " +
u"what personal information was used to create it.")) "what personal information was used to create it."))
key_group.addWidget(self.key_ledit) key_group.addWidget(self.key_ledit)
name_group = QHBoxLayout() name_group = QHBoxLayout()
data_group_box_layout.addLayout(name_group) data_group_box_layout.addLayout(name_group)
name_group.addWidget(QLabel(u"B&N/nook account email address:", self)) name_group.addWidget(QLabel("B&N/nook account email address:", self))
self.name_ledit = QLineEdit(u"", self) self.name_ledit = QLineEdit(u"", self)
self.name_ledit.setToolTip(_(u"<p>Enter your email address as it appears in your B&N " + self.name_ledit.setToolTip(_("<p>Enter your email address as it appears in your B&N " +
u"account.</p>" + "account.</p>" +
u"<p>It will only be used to generate this " + "<p>It will only be used to generate this " +
u"key and won\'t be stored anywhere " + "key and won\'t be stored anywhere " +
u"in calibre or on your computer.</p>" + "in calibre or on your computer.</p>" +
u"<p>eg: apprenticeharper@gmail.com</p>")) "<p>eg: apprenticeharper@gmail.com</p>"))
name_group.addWidget(self.name_ledit) name_group.addWidget(self.name_ledit)
name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self) name_disclaimer_label = QLabel(_("(Will not be saved in configuration data)"), self)
name_disclaimer_label.setAlignment(Qt.AlignHCenter) name_disclaimer_label.setAlignment(Qt.AlignHCenter)
data_group_box_layout.addWidget(name_disclaimer_label) data_group_box_layout.addWidget(name_disclaimer_label)
ccn_group = QHBoxLayout() ccn_group = QHBoxLayout()
data_group_box_layout.addLayout(ccn_group) data_group_box_layout.addLayout(ccn_group)
ccn_group.addWidget(QLabel(u"B&N/nook account password:", self)) ccn_group.addWidget(QLabel("B&N/nook account password:", self))
self.cc_ledit = QLineEdit(u"", self) self.cc_ledit = QLineEdit(u"", self)
self.cc_ledit.setToolTip(_(u"<p>Enter the password " + self.cc_ledit.setToolTip(_("<p>Enter the password " +
u"for your B&N account.</p>" + "for your B&N account.</p>" +
u"<p>The password will only be used to generate this " + "<p>The password will only be used to generate this " +
u"key and won\'t be stored anywhere in " + "key and won\'t be stored anywhere in " +
u"calibre or on your computer.")) "calibre or on your computer."))
ccn_group.addWidget(self.cc_ledit) ccn_group.addWidget(self.cc_ledit)
ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self) ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self)
ccn_disclaimer_label.setAlignment(Qt.AlignHCenter) ccn_disclaimer_label.setAlignment(Qt.AlignHCenter)
@ -571,13 +559,13 @@ class AddBandNKeyDialog(QDialog):
key_group = QHBoxLayout() key_group = QHBoxLayout()
data_group_box_layout.addLayout(key_group) data_group_box_layout.addLayout(key_group)
key_group.addWidget(QLabel(u"Retrieved key:", self)) key_group.addWidget(QLabel("Retrieved key:", self))
self.key_display = QLabel(u"", self) self.key_display = QLabel(u"", self)
self.key_display.setToolTip(_(u"Click the Retrieve Key button to fetch your B&N encryption key from the B&N servers")) self.key_display.setToolTip(_("Click the Retrieve Key button to fetch your B&N encryption key from the B&N servers"))
key_group.addWidget(self.key_display) key_group.addWidget(self.key_display)
self.retrieve_button = QtGui.QPushButton(self) self.retrieve_button = QtGui.QPushButton(self)
self.retrieve_button.setToolTip(_(u"Click to retrieve your B&N encryption key from the B&N servers")) self.retrieve_button.setToolTip(_("Click to retrieve your B&N encryption key from the B&N servers"))
self.retrieve_button.setText(u"Retrieve Key") self.retrieve_button.setText("Retrieve Key")
self.retrieve_button.clicked.connect(self.retrieve_key) self.retrieve_button.clicked.connect(self.retrieve_key)
key_group.addWidget(self.retrieve_button) key_group.addWidget(self.retrieve_button)
layout.addSpacing(10) layout.addSpacing(10)
@ -609,17 +597,17 @@ class AddBandNKeyDialog(QDialog):
from calibre_plugins.dedrm.ignoblekeyfetch import fetch_key as fetch_bandn_key from calibre_plugins.dedrm.ignoblekeyfetch import fetch_key as fetch_bandn_key
fetched_key = fetch_bandn_key(self.user_name,self.cc_number) fetched_key = fetch_bandn_key(self.user_name,self.cc_number)
if fetched_key == "": if fetched_key == "":
errmsg = u"Could not retrieve key. Check username, password and intenet connectivity and try again." errmsg = "Could not retrieve key. Check username, password and intenet connectivity and try again."
error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
else: else:
self.key_display.setText(fetched_key) self.key_display.setText(fetched_key)
def accept(self): def accept(self):
if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace(): if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace():
errmsg = u"All fields are required!" errmsg = "All fields are required!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
if len(self.key_name) < 4: if len(self.key_name) < 4:
errmsg = u"Key name must be at <i>least</i> 4 characters long!" errmsg = "Key name must be at <i>least</i> 4 characters long!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
if len(self.key_value) == 0: if len(self.key_value) == 0:
self.retrieve_key() self.retrieve_key()
@ -631,7 +619,7 @@ class AddEReaderDialog(QDialog):
def __init__(self, parent=None,): def __init__(self, parent=None,):
QDialog.__init__(self, parent) QDialog.__init__(self, parent)
self.parent = parent self.parent = parent
self.setWindowTitle(u"{0} {1}: Create New eReader Key".format(PLUGIN_NAME, PLUGIN_VERSION)) self.setWindowTitle("{0} {1}: Create New eReader Key".format(PLUGIN_NAME, PLUGIN_VERSION))
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
self.setLayout(layout) self.setLayout(layout)
@ -642,26 +630,26 @@ class AddEReaderDialog(QDialog):
key_group = QHBoxLayout() key_group = QHBoxLayout()
data_group_box_layout.addLayout(key_group) data_group_box_layout.addLayout(key_group)
key_group.addWidget(QLabel(u"Unique Key Name:", self)) key_group.addWidget(QLabel("Unique Key Name:", self))
self.key_ledit = QLineEdit("", self) self.key_ledit = QLineEdit("", self)
self.key_ledit.setToolTip(u"<p>Enter an identifying name for this new key.\nIt should be something that will help you remember what personal information was used to create it.") self.key_ledit.setToolTip("<p>Enter an identifying name for this new key.\nIt should be something that will help you remember what personal information was used to create it.")
key_group.addWidget(self.key_ledit) key_group.addWidget(self.key_ledit)
name_group = QHBoxLayout() name_group = QHBoxLayout()
data_group_box_layout.addLayout(name_group) data_group_box_layout.addLayout(name_group)
name_group.addWidget(QLabel(u"Your Name:", self)) name_group.addWidget(QLabel("Your Name:", self))
self.name_ledit = QLineEdit(u"", self) self.name_ledit = QLineEdit(u"", self)
self.name_ledit.setToolTip(u"Enter the name for this eReader key, usually the name on your credit card.\nIt will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.\n(ex: Mr Jonathan Q Smith)") self.name_ledit.setToolTip("Enter the name for this eReader key, usually the name on your credit card.\nIt will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.\n(ex: Mr Jonathan Q Smith)")
name_group.addWidget(self.name_ledit) name_group.addWidget(self.name_ledit)
name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self) name_disclaimer_label = QLabel(_("(Will not be saved in configuration data)"), self)
name_disclaimer_label.setAlignment(Qt.AlignHCenter) name_disclaimer_label.setAlignment(Qt.AlignHCenter)
data_group_box_layout.addWidget(name_disclaimer_label) data_group_box_layout.addWidget(name_disclaimer_label)
ccn_group = QHBoxLayout() ccn_group = QHBoxLayout()
data_group_box_layout.addLayout(ccn_group) data_group_box_layout.addLayout(ccn_group)
ccn_group.addWidget(QLabel(u"Credit Card#:", self)) ccn_group.addWidget(QLabel("Credit Card#:", self))
self.cc_ledit = QLineEdit(u"", self) self.cc_ledit = QLineEdit(u"", self)
self.cc_ledit.setToolTip(u"<p>Enter the last 8 digits of credit card number for this eReader key.\nThey will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.") self.cc_ledit.setToolTip("<p>Enter the last 8 digits of credit card number for this eReader key.\nThey will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.")
ccn_group.addWidget(self.cc_ledit) ccn_group.addWidget(self.cc_ledit)
ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self) ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self)
ccn_disclaimer_label.setAlignment(Qt.AlignHCenter) ccn_disclaimer_label.setAlignment(Qt.AlignHCenter)
@ -695,13 +683,13 @@ class AddEReaderDialog(QDialog):
def accept(self): def accept(self):
if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace(): if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace():
errmsg = u"All fields are required!" errmsg = "All fields are required!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
if not self.cc_number.isdigit(): if not self.cc_number.isdigit():
errmsg = u"Numbers only in the credit card number field!" errmsg = "Numbers only in the credit card number field!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
if len(self.key_name) < 4: if len(self.key_name) < 4:
errmsg = u"Key name must be at <i>least</i> 4 characters long!" errmsg = "Key name must be at <i>least</i> 4 characters long!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
QDialog.accept(self) QDialog.accept(self)
@ -710,7 +698,7 @@ class AddAdeptDialog(QDialog):
def __init__(self, parent=None,): def __init__(self, parent=None,):
QDialog.__init__(self, parent) QDialog.__init__(self, parent)
self.parent = parent self.parent = parent
self.setWindowTitle(u"{0} {1}: Getting Default Adobe Digital Editions Key".format(PLUGIN_NAME, PLUGIN_VERSION)) self.setWindowTitle("{0} {1}: Getting Default Adobe Digital Editions Key".format(PLUGIN_NAME, PLUGIN_VERSION))
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
self.setLayout(layout) self.setLayout(layout)
@ -722,8 +710,8 @@ class AddAdeptDialog(QDialog):
else: # linux else: # linux
from wineutils import WineGetKeys from wineutils import WineGetKeys
scriptpath = os.path.join(parent.parent.alfdir,u"adobekey.py") scriptpath = os.path.join(parent.parent.alfdir,"adobekey.py")
defaultkeys = WineGetKeys(scriptpath, u".der",parent.getwineprefix()) defaultkeys = WineGetKeys(scriptpath, ".der",parent.getwineprefix())
self.default_key = defaultkeys[0] self.default_key = defaultkeys[0]
except: except:
@ -740,14 +728,14 @@ class AddAdeptDialog(QDialog):
key_group = QHBoxLayout() key_group = QHBoxLayout()
data_group_box_layout.addLayout(key_group) data_group_box_layout.addLayout(key_group)
key_group.addWidget(QLabel(u"Unique Key Name:", self)) key_group.addWidget(QLabel("Unique Key Name:", self))
self.key_ledit = QLineEdit(u"default_key", self) self.key_ledit = QLineEdit("default_key", self)
self.key_ledit.setToolTip(u"<p>Enter an identifying name for the current default Adobe Digital Editions key.") self.key_ledit.setToolTip("<p>Enter an identifying name for the current default Adobe Digital Editions key.")
key_group.addWidget(self.key_ledit) key_group.addWidget(self.key_ledit)
self.button_box.accepted.connect(self.accept) self.button_box.accepted.connect(self.accept)
else: else:
default_key_error = QLabel(u"The default encryption key for Adobe Digital Editions could not be found.", self) default_key_error = QLabel("The default encryption key for Adobe Digital Editions could not be found.", self)
default_key_error.setAlignment(Qt.AlignHCenter) default_key_error.setAlignment(Qt.AlignHCenter)
layout.addWidget(default_key_error) layout.addWidget(default_key_error)
# if no default, bot buttons do the same # if no default, bot buttons do the same
@ -769,10 +757,10 @@ class AddAdeptDialog(QDialog):
def accept(self): def accept(self):
if len(self.key_name) == 0 or self.key_name.isspace(): if len(self.key_name) == 0 or self.key_name.isspace():
errmsg = u"All fields are required!" errmsg = "All fields are required!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
if len(self.key_name) < 4: if len(self.key_name) < 4:
errmsg = u"Key name must be at <i>least</i> 4 characters long!" errmsg = "Key name must be at <i>least</i> 4 characters long!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
QDialog.accept(self) QDialog.accept(self)
@ -781,7 +769,7 @@ class AddKindleDialog(QDialog):
def __init__(self, parent=None,): def __init__(self, parent=None,):
QDialog.__init__(self, parent) QDialog.__init__(self, parent)
self.parent = parent self.parent = parent
self.setWindowTitle(u"{0} {1}: Getting Default Kindle for Mac/PC Key".format(PLUGIN_NAME, PLUGIN_VERSION)) self.setWindowTitle("{0} {1}: Getting Default Kindle for Mac/PC Key".format(PLUGIN_NAME, PLUGIN_VERSION))
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
self.setLayout(layout) self.setLayout(layout)
@ -793,8 +781,8 @@ class AddKindleDialog(QDialog):
else: # linux else: # linux
from wineutils import WineGetKeys from wineutils import WineGetKeys
scriptpath = os.path.join(parent.parent.alfdir,u"kindlekey.py") scriptpath = os.path.join(parent.parent.alfdir,"kindlekey.py")
defaultkeys = WineGetKeys(scriptpath, u".k4i",parent.getwineprefix()) defaultkeys = WineGetKeys(scriptpath, ".k4i",parent.getwineprefix())
self.default_key = defaultkeys[0] self.default_key = defaultkeys[0]
except: except:
@ -811,14 +799,14 @@ class AddKindleDialog(QDialog):
key_group = QHBoxLayout() key_group = QHBoxLayout()
data_group_box_layout.addLayout(key_group) data_group_box_layout.addLayout(key_group)
key_group.addWidget(QLabel(u"Unique Key Name:", self)) key_group.addWidget(QLabel("Unique Key Name:", self))
self.key_ledit = QLineEdit(u"default_key", self) self.key_ledit = QLineEdit("default_key", self)
self.key_ledit.setToolTip(u"<p>Enter an identifying name for the current default Kindle for Mac/PC key.") self.key_ledit.setToolTip("<p>Enter an identifying name for the current default Kindle for Mac/PC key.")
key_group.addWidget(self.key_ledit) key_group.addWidget(self.key_ledit)
self.button_box.accepted.connect(self.accept) self.button_box.accepted.connect(self.accept)
else: else:
default_key_error = QLabel(u"The default encryption key for Kindle for Mac/PC could not be found.", self) default_key_error = QLabel("The default encryption key for Kindle for Mac/PC could not be found.", self)
default_key_error.setAlignment(Qt.AlignHCenter) default_key_error.setAlignment(Qt.AlignHCenter)
layout.addWidget(default_key_error) layout.addWidget(default_key_error)
@ -841,10 +829,10 @@ class AddKindleDialog(QDialog):
def accept(self): def accept(self):
if len(self.key_name) == 0 or self.key_name.isspace(): if len(self.key_name) == 0 or self.key_name.isspace():
errmsg = u"All fields are required!" errmsg = "All fields are required!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
if len(self.key_name) < 4: if len(self.key_name) < 4:
errmsg = u"Key name must be at <i>least</i> 4 characters long!" errmsg = "Key name must be at <i>least</i> 4 characters long!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
QDialog.accept(self) QDialog.accept(self)
@ -853,7 +841,7 @@ class AddSerialDialog(QDialog):
def __init__(self, parent=None,): def __init__(self, parent=None,):
QDialog.__init__(self, parent) QDialog.__init__(self, parent)
self.parent = parent self.parent = parent
self.setWindowTitle(u"{0} {1}: Add New EInk Kindle Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION)) self.setWindowTitle("{0} {1}: Add New EInk Kindle Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION))
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
self.setLayout(layout) self.setLayout(layout)
@ -864,9 +852,9 @@ class AddSerialDialog(QDialog):
key_group = QHBoxLayout() key_group = QHBoxLayout()
data_group_box_layout.addLayout(key_group) data_group_box_layout.addLayout(key_group)
key_group.addWidget(QLabel(u"EInk Kindle Serial Number:", self)) key_group.addWidget(QLabel("EInk Kindle Serial Number:", self))
self.key_ledit = QLineEdit("", self) self.key_ledit = QLineEdit("", self)
self.key_ledit.setToolTip(u"Enter an eInk Kindle serial number. EInk Kindle serial numbers are 16 characters long and usually start with a 'B' or a '9'. Kindle Serial Numbers are case-sensitive, so be sure to enter the upper and lower case letters unchanged.") self.key_ledit.setToolTip("Enter an eInk Kindle serial number. EInk Kindle serial numbers are 16 characters long and usually start with a 'B' or a '9'. Kindle Serial Numbers are case-sensitive, so be sure to enter the upper and lower case letters unchanged.")
key_group.addWidget(self.key_ledit) key_group.addWidget(self.key_ledit)
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
@ -886,10 +874,10 @@ class AddSerialDialog(QDialog):
def accept(self): def accept(self):
if len(self.key_name) == 0 or self.key_name.isspace(): if len(self.key_name) == 0 or self.key_name.isspace():
errmsg = u"Please enter an eInk Kindle Serial Number or click Cancel in the dialog." errmsg = "Please enter an eInk Kindle Serial Number or click Cancel in the dialog."
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
if len(self.key_name) != 16: if len(self.key_name) != 16:
errmsg = u"EInk Kindle Serial Numbers must be 16 characters long. This is {0:d} characters long.".format(len(self.key_name)) errmsg = "EInk Kindle Serial Numbers must be 16 characters long. This is {0:d} characters long.".format(len(self.key_name))
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
QDialog.accept(self) QDialog.accept(self)
@ -899,7 +887,7 @@ class AddAndroidDialog(QDialog):
QDialog.__init__(self, parent) QDialog.__init__(self, parent)
self.parent = parent self.parent = parent
self.setWindowTitle(u"{0} {1}: Add new Kindle for Android Key".format(PLUGIN_NAME, PLUGIN_VERSION)) self.setWindowTitle("{0} {1}: Add new Kindle for Android Key".format(PLUGIN_NAME, PLUGIN_VERSION))
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
self.setLayout(layout) self.setLayout(layout)
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
@ -911,8 +899,8 @@ class AddAndroidDialog(QDialog):
file_group = QHBoxLayout() file_group = QHBoxLayout()
data_group_box_layout.addLayout(file_group) data_group_box_layout.addLayout(file_group)
add_btn = QPushButton(u"Choose Backup File", self) add_btn = QPushButton("Choose Backup File", self)
add_btn.setToolTip(u"Import Kindle for Android backup file.") add_btn.setToolTip("Import Kindle for Android backup file.")
add_btn.clicked.connect(self.get_android_file) add_btn.clicked.connect(self.get_android_file)
file_group.addWidget(add_btn) file_group.addWidget(add_btn)
self.selected_file_name = QLabel(u"",self) self.selected_file_name = QLabel(u"",self)
@ -921,9 +909,9 @@ class AddAndroidDialog(QDialog):
key_group = QHBoxLayout() key_group = QHBoxLayout()
data_group_box_layout.addLayout(key_group) data_group_box_layout.addLayout(key_group)
key_group.addWidget(QLabel(u"Unique Key Name:", self)) key_group.addWidget(QLabel("Unique Key Name:", self))
self.key_ledit = QLineEdit(u"", self) self.key_ledit = QLineEdit(u"", self)
self.key_ledit.setToolTip(u"<p>Enter an identifying name for the Android for Kindle key.") self.key_ledit.setToolTip("<p>Enter an identifying name for the Android for Kindle key.")
key_group.addWidget(self.key_ledit) key_group.addWidget(self.key_ledit)
#key_label = QLabel(_(''), self) #key_label = QLabel(_(''), self)
#key_label.setAlignment(Qt.AlignHCenter) #key_label.setAlignment(Qt.AlignHCenter)
@ -947,9 +935,9 @@ class AddAndroidDialog(QDialog):
return self.serials_from_file return self.serials_from_file
def get_android_file(self): def get_android_file(self):
unique_dlg_name = PLUGIN_NAME + u"Import Kindle for Android backup file" #takes care of automatically remembering last directory unique_dlg_name = PLUGIN_NAME + "Import Kindle for Android backup file" #takes care of automatically remembering last directory
caption = u"Select Kindle for Android backup file to add" caption = "Select Kindle for Android backup file to add"
filters = [(u"Kindle for Android backup files", ['db','ab','xml'])] filters = [("Kindle for Android backup files", ['db','ab','xml'])]
files = choose_files(self, unique_dlg_name, caption, filters, all_files=False) files = choose_files(self, unique_dlg_name, caption, filters, all_files=False)
self.serials_from_file = [] self.serials_from_file = []
file_name = u"" file_name = u""
@ -967,13 +955,13 @@ class AddAndroidDialog(QDialog):
def accept(self): def accept(self):
if len(self.file_name) == 0 or len(self.key_value) == 0: if len(self.file_name) == 0 or len(self.key_value) == 0:
errmsg = u"Please choose a Kindle for Android backup file." errmsg = "Please choose a Kindle for Android backup file."
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
if len(self.key_name) == 0 or self.key_name.isspace(): if len(self.key_name) == 0 or self.key_name.isspace():
errmsg = u"Please enter a key name." errmsg = "Please enter a key name."
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
if len(self.key_name) < 4: if len(self.key_name) < 4:
errmsg = u"Key name must be at <i>least</i> 4 characters long!" errmsg = "Key name must be at <i>least</i> 4 characters long!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
QDialog.accept(self) QDialog.accept(self)
@ -981,7 +969,7 @@ class AddPIDDialog(QDialog):
def __init__(self, parent=None,): def __init__(self, parent=None,):
QDialog.__init__(self, parent) QDialog.__init__(self, parent)
self.parent = parent self.parent = parent
self.setWindowTitle(u"{0} {1}: Add New Mobipocket PID".format(PLUGIN_NAME, PLUGIN_VERSION)) self.setWindowTitle("{0} {1}: Add New Mobipocket PID".format(PLUGIN_NAME, PLUGIN_VERSION))
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
self.setLayout(layout) self.setLayout(layout)
@ -992,9 +980,9 @@ class AddPIDDialog(QDialog):
key_group = QHBoxLayout() key_group = QHBoxLayout()
data_group_box_layout.addLayout(key_group) data_group_box_layout.addLayout(key_group)
key_group.addWidget(QLabel(u"PID:", self)) key_group.addWidget(QLabel("PID:", self))
self.key_ledit = QLineEdit("", self) self.key_ledit = QLineEdit("", self)
self.key_ledit.setToolTip(u"Enter a Mobipocket PID. Mobipocket PIDs are 8 or 10 characters long. Mobipocket PIDs are case-sensitive, so be sure to enter the upper and lower case letters unchanged.") self.key_ledit.setToolTip("Enter a Mobipocket PID. Mobipocket PIDs are 8 or 10 characters long. Mobipocket PIDs are case-sensitive, so be sure to enter the upper and lower case letters unchanged.")
key_group.addWidget(self.key_ledit) key_group.addWidget(self.key_ledit)
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
@ -1014,10 +1002,10 @@ class AddPIDDialog(QDialog):
def accept(self): def accept(self):
if len(self.key_name) == 0 or self.key_name.isspace(): if len(self.key_name) == 0 or self.key_name.isspace():
errmsg = u"Please enter a Mobipocket PID or click Cancel in the dialog." errmsg = "Please enter a Mobipocket PID or click Cancel in the dialog."
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
if len(self.key_name) != 8 and len(self.key_name) != 10: if len(self.key_name) != 8 and len(self.key_name) != 10:
errmsg = u"Mobipocket PIDs must be 8 or 10 characters long. This is {0:d} characters long.".format(len(self.key_name)) errmsg = "Mobipocket PIDs must be 8 or 10 characters long. This is {0:d} characters long.".format(len(self.key_name))
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
QDialog.accept(self) QDialog.accept(self)

@ -1,15 +1,16 @@
#! /usr/bin/python #!/usr/bin/env python3
# -*- coding: utf-8 -*-
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
# For use with Topaz Scripts Version 2.6 # For use with Topaz Scripts Version 2.6
# Added Python 3 compatibility, September 2020 # Python 3, September 2020
from __future__ import print_function
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
self.stream = stream self.stream = stream
def write(self, data): def write(self, data):
self.stream.write(data) self.stream.buffer.write(data)
self.stream.flush() self.stream.buffer.flush()
def __getattr__(self, attr): def __getattr__(self, attr):
return getattr(self.stream, attr) return getattr(self.stream, attr)

@ -1,46 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# base64.py, version 1.0
# Copyright © 2010 Apprentice Alf
# Released under the terms of the GNU General Public Licence, version 3 or
# later. <http://www.gnu.org/licenses/>
# Revision history:
# 1 - Initial release. To allow Applescript to do base64 encoding
"""
Provide base64 encoding.
"""
from __future__ import with_statement
from __future__ import print_function
__license__ = 'GPL v3'
import sys
import os
import base64
def usage(progname):
print("Applies base64 encoding to the supplied file, sending to standard output")
print("Usage:")
print(" %s <infile>" % progname)
def cli_main(argv=sys.argv):
progname = os.path.basename(argv[0])
if len(argv)<2:
usage(progname)
sys.exit(2)
keypath = argv[1]
with open(keypath, 'rb') as f:
keyder = f.read()
print(keyder.encode('base64'))
return 0
if __name__ == '__main__':
sys.exit(cli_main())

@ -1,4 +1,5 @@
#!/usr/bin/python #!/usr/bin/env python3
# -*- coding: utf-8 -*-
# #
# This is a python script. You need a Python interpreter to run it. # This is a python script. You need a Python interpreter to run it.
# For example, ActiveState Python, which exists for windows. # For example, ActiveState Python, which exists for windows.
@ -10,7 +11,7 @@
# Changelog epubtest # Changelog epubtest
# 1.00 - Cut to epubtest.py, testing ePub files only by Apprentice Alf # 1.00 - Cut to epubtest.py, testing ePub files only by Apprentice Alf
# 1.01 - Added routine for use by Windows DeDRM # 1.01 - Added routine for use by Windows DeDRM
# 2.00 - Added Python 3 compatibility, September 2020 # 2.00 - Python 3, September 2020
# #
# Written in 2011 by Paul Durrant # Written in 2011 by Paul Durrant
# Released with unlicense. See http://unlicense.org/ # Released with unlicense. See http://unlicense.org/
@ -45,9 +46,6 @@
# It's still polite to give attribution if you do reuse this code. # It's still polite to give attribution if you do reuse this code.
# #
from __future__ import with_statement
from __future__ import print_function
__version__ = '2.0' __version__ = '2.0'
import sys, struct, os, traceback import sys, struct, os, traceback
@ -112,7 +110,7 @@ def unicode_argv():
xrange(start, argc.value)] xrange(start, argc.value)]
# if we don't have any arguments at all, just pass back script name # if we don't have any arguments at all, just pass back script name
# this should never happen # this should never happen
return [u"epubtest.py"] return ["epubtest.py"]
else: else:
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:

@ -1,13 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# erdr2pml.py # erdr2pml.py
# Copyright © 2008 The Dark Reverser # Copyright © 2008-2020 The Dark Reverser, Apprentice Harper et al.
# #
# Modified 20082012 by some_updates, DiapDealer and Apprentice Alf
# This is a python script. You need a Python interpreter to run it.
# For example, ActiveState Python, which exists for windows.
# Changelog # Changelog
# #
# Based on ereader2html version 0.08 plus some later small fixes # Based on ereader2html version 0.08 plus some later small fixes
@ -89,10 +85,10 @@ class SafeUnbuffered:
if self.encoding == None: if self.encoding == None:
self.encoding = "utf-8" self.encoding = "utf-8"
def write(self, data): def write(self, data):
if isinstance(data,bytes): if isinstance(data,str):
data = data.encode(self.encoding,"replace") data = data.encode(self.encoding,"replace")
self.stream.write(data) self.stream.buffer.write(data)
self.stream.flush() self.stream.buffer.flush()
def __getattr__(self, attr): def __getattr__(self, attr):
return getattr(self.stream, attr) return getattr(self.stream, attr)
@ -130,7 +126,7 @@ def unicode_argv():
range(start, argc.value)] range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name # if we don't have any arguments at all, just pass back script name
# this should never happen # this should never happen
return [u"mobidedrm.py"] return ["mobidedrm.py"]
else: else:
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
@ -230,16 +226,16 @@ class Sectionizer(object):
# and with some (heavily edited) code from Paul Durrant's kindlenamer.py # and with some (heavily edited) code from Paul Durrant's kindlenamer.py
def sanitizeFileName(name): def sanitizeFileName(name):
# substitute filename unfriendly characters # substitute filename unfriendly characters
name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" ").replace(u": ",u" ").replace(u":",u"").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'") name = name.replace("<","[").replace(">","]").replace(" : "," ").replace(": "," ").replace(":","").replace("/","_").replace("\\","_").replace("|","_").replace("\"","\'")
# delete control characters # delete control characters
name = u"".join(char for char in name if ord(char)>=32) name = "".join(char for char in name if ord(char)>=32)
# white space to single space, delete leading and trailing while space # white space to single space, delete leading and trailing while space
name = re.sub(r"\s", u" ", name).strip() name = re.sub(r"\s", " ", name).strip()
# remove leading dots # remove leading dots
while len(name)>0 and name[0] == u".": while len(name)>0 and name[0] == ".":
name = name[1:] name = name[1:]
# remove trailing dots (Windows doesn't like them) # remove trailing dots (Windows doesn't like them)
if name.endswith(u'.'): if name.endswith("."):
name = name[:-1] name = name[:-1]
return name return name
@ -472,35 +468,35 @@ def decryptBook(infile, outpath, make_pmlz, user_key):
# outpath is actually pmlz name # outpath is actually pmlz name
pmlzname = outpath pmlzname = outpath
outdir = tempfile.mkdtemp() outdir = tempfile.mkdtemp()
imagedirpath = os.path.join(outdir,u"images") imagedirpath = os.path.join(outdir,"images")
else: else:
pmlzname = None pmlzname = None
outdir = outpath outdir = outpath
imagedirpath = os.path.join(outdir,bookname + u"_img") imagedirpath = os.path.join(outdir,bookname + "_img")
try: try:
if not os.path.exists(outdir): if not os.path.exists(outdir):
os.makedirs(outdir) os.makedirs(outdir)
print(u"Decoding File") print("Decoding File")
sect = Sectionizer(infile, 'PNRdPPrs') sect =Sectionizer(infile, 'PNRdPPrs')
er = EreaderProcessor(sect, user_key) er = EreaderProcessor(sect, user_key)
if er.getNumImages() > 0: if er.getNumImages() > 0:
print(u"Extracting images") print("Extracting images")
if not os.path.exists(imagedirpath): if not os.path.exists(imagedirpath):
os.makedirs(imagedirpath) os.makedirs(imagedirpath)
for i in range(er.getNumImages()): for i in range(er.getNumImages()):
name, contents = er.getImage(i) name, contents = er.getImage(i)
open(os.path.join(imagedirpath, name), 'wb').write(contents) open(os.path.join(imagedirpath, name), 'wb').write(contents)
print(u"Extracting pml") print("Extracting pml")
pml_string = er.getText() pml_string = er.getText()
pmlfilename = bookname + ".pml" pmlfilename = bookname + ".pml"
open(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string)) open(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string))
if pmlzname is not None: if pmlzname is not None:
import zipfile import zipfile
import shutil import shutil
print(u"Creating PMLZ file {0}".format(os.path.basename(pmlzname))) print("Creating PMLZ file {0}".format(os.path.basename(pmlzname)))
myZipFile = zipfile.ZipFile(pmlzname,'w',zipfile.ZIP_STORED, False) myZipFile = zipfile.ZipFile(pmlzname,'w',zipfile.ZIP_STORED, False)
list = os.listdir(outdir) list = os.listdir(outdir)
for filename in list: for filename in list:
@ -519,33 +515,33 @@ def decryptBook(infile, outpath, make_pmlz, user_key):
myZipFile.close() myZipFile.close()
# remove temporary directory # remove temporary directory
shutil.rmtree(outdir, True) shutil.rmtree(outdir, True)
print(u"Output is {0}".format(pmlzname)) print("Output is {0}".format(pmlzname))
else : else
print(u"Output is in {0}".format(outdir)) print("Output is in {0}".format(outdir))
print("done") print("done")
except ValueError as e: except ValueError as e:
print(u"Error: {0}".format(e)) print("Error: {0}".format(e))
traceback.print_exc() traceback.print_exc()
return 1 return 1
return 0 return 0
def usage(): def usage():
print(u"Converts DRMed eReader books to PML Source") print("Converts DRMed eReader books to PML Source")
print(u"Usage:") print("Usage:")
print(u" erdr2pml [options] infile.pdb [outpath] \"your name\" credit_card_number") print(" erdr2pml [options] infile.pdb [outpath] \"your name\" credit_card_number")
print(u" ") print(" ")
print(u"Options: ") print("Options: ")
print(u" -h prints this message") print(" -h prints this message")
print(u" -p create PMLZ instead of source folder") print(" -p create PMLZ instead of source folder")
print(u" --make-pmlz create PMLZ instead of source folder") print(" --make-pmlz create PMLZ instead of source folder")
print(u" ") print(" ")
print(u"Note:") print("Note:")
print(u" if outpath is ommitted, creates source in 'infile_Source' folder") print(" if outpath is ommitted, creates source in 'infile_Source' folder")
print(u" if outpath is ommitted and pmlz option, creates PMLZ 'infile.pmlz'") print(" if outpath is ommitted and pmlz option, creates PMLZ 'infile.pmlz'")
print(u" if source folder created, images are in infile_img folder") print(" if source folder created, images are in infile_img folder")
print(u" if pmlz file created, images are in images folder") print(" if pmlz file created, images are in images folder")
print(u" It's enough to enter the last 8 digits of the credit card number") print(" It's enough to enter the last 8 digits of the credit card number")
return return
def getuser_key(name,cc): def getuser_key(name,cc):
@ -554,7 +550,7 @@ def getuser_key(name,cc):
return struct.pack('>LL', binascii.crc32(newname) & 0xffffffff,binascii.crc32(cc[-8:])& 0xffffffff) return struct.pack('>LL', binascii.crc32(newname) & 0xffffffff,binascii.crc32(cc[-8:])& 0xffffffff)
def cli_main(): def cli_main():
print(u"eRdr2Pml v{0}. Copyright © 20092012 The Dark Reverser et al.".format(__version__)) print("eRdr2Pml v{0}. Copyright © 20092020 The Dark Reverser et al.".format(__version__))
argv=unicode_argv() argv=unicode_argv()
try: try:
@ -580,9 +576,9 @@ def cli_main():
if len(args)==3: if len(args)==3:
infile, name, cc = args infile, name, cc = args
if make_pmlz: if make_pmlz:
outpath = os.path.splitext(infile)[0] + u".pmlz" outpath = os.path.splitext(infile)[0] + ".pmlz"
else: else:
outpath = os.path.splitext(infile)[0] + u"_Source" outpath = os.path.splitext(infile)[0] + "_Source"
elif len(args)==4: elif len(args)==4:
infile, outpath, name, cc = args infile, outpath, name, cc = args

@ -1,16 +1,14 @@
#! /usr/bin/python #!/usr/bin/env python3
# -*- coding: utf-8 -*-
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
# Added Python 3 compatibility for calibre 5.0 # Python 3 for calibre 5.0
from __future__ import print_function
from .convert2xml import encodeNumber
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
self.stream = stream self.stream = stream
def write(self, data): def write(self, data):
self.stream.write(data) self.stream.buffer.write(data)
self.stream.flush() self.stream.buffer.flush()
def __getattr__(self, attr): def __getattr__(self, attr):
return getattr(self.stream, attr) return getattr(self.stream, attr)

@ -1,28 +1,13 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement # ignobleepub.py
from __future__ import print_function # Copyright © 2009-2020 by i♥cabbages, Apprentice Harper et al.
# ignobleepub.pyw, version 4.1
# Copyright © 2009-2010 by i♥cabbages
# Released under the terms of the GNU General Public Licence, version 3 # Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/> # <http://www.gnu.org/licenses/>
# Modified 20102013 by some_updates, DiapDealer and Apprentice Alf
# Modified 20152017 by Apprentice Harper
# Windows users: Before running this program, you must first install Python 2.6
# from <http://www.python.org/download/> and PyCrypto from
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
# install the version for Python 2.6). Save this script file as
# ineptepub.pyw and double-click on it to run it.
# #
# Mac OS X users: Save this script file as ineptepub.pyw. You can run this
# program from the command line (pythonw ineptepub.pyw) or by double-clicking
# it when it has been associated with PythonLauncher.
# Revision history: # Revision history:
# 1 - Initial release # 1 - Initial release
# 2 - Added OS X support by using OpenSSL when available # 2 - Added OS X support by using OpenSSL when available
@ -38,7 +23,7 @@ from __future__ import print_function
# 3.9 - moved unicode_argv call inside main for Windows DeDRM compatibility # 3.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
# 4.0 - Work if TkInter is missing # 4.0 - Work if TkInter is missing
# 4.1 - Import tkFileDialog, don't assume something else will import it. # 4.1 - Import tkFileDialog, don't assume something else will import it.
# 5.0 - Added Python 3 compatibility for calibre 5.0 # 5.0 - Python 3 for calibre 5.0
""" """
Decrypt Barnes & Noble encrypted ePub books. Decrypt Barnes & Noble encrypted ePub books.
@ -66,10 +51,10 @@ class SafeUnbuffered:
if self.encoding == None: if self.encoding == None:
self.encoding = "utf-8" self.encoding = "utf-8"
def write(self, data): def write(self, data):
if isinstance(data,bytes): if isinstance(data,str):
data = data.encode(self.encoding,"replace") data = data.encode(self.encoding,"replace")
self.stream.write(data) self.stream.buffer.write(data)
self.stream.flush() self.stream.buffer.flush()
def __getattr__(self, attr): def __getattr__(self, attr):
return getattr(self.stream, attr) return getattr(self.stream, attr)
@ -108,7 +93,7 @@ def unicode_argv():
start = argc.value - len(sys.argv) start = argc.value - len(sys.argv)
return [argv[i] for i in return [argv[i] for i in
range(start, argc.value)] range(start, argc.value)]
return [u"ineptepub.py"] return ["ineptepub.py"]
else: else:
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
@ -257,14 +242,14 @@ def ignobleBook(inpath):
def decryptBook(keyb64, inpath, outpath): def decryptBook(keyb64, inpath, outpath):
if AES is None: if AES is None:
raise IGNOBLEError(u"PyCrypto or OpenSSL must be installed.") raise IGNOBLEError("PyCrypto or OpenSSL must be installed.")
key = keyb64.decode('base64')[:16] key = keyb64.decode('base64')[:16]
aes = AES(key) aes = AES(key)
with closing(ZipFile(open(inpath, 'rb'))) as inf: with closing(ZipFile(open(inpath, 'rb'))) as inf:
namelist = set(inf.namelist()) namelist = set(inf.namelist())
if 'META-INF/rights.xml' not in namelist or \ if 'META-INF/rights.xml' not in namelist or \
'META-INF/encryption.xml' not in namelist: 'META-INF/encryption.xml' not in namelist:
print(u"{0:s} is DRM-free.".format(os.path.basename(inpath))) print("{0:s} is DRM-free.".format(os.path.basename(inpath)))
return 1 return 1
for name in META_NAMES: for name in META_NAMES:
namelist.remove(name) namelist.remove(name)
@ -274,7 +259,7 @@ def decryptBook(keyb64, inpath, outpath):
expr = './/%s' % (adept('encryptedKey'),) expr = './/%s' % (adept('encryptedKey'),)
bookkey = ''.join(rights.findtext(expr)) bookkey = ''.join(rights.findtext(expr))
if len(bookkey) != 64: if len(bookkey) != 64:
print(u"{0:s} is not a secure Barnes & Noble ePub.".format(os.path.basename(inpath))) print("{0:s} is not a secure Barnes & Noble ePub.".format(os.path.basename(inpath)))
return 1 return 1
bookkey = aes.decrypt(bookkey.decode('base64')) bookkey = aes.decrypt(bookkey.decode('base64'))
bookkey = bookkey[:-ord(bookkey[-1])] bookkey = bookkey[:-ord(bookkey[-1])]
@ -317,7 +302,7 @@ def decryptBook(keyb64, inpath, outpath):
pass pass
outf.writestr(zi, decryptor.decrypt(path, data)) outf.writestr(zi, decryptor.decrypt(path, data))
except: except:
print(u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc())) print("Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc()))
return 2 return 2
return 0 return 0
@ -328,13 +313,13 @@ def cli_main():
argv=unicode_argv() argv=unicode_argv()
progname = os.path.basename(argv[0]) progname = os.path.basename(argv[0])
if len(argv) != 4: if len(argv) != 4:
print(u"usage: {0} <keyfile.b64> <inbook.epub> <outbook.epub>".format(progname)) print("usage: {0} <keyfile.b64> <inbook.epub> <outbook.epub>".format(progname))
return 1 return 1
keypath, inpath, outpath = argv[1:] keypath, inpath, outpath = argv[1:]
userkey = open(keypath,'rb').read() userkey = open(keypath,'rb').read()
result = decryptBook(userkey, inpath, outpath) result = decryptBook(userkey, inpath, outpath)
if result == 0: if result == 0:
print(u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath))) print("Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)))
return result return result
def gui_main(): def gui_main():
@ -350,43 +335,43 @@ def gui_main():
class DecryptionDialog(Tkinter.Frame): class DecryptionDialog(Tkinter.Frame):
def __init__(self, root): def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5) Tkinter.Frame.__init__(self, root, border=5)
self.status = Tkinter.Label(self, text=u"Select files for decryption") self.status = Tkinter.Label(self, text="Select files for decryption")
self.status.pack(fill=Tkconstants.X, expand=1) self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self) body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1) body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2) body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text=u"Key file").grid(row=0) Tkinter.Label(body, text="Key file").grid(row=0)
self.keypath = Tkinter.Entry(body, width=30) self.keypath = Tkinter.Entry(body, width=30)
self.keypath.grid(row=0, column=1, sticky=sticky) self.keypath.grid(row=0, column=1, sticky=sticky)
if os.path.exists(u"bnepubkey.b64"): if os.path.exists("bnepubkey.b64"):
self.keypath.insert(0, u"bnepubkey.b64") self.keypath.insert(0, "bnepubkey.b64")
button = Tkinter.Button(body, text=u"...", command=self.get_keypath) button = Tkinter.Button(body, text="...", command=self.get_keypath)
button.grid(row=0, column=2) button.grid(row=0, column=2)
Tkinter.Label(body, text=u"Input file").grid(row=1) Tkinter.Label(body, text="Input file").grid(row=1)
self.inpath = Tkinter.Entry(body, width=30) self.inpath = Tkinter.Entry(body, width=30)
self.inpath.grid(row=1, column=1, sticky=sticky) self.inpath.grid(row=1, column=1, sticky=sticky)
button = Tkinter.Button(body, text=u"...", command=self.get_inpath) button = Tkinter.Button(body, text="...", command=self.get_inpath)
button.grid(row=1, column=2) button.grid(row=1, column=2)
Tkinter.Label(body, text=u"Output file").grid(row=2) Tkinter.Label(body, text="Output file").grid(row=2)
self.outpath = Tkinter.Entry(body, width=30) self.outpath = Tkinter.Entry(body, width=30)
self.outpath.grid(row=2, column=1, sticky=sticky) self.outpath.grid(row=2, column=1, sticky=sticky)
button = Tkinter.Button(body, text=u"...", command=self.get_outpath) button = Tkinter.Button(body, text="...", command=self.get_outpath)
button.grid(row=2, column=2) button.grid(row=2, column=2)
buttons = Tkinter.Frame(self) buttons = Tkinter.Frame(self)
buttons.pack() buttons.pack()
botton = Tkinter.Button( botton = Tkinter.Button(
buttons, text=u"Decrypt", width=10, command=self.decrypt) buttons, text="Decrypt", width=10, command=self.decrypt)
botton.pack(side=Tkconstants.LEFT) botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button = Tkinter.Button( button = Tkinter.Button(
buttons, text=u"Quit", width=10, command=self.quit) buttons, text="Quit", width=10, command=self.quit)
button.pack(side=Tkconstants.RIGHT) button.pack(side=Tkconstants.RIGHT)
def get_keypath(self): def get_keypath(self):
keypath = tkFileDialog.askopenfilename( keypath = tkFileDialog.askopenfilename(
parent=None, title=u"Select Barnes & Noble \'.b64\' key file", parent=None, title="Select Barnes & Noble \'.b64\' key file",
defaultextension=u".b64", defaultextension=".b64",
filetypes=[('base64-encoded files', '.b64'), filetypes=[('base64-encoded files', '.b64'),
('All Files', '.*')]) ('All Files', '.*')])
if keypath: if keypath:
@ -397,8 +382,8 @@ def gui_main():
def get_inpath(self): def get_inpath(self):
inpath = tkFileDialog.askopenfilename( inpath = tkFileDialog.askopenfilename(
parent=None, title=u"Select B&N-encrypted ePub file to decrypt", parent=None, title="Select B&N-encrypted ePub file to decrypt",
defaultextension=u".epub", filetypes=[('ePub files', '.epub')]) defaultextension=".epub", filetypes=[('ePub files', '.epub')])
if inpath: if inpath:
inpath = os.path.normpath(inpath) inpath = os.path.normpath(inpath)
self.inpath.delete(0, Tkconstants.END) self.inpath.delete(0, Tkconstants.END)
@ -407,8 +392,8 @@ def gui_main():
def get_outpath(self): def get_outpath(self):
outpath = tkFileDialog.asksaveasfilename( outpath = tkFileDialog.asksaveasfilename(
parent=None, title=u"Select unencrypted ePub file to produce", parent=None, title="Select unencrypted ePub file to produce",
defaultextension=u".epub", filetypes=[('ePub files', '.epub')]) defaultextension=".epub", filetypes=[('ePub files', '.epub')])
if outpath: if outpath:
outpath = os.path.normpath(outpath) outpath = os.path.normpath(outpath)
self.outpath.delete(0, Tkconstants.END) self.outpath.delete(0, Tkconstants.END)
@ -420,31 +405,31 @@ def gui_main():
inpath = self.inpath.get() inpath = self.inpath.get()
outpath = self.outpath.get() outpath = self.outpath.get()
if not keypath or not os.path.exists(keypath): if not keypath or not os.path.exists(keypath):
self.status['text'] = u"Specified key file does not exist" self.status['text'] = "Specified key file does not exist"
return return
if not inpath or not os.path.exists(inpath): if not inpath or not os.path.exists(inpath):
self.status['text'] = u"Specified input file does not exist" self.status['text'] = "Specified input file does not exist"
return return
if not outpath: if not outpath:
self.status['text'] = u"Output file not specified" self.status['text'] = "Output file not specified"
return return
if inpath == outpath: if inpath == outpath:
self.status['text'] = u"Must have different input and output files" self.status['text'] = "Must have different input and output files"
return return
userkey = open(keypath,'rb').read() userkey = open(keypath,'rb').read()
self.status['text'] = u"Decrypting..." self.status['text'] = "Decrypting..."
try: try:
decrypt_status = decryptBook(userkey, inpath, outpath) decrypt_status = decryptBook(userkey, inpath, outpath)
except Exception as e: except Exception as e:
self.status['text'] = u"Error: {0}".format(e.args[0]) self.status['text'] = "Error: {0}".format(e.args[0])
return return
if decrypt_status == 0: if decrypt_status == 0:
self.status['text'] = u"File successfully decrypted" self.status['text'] = "File successfully decrypted"
else: else:
self.status['text'] = u"The was an error decrypting the file." self.status['text'] = "The was an error decrypting the file."
root = Tkinter.Tk() root = Tkinter.Tk()
root.title(u"Barnes & Noble ePub Decrypter v.{0}".format(__version__)) root.title("Barnes & Noble ePub Decrypter v.{0}".format(__version__))
root.resizable(True, False) root.resizable(True, False)
root.minsize(300, 0) root.minsize(300, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)

@ -1,9 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement
from __future__ import print_function
# ignoblekey.py # ignoblekey.py
# Copyright © 2015-2020 Apprentice Alf, Apprentice Harper et al. # Copyright © 2015-2020 Apprentice Alf, Apprentice Harper et al.
@ -15,7 +12,7 @@ from __future__ import print_function
# Revision history: # Revision history:
# 1.0 - Initial release # 1.0 - Initial release
# 1.1 - remove duplicates and return last key as single key # 1.1 - remove duplicates and return last key as single key
# 2.0 - Added Python 3 compatibility for calibre 5.0 # 2.0 - Python 3 for calibre 5.0
""" """
Get Barnes & Noble EPUB user key from nook Studio log file Get Barnes & Noble EPUB user key from nook Studio log file
@ -40,10 +37,10 @@ class SafeUnbuffered:
if self.encoding == None: if self.encoding == None:
self.encoding = "utf-8" self.encoding = "utf-8"
def write(self, data): def write(self, data):
if isinstance(data,bytes): if isinstance(data,str):
data = data.encode(self.encoding,"replace") data = data.encode(self.encoding,"replace")
self.stream.write(data) self.stream.buffer.write(data)
self.stream.flush() self.stream.buffer.flush()
def __getattr__(self, attr): def __getattr__(self, attr):
return getattr(self.stream, attr) return getattr(self.stream, attr)
@ -84,7 +81,7 @@ def unicode_argv():
range(start, argc.value)] range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name # if we don't have any arguments at all, just pass back script name
# this should never happen # this should never happen
return [u"ignoblekey.py"] return ["ignoblekey.py"]
else: else:
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
@ -106,15 +103,15 @@ def getNookLogFiles():
paths = set() paths = set()
if 'LOCALAPPDATA' in os.environ.keys(): if 'LOCALAPPDATA' in os.environ.keys():
# Python 2.x does not return unicode env. Use Python 3.x # Python 2.x does not return unicode env. Use Python 3.x
path = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%") path = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%")
if os.path.isdir(path): if os.path.isdir(path):
paths.add(path) paths.add(path)
if 'USERPROFILE' in os.environ.keys(): if 'USERPROFILE' in os.environ.keys():
# Python 2.x does not return unicode env. Use Python 3.x # Python 2.x does not return unicode env. Use Python 3.x
path = winreg.ExpandEnvironmentStrings(u"%USERPROFILE%")+u"\\AppData\\Local" path = winreg.ExpandEnvironmentStrings("%USERPROFILE%")+"\\AppData\\Local"
if os.path.isdir(path): if os.path.isdir(path):
paths.add(path) paths.add(path)
path = winreg.ExpandEnvironmentStrings(u"%USERPROFILE%")+u"\\AppData\\Roaming" path = winreg.ExpandEnvironmentStrings("%USERPROFILE%")+"\\AppData\\Roaming"
if os.path.isdir(path): if os.path.isdir(path):
paths.add(path) paths.add(path)
# User Shell Folders show take precedent over Shell Folders if present # User Shell Folders show take precedent over Shell Folders if present
@ -200,7 +197,7 @@ def nookkeys(files = []):
for file in files: for file in files:
fileKeys = getKeysFromLog(file) fileKeys = getKeysFromLog(file)
if fileKeys: if fileKeys:
print(u"Found {0} keys in the Nook Study log files".format(len(fileKeys))) print("Found {0} keys in the Nook Study log files".format(len(fileKeys)))
keys.extend(fileKeys) keys.extend(fileKeys)
return list(set(keys)) return list(set(keys))
@ -213,27 +210,27 @@ def getkey(outpath, files=[]):
outfile = outpath outfile = outpath
with open(outfile, 'w') as keyfileout: with open(outfile, 'w') as keyfileout:
keyfileout.write(keys[-1]) keyfileout.write(keys[-1])
print(u"Saved a key to {0}".format(outfile)) print("Saved a key to {0}".format(outfile))
else: else:
keycount = 0 keycount = 0
for key in keys: for key in keys:
while True: while True:
keycount += 1 keycount += 1
outfile = os.path.join(outpath,u"nookkey{0:d}.b64".format(keycount)) outfile = os.path.join(outpath,"nookkey{0:d}.b64".format(keycount))
if not os.path.exists(outfile): if not os.path.exists(outfile):
break break
with open(outfile, 'w') as keyfileout: with open(outfile, 'w') as keyfileout:
keyfileout.write(key) keyfileout.write(key)
print(u"Saved a key to {0}".format(outfile)) print("Saved a key to {0}".format(outfile))
return True return True
return False return False
def usage(progname): def usage(progname):
print(u"Finds the nook Study encryption keys.") print("Finds the nook Study encryption keys.")
print(u"Keys are saved to the current directory, or a specified output directory.") print("Keys are saved to the current directory, or a specified output directory.")
print(u"If a file name is passed instead of a directory, only the first key is saved, in that file.") print("If a file name is passed instead of a directory, only the first key is saved, in that file.")
print(u"Usage:") print("Usage:")
print(u" {0:s} [-h] [-k <logFile>] [<outpath>]".format(progname)) print(" {0:s} [-h] [-k <logFile>] [<outpath>]".format(progname))
def cli_main(): def cli_main():
@ -241,12 +238,12 @@ def cli_main():
sys.stderr=SafeUnbuffered(sys.stderr) sys.stderr=SafeUnbuffered(sys.stderr)
argv=unicode_argv() argv=unicode_argv()
progname = os.path.basename(argv[0]) progname = os.path.basename(argv[0])
print(u"{0} v{1}\nCopyright © 2015 Apprentice Alf".format(progname,__version__)) print("{0} v{1}\nCopyright © 2015 Apprentice Alf".format(progname,__version__))
try: try:
opts, args = getopt.getopt(argv[1:], "hk:") opts, args = getopt.getopt(argv[1:], "hk:")
except getopt.GetoptError as err: except getopt.GetoptError as err:
print(u"Error in options or arguments: {0}".format(err.args[0])) print("Error in options or arguments: {0}".format(err.args[0]))
usage(progname) usage(progname)
sys.exit(2) sys.exit(2)
@ -275,7 +272,7 @@ def cli_main():
outpath = os.path.realpath(os.path.normpath(outpath)) outpath = os.path.realpath(os.path.normpath(outpath))
if not getkey(outpath, files): if not getkey(outpath, files):
print(u"Could not retrieve nook Study key.") print("Could not retrieve nook Study key.")
return 0 return 0
@ -291,7 +288,7 @@ def gui_main():
class ExceptionDialog(Tkinter.Frame): class ExceptionDialog(Tkinter.Frame):
def __init__(self, root, text): def __init__(self, root, text):
Tkinter.Frame.__init__(self, root, border=5) Tkinter.Frame.__init__(self, root, border=5)
label = Tkinter.Label(self, text=u"Unexpected error:", label = Tkinter.Label(self, text="Unexpected error:",
anchor=Tkconstants.W, justify=Tkconstants.LEFT) anchor=Tkconstants.W, justify=Tkconstants.LEFT)
label.pack(fill=Tkconstants.X, expand=0) label.pack(fill=Tkconstants.X, expand=0)
self.text = Tkinter.Text(self) self.text = Tkinter.Text(self)
@ -312,16 +309,16 @@ def gui_main():
print(key) print(key)
while True: while True:
keycount += 1 keycount += 1
outfile = os.path.join(progpath,u"nookkey{0:d}.b64".format(keycount)) outfile = os.path.join(progpath,"nookkey{0:d}.b64".format(keycount))
if not os.path.exists(outfile): if not os.path.exists(outfile):
break break
with open(outfile, 'w') as keyfileout: with open(outfile, 'w') as keyfileout:
keyfileout.write(key) keyfileout.write(key)
success = True success = True
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile)) tkMessageBox.showinfo(progname, "Key successfully retrieved to {0}".format(outfile))
except DrmException as e: except DrmException as e:
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e))) tkMessageBox.showerror(progname, "Error: {0}".format(str(e)))
except Exception: except Exception:
root.wm_state('normal') root.wm_state('normal')
root.title(progname) root.title(progname)

@ -1,10 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement # ignoblekeyfetch.py
from __future__ import print_function
# ignoblekeyfetch.pyw, version 2.0
# Copyright © 2015-2020 Apprentice Harper et al. # Copyright © 2015-2020 Apprentice Harper et al.
# Released under the terms of the GNU General Public Licence, version 3 # Released under the terms of the GNU General Public Licence, version 3
@ -25,14 +22,14 @@ from __future__ import print_function
# Revision history: # Revision history:
# 1.0 - Initial version # 1.0 - Initial version
# 1.1 - Try second URL if first one fails # 1.1 - Try second URL if first one fails
# 2.0 - Added Python 3 compatibility for calibre 5.0 # 2.0 - Python 3 for calibre 5.0
""" """
Fetch Barnes & Noble EPUB user key from B&N servers using email and password Fetch Barnes & Noble EPUB user key from B&N servers using email and password
""" """
__license__ = 'GPL v3' __license__ = 'GPL v3'
__version__ = "1.1" __version__ = "2.0"
import sys import sys
import os import os
@ -91,7 +88,7 @@ def unicode_argv():
range(start, argc.value)] range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name # if we don't have any arguments at all, just pass back script name
# this should never happen # this should never happen
return [u"ignoblekeyfetch.py"] return ["ignoblekeyfetch.py"]
else: else:
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
@ -157,14 +154,14 @@ def cli_main():
argv=unicode_argv() argv=unicode_argv()
progname = os.path.basename(argv[0]) progname = os.path.basename(argv[0])
if len(argv) != 4: if len(argv) != 4:
print(u"usage: {0} <email> <password> <keyfileout.b64>".format(progname)) print("usage: {0} <email> <password> <keyfileout.b64>".format(progname))
return 1 return 1
email, password, keypath = argv[1:] email, password, keypath = argv[1:]
userkey = fetch_key(email, password) userkey = fetch_key(email, password)
if len(userkey) == 28: if len(userkey) == 28:
open(keypath,'wb').write(userkey) open(keypath,'wb').write(userkey)
return 0 return 0
print(u"Failed to fetch key.") print("Failed to fetch key.")
return 1 return 1
@ -181,38 +178,38 @@ def gui_main():
class DecryptionDialog(Tkinter.Frame): class DecryptionDialog(Tkinter.Frame):
def __init__(self, root): def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5) Tkinter.Frame.__init__(self, root, border=5)
self.status = Tkinter.Label(self, text=u"Enter parameters") self.status = Tkinter.Label(self, text="Enter parameters")
self.status.pack(fill=Tkconstants.X, expand=1) self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self) body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1) body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2) body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text=u"Account email address").grid(row=0) Tkinter.Label(body, text="Account email address").grid(row=0)
self.name = Tkinter.Entry(body, width=40) self.name = Tkinter.Entry(body, width=40)
self.name.grid(row=0, column=1, sticky=sticky) self.name.grid(row=0, column=1, sticky=sticky)
Tkinter.Label(body, text=u"Account password").grid(row=1) Tkinter.Label(body, text="Account password").grid(row=1)
self.ccn = Tkinter.Entry(body, width=40) self.ccn = Tkinter.Entry(body, width=40)
self.ccn.grid(row=1, column=1, sticky=sticky) self.ccn.grid(row=1, column=1, sticky=sticky)
Tkinter.Label(body, text=u"Output file").grid(row=2) Tkinter.Label(body, text="Output file").grid(row=2)
self.keypath = Tkinter.Entry(body, width=40) self.keypath = Tkinter.Entry(body, width=40)
self.keypath.grid(row=2, column=1, sticky=sticky) self.keypath.grid(row=2, column=1, sticky=sticky)
self.keypath.insert(2, u"bnepubkey.b64") self.keypath.insert(2, "bnepubkey.b64")
button = Tkinter.Button(body, text=u"...", command=self.get_keypath) button = Tkinter.Button(body, text="...", command=self.get_keypath)
button.grid(row=2, column=2) button.grid(row=2, column=2)
buttons = Tkinter.Frame(self) buttons = Tkinter.Frame(self)
buttons.pack() buttons.pack()
botton = Tkinter.Button( botton = Tkinter.Button(
buttons, text=u"Fetch", width=10, command=self.generate) buttons, text="Fetch", width=10, command=self.generate)
botton.pack(side=Tkconstants.LEFT) botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button = Tkinter.Button( button = Tkinter.Button(
buttons, text=u"Quit", width=10, command=self.quit) buttons, text="Quit", width=10, command=self.quit)
button.pack(side=Tkconstants.RIGHT) button.pack(side=Tkconstants.RIGHT)
def get_keypath(self): def get_keypath(self):
keypath = tkFileDialog.asksaveasfilename( keypath = tkFileDialog.asksaveasfilename(
parent=None, title=u"Select B&N ePub key file to produce", parent=None, title="Select B&N ePub key file to produce",
defaultextension=u".b64", defaultextension=".b64",
filetypes=[('base64-encoded files', '.b64'), filetypes=[('base64-encoded files', '.b64'),
('All Files', '.*')]) ('All Files', '.*')])
if keypath: if keypath:
@ -226,28 +223,28 @@ def gui_main():
password = self.ccn.get() password = self.ccn.get()
keypath = self.keypath.get() keypath = self.keypath.get()
if not email: if not email:
self.status['text'] = u"Email address not given" self.status['text'] = "Email address not given"
return return
if not password: if not password:
self.status['text'] = u"Account password not given" self.status['text'] = "Account password not given"
return return
if not keypath: if not keypath:
self.status['text'] = u"Output keyfile path not set" self.status['text'] = "Output keyfile path not set"
return return
self.status['text'] = u"Fetching..." self.status['text'] = "Fetching..."
try: try:
userkey = fetch_key(email, password) userkey = fetch_key(email, password)
except Exception as e: except Exception as e:
self.status['text'] = u"Error: {0}".format(e.args[0]) self.status['text'] = "Error: {0}".format(e.args[0])
return return
if len(userkey) == 28: if len(userkey) == 28:
open(keypath,'wb').write(userkey) open(keypath,'wb').write(userkey)
self.status['text'] = u"Keyfile fetched successfully" self.status['text'] = "Keyfile fetched successfully"
else: else:
self.status['text'] = u"Keyfile fetch failed." self.status['text'] = "Keyfile fetch failed."
root = Tkinter.Tk() root = Tkinter.Tk()
root.title(u"Barnes & Noble ePub Keyfile Fetch v.{0}".format(__version__)) root.title("Barnes & Noble ePub Keyfile Fetch v.{0}".format(__version__))
root.resizable(True, False) root.resizable(True, False)
root.minsize(300, 0) root.minsize(300, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)

@ -1,10 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement # ignoblekeygen.py
from __future__ import print_function
# ignoblekeygen.pyw
# Copyright © 2009-2020 i♥cabbages, Apprentice Harper et al. # Copyright © 2009-2020 i♥cabbages, Apprentice Harper et al.
# Released under the terms of the GNU General Public Licence, version 3 # Released under the terms of the GNU General Public Licence, version 3
@ -100,7 +97,7 @@ def unicode_argv():
range(start, argc.value)] range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name # if we don't have any arguments at all, just pass back script name
# this should never happen # this should never happen
return [u"ignoblekeygen.py"] return ["ignoblekeygen.py"]
else: else:
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
@ -228,7 +225,7 @@ def cli_main():
(progname,)) (progname,))
return 1 return 1
if len(argv) != 4: if len(argv) != 4:
print(u"usage: {0} <Name> <CC#> <keyfileout.b64>".format(progname)) print("usage: {0} <Name> <CC#> <keyfileout.b64>".format(progname))
return 1 return 1
name, ccn, keypath = argv[1:] name, ccn, keypath = argv[1:]
userkey = generate_key(name, ccn) userkey = generate_key(name, ccn)
@ -249,38 +246,38 @@ def gui_main():
class DecryptionDialog(Tkinter.Frame): class DecryptionDialog(Tkinter.Frame):
def __init__(self, root): def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5) Tkinter.Frame.__init__(self, root, border=5)
self.status = Tkinter.Label(self, text=u"Enter parameters") self.status = Tkinter.Label(self, text="Enter parameters")
self.status.pack(fill=Tkconstants.X, expand=1) self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self) body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1) body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2) body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text=u"Account Name").grid(row=0) Tkinter.Label(body, text="Account Name").grid(row=0)
self.name = Tkinter.Entry(body, width=40) self.name = Tkinter.Entry(body, width=40)
self.name.grid(row=0, column=1, sticky=sticky) self.name.grid(row=0, column=1, sticky=sticky)
Tkinter.Label(body, text=u"CC#").grid(row=1) Tkinter.Label(body, text="CC#").grid(row=1)
self.ccn = Tkinter.Entry(body, width=40) self.ccn = Tkinter.Entry(body, width=40)
self.ccn.grid(row=1, column=1, sticky=sticky) self.ccn.grid(row=1, column=1, sticky=sticky)
Tkinter.Label(body, text=u"Output file").grid(row=2) Tkinter.Label(body, text="Output file").grid(row=2)
self.keypath = Tkinter.Entry(body, width=40) self.keypath = Tkinter.Entry(body, width=40)
self.keypath.grid(row=2, column=1, sticky=sticky) self.keypath.grid(row=2, column=1, sticky=sticky)
self.keypath.insert(2, u"bnepubkey.b64") self.keypath.insert(2, "bnepubkey.b64")
button = Tkinter.Button(body, text=u"...", command=self.get_keypath) button = Tkinter.Button(body, text="...", command=self.get_keypath)
button.grid(row=2, column=2) button.grid(row=2, column=2)
buttons = Tkinter.Frame(self) buttons = Tkinter.Frame(self)
buttons.pack() buttons.pack()
botton = Tkinter.Button( botton = Tkinter.Button(
buttons, text=u"Generate", width=10, command=self.generate) buttons, text="Generate", width=10, command=self.generate)
botton.pack(side=Tkconstants.LEFT) botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button = Tkinter.Button( button = Tkinter.Button(
buttons, text=u"Quit", width=10, command=self.quit) buttons, text="Quit", width=10, command=self.quit)
button.pack(side=Tkconstants.RIGHT) button.pack(side=Tkconstants.RIGHT)
def get_keypath(self): def get_keypath(self):
keypath = tkFileDialog.asksaveasfilename( keypath = tkFileDialog.asksaveasfilename(
parent=None, title=u"Select B&N ePub key file to produce", parent=None, title="Select B&N ePub key file to produce",
defaultextension=u".b64", defaultextension=".b64",
filetypes=[('base64-encoded files', '.b64'), filetypes=[('base64-encoded files', '.b64'),
('All Files', '.*')]) ('All Files', '.*')])
if keypath: if keypath:
@ -294,22 +291,22 @@ def gui_main():
ccn = self.ccn.get() ccn = self.ccn.get()
keypath = self.keypath.get() keypath = self.keypath.get()
if not name: if not name:
self.status['text'] = u"Name not specified" self.status['text'] = "Name not specified"
return return
if not ccn: if not ccn:
self.status['text'] = u"Credit card number not specified" self.status['text'] = "Credit card number not specified"
return return
if not keypath: if not keypath:
self.status['text'] = u"Output keyfile path not specified" self.status['text'] = "Output keyfile path not specified"
return return
self.status['text'] = u"Generating..." self.status['text'] = "Generating..."
try: try:
userkey = generate_key(name, ccn) userkey = generate_key(name, ccn)
except Exception as e: except Exception as e:
self.status['text'] = u"Error: (0}".format(e.args[0]) self.status['text'] = "Error: (0}".format(e.args[0])
return return
open(keypath,'wb').write(userkey) open(keypath,'wb').write(userkey)
self.status['text'] = u"Keyfile successfully generated" self.status['text'] = "Keyfile successfully generated"
root = Tkinter.Tk() root = Tkinter.Tk()
if AES is None: if AES is None:
@ -319,7 +316,7 @@ def gui_main():
"This script requires OpenSSL or PyCrypto, which must be installed " "This script requires OpenSSL or PyCrypto, which must be installed "
"separately. Read the top-of-script comment for details.") "separately. Read the top-of-script comment for details.")
return 1 return 1
root.title(u"Barnes & Noble ePub Keyfile Generator v.{0}".format(__version__)) root.title("Barnes & Noble ePub Keyfile Generator v.{0}".format(__version__))
root.resizable(True, False) root.resizable(True, False)
root.minsize(300, 0) root.minsize(300, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)

@ -1,7 +1,6 @@
#! /usr/bin/python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement
# ignoblepdf.py # ignoblepdf.py
# Copyright © 2009-2020 by Apprentice Harper et al. # Copyright © 2009-2020 by Apprentice Harper et al.
@ -14,6 +13,7 @@ from __future__ import with_statement
# Revision history: # Revision history:
# 0.1 - Initial alpha testing release 2020 by Pu D. Pud # 0.1 - Initial alpha testing release 2020 by Pu D. Pud
# 0.2 - Python 3 for calibre 5.0 (in testing)
""" """
@ -82,7 +82,7 @@ def unicode_argv():
start = argc.value - len(sys.argv) start = argc.value - len(sys.argv)
return [argv[i] for i in return [argv[i] for i in
xrange(start, argc.value)] xrange(start, argc.value)]
return [u"ignoblepdf.py"] return ["ignoblepdf.py"]
else: else:
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
@ -2005,12 +2005,12 @@ class PDFSerializer(object):
def decryptBook(userkey, inpath, outpath): def decryptBook(userkey, inpath, outpath):
if AES is None: if AES is None:
raise IGNOBLEError(u"PyCrypto or OpenSSL must be installed.") raise IGNOBLEError("PyCrypto or OpenSSL must be installed.")
with open(inpath, 'rb') as inf: with open(inpath, 'rb') as inf:
#try: #try:
serializer = PDFSerializer(inf, userkey) serializer = PDFSerializer(inf, userkey)
#except: #except:
# print u"Error serializing pdf {0}. Probably wrong key.".format(os.path.basename(inpath)) # print "Error serializing pdf {0}. Probably wrong key.".format(os.path.basename(inpath))
# return 2 # return 2
# hope this will fix the 'bad file descriptor' problem # hope this will fix the 'bad file descriptor' problem
with open(outpath, 'wb') as outf: with open(outpath, 'wb') as outf:
@ -2018,7 +2018,7 @@ def decryptBook(userkey, inpath, outpath):
try: try:
serializer.dump(outf) serializer.dump(outf)
except Exception, e: except Exception, e:
print u"error writing pdf: {0}".format(e.args[0]) print "error writing pdf: {0}".format(e.args[0])
return 2 return 2
return 0 return 0
@ -2029,13 +2029,13 @@ def cli_main():
argv=unicode_argv() argv=unicode_argv()
progname = os.path.basename(argv[0]) progname = os.path.basename(argv[0])
if len(argv) != 4: if len(argv) != 4:
print u"usage: {0} <keyfile.b64> <inbook.pdf> <outbook.pdf>".format(progname) print "usage: {0} <keyfile.b64> <inbook.pdf> <outbook.pdf>".format(progname)
return 1 return 1
keypath, inpath, outpath = argv[1:] keypath, inpath, outpath = argv[1:]
userkey = open(keypath,'rb').read() userkey = open(keypath,'rb').read()
result = decryptBook(userkey, inpath, outpath) result = decryptBook(userkey, inpath, outpath)
if result == 0: if result == 0:
print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)) print "Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath))
return result return result
@ -2052,43 +2052,43 @@ def gui_main():
class DecryptionDialog(Tkinter.Frame): class DecryptionDialog(Tkinter.Frame):
def __init__(self, root): def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5) Tkinter.Frame.__init__(self, root, border=5)
self.status = Tkinter.Label(self, text=u"Select files for decryption") self.status = Tkinter.Label(self, text="Select files for decryption")
self.status.pack(fill=Tkconstants.X, expand=1) self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self) body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1) body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2) body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text=u"Key file").grid(row=0) Tkinter.Label(body, text="Key file").grid(row=0)
self.keypath = Tkinter.Entry(body, width=30) self.keypath = Tkinter.Entry(body, width=30)
self.keypath.grid(row=0, column=1, sticky=sticky) self.keypath.grid(row=0, column=1, sticky=sticky)
if os.path.exists(u"bnpdfkey.b64"): if os.path.exists("bnpdfkey.b64"):
self.keypath.insert(0, u"bnpdfkey.b64") self.keypath.insert(0, "bnpdfkey.b64")
button = Tkinter.Button(body, text=u"...", command=self.get_keypath) button = Tkinter.Button(body, text="...", command=self.get_keypath)
button.grid(row=0, column=2) button.grid(row=0, column=2)
Tkinter.Label(body, text=u"Input file").grid(row=1) Tkinter.Label(body, text="Input file").grid(row=1)
self.inpath = Tkinter.Entry(body, width=30) self.inpath = Tkinter.Entry(body, width=30)
self.inpath.grid(row=1, column=1, sticky=sticky) self.inpath.grid(row=1, column=1, sticky=sticky)
button = Tkinter.Button(body, text=u"...", command=self.get_inpath) button = Tkinter.Button(body, text="...", command=self.get_inpath)
button.grid(row=1, column=2) button.grid(row=1, column=2)
Tkinter.Label(body, text=u"Output file").grid(row=2) Tkinter.Label(body, text="Output file").grid(row=2)
self.outpath = Tkinter.Entry(body, width=30) self.outpath = Tkinter.Entry(body, width=30)
self.outpath.grid(row=2, column=1, sticky=sticky) self.outpath.grid(row=2, column=1, sticky=sticky)
button = Tkinter.Button(body, text=u"...", command=self.get_outpath) button = Tkinter.Button(body, text="...", command=self.get_outpath)
button.grid(row=2, column=2) button.grid(row=2, column=2)
buttons = Tkinter.Frame(self) buttons = Tkinter.Frame(self)
buttons.pack() buttons.pack()
botton = Tkinter.Button( botton = Tkinter.Button(
buttons, text=u"Decrypt", width=10, command=self.decrypt) buttons, text="Decrypt", width=10, command=self.decrypt)
botton.pack(side=Tkconstants.LEFT) botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button = Tkinter.Button( button = Tkinter.Button(
buttons, text=u"Quit", width=10, command=self.quit) buttons, text="Quit", width=10, command=self.quit)
button.pack(side=Tkconstants.RIGHT) button.pack(side=Tkconstants.RIGHT)
def get_keypath(self): def get_keypath(self):
keypath = tkFileDialog.askopenfilename( keypath = tkFileDialog.askopenfilename(
parent=None, title=u"Select Barnes & Noble \'.b64\' key file", parent=None, title="Select Barnes & Noble \'.b64\' key file",
defaultextension=u".b64", defaultextension=".b64",
filetypes=[('base64-encoded files', '.b64'), filetypes=[('base64-encoded files', '.b64'),
('All Files', '.*')]) ('All Files', '.*')])
if keypath: if keypath:
@ -2099,8 +2099,8 @@ def gui_main():
def get_inpath(self): def get_inpath(self):
inpath = tkFileDialog.askopenfilename( inpath = tkFileDialog.askopenfilename(
parent=None, title=u"Select B&N-encrypted PDF file to decrypt", parent=None, title="Select B&N-encrypted PDF file to decrypt",
defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')]) defaultextension=".pdf", filetypes=[('PDF files', '.pdf')])
if inpath: if inpath:
inpath = os.path.normpath(inpath) inpath = os.path.normpath(inpath)
self.inpath.delete(0, Tkconstants.END) self.inpath.delete(0, Tkconstants.END)
@ -2109,8 +2109,8 @@ def gui_main():
def get_outpath(self): def get_outpath(self):
outpath = tkFileDialog.asksaveasfilename( outpath = tkFileDialog.asksaveasfilename(
parent=None, title=u"Select unencrypted PDF file to produce", parent=None, title="Select unencrypted PDF file to produce",
defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')]) defaultextension=".pdf", filetypes=[('PDF files', '.pdf')])
if outpath: if outpath:
outpath = os.path.normpath(outpath) outpath = os.path.normpath(outpath)
self.outpath.delete(0, Tkconstants.END) self.outpath.delete(0, Tkconstants.END)
@ -2122,28 +2122,28 @@ def gui_main():
inpath = self.inpath.get() inpath = self.inpath.get()
outpath = self.outpath.get() outpath = self.outpath.get()
if not keypath or not os.path.exists(keypath): if not keypath or not os.path.exists(keypath):
self.status['text'] = u"Specified key file does not exist" self.status['text'] = "Specified key file does not exist"
return return
if not inpath or not os.path.exists(inpath): if not inpath or not os.path.exists(inpath):
self.status['text'] = u"Specified input file does not exist" self.status['text'] = "Specified input file does not exist"
return return
if not outpath: if not outpath:
self.status['text'] = u"Output file not specified" self.status['text'] = "Output file not specified"
return return
if inpath == outpath: if inpath == outpath:
self.status['text'] = u"Must have different input and output files" self.status['text'] = "Must have different input and output files"
return return
userkey = open(keypath,'rb').read() userkey = open(keypath,'rb').read()
self.status['text'] = u"Decrypting..." self.status['text'] = "Decrypting..."
try: try:
decrypt_status = decryptBook(userkey, inpath, outpath) decrypt_status = decryptBook(userkey, inpath, outpath)
except Exception, e: except Exception, e:
self.status['text'] = u"Error; {0}".format(e.args[0]) self.status['text'] = "Error; {0}".format(e.args[0])
return return
if decrypt_status == 0: if decrypt_status == 0:
self.status['text'] = u"File successfully decrypted" self.status['text'] = "File successfully decrypted"
else: else:
self.status['text'] = u"The was an error decrypting the file." self.status['text'] = "The was an error decrypting the file."
root = Tkinter.Tk() root = Tkinter.Tk()
@ -2154,7 +2154,7 @@ def gui_main():
"This script requires OpenSSL or PyCrypto, which must be installed " "This script requires OpenSSL or PyCrypto, which must be installed "
"separately. Read the top-of-script comment for details.") "separately. Read the top-of-script comment for details.")
return 1 return 1
root.title(u"Barnes & Noble PDF Decrypter v.{0}".format(__version__)) root.title("Barnes & Noble PDF Decrypter v.{0}".format(__version__))
root.resizable(True, False) root.resizable(True, False)
root.minsize(370, 0) root.minsize(370, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)

@ -1,9 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement # ineptepub.py
# ineptepub.pyw
# Copyright © 2009-2020 by i♥cabbages, Apprentice Harper et al. # Copyright © 2009-2020 by i♥cabbages, Apprentice Harper et al.
# Released under the terms of the GNU General Public Licence, version 3 # Released under the terms of the GNU General Public Licence, version 3
@ -102,7 +100,7 @@ def unicode_argv():
start = argc.value - len(sys.argv) start = argc.value - len(sys.argv)
return [argv[i] for i in return [argv[i] for i in
range(start, argc.value)] range(start, argc.value)]
return [u"ineptepub.py"] return ["ineptepub.py"]
else: else:
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
@ -393,13 +391,13 @@ def adeptBook(inpath):
def decryptBook(userkey, inpath, outpath): def decryptBook(userkey, inpath, outpath):
if AES is None: if AES is None:
raise ADEPTError(u"PyCrypto or OpenSSL must be installed.") raise ADEPTError("PyCrypto or OpenSSL must be installed.")
rsa = RSA(userkey) rsa = RSA(userkey)
with closing(ZipFile(open(inpath, 'rb'))) as inf: with closing(ZipFile(open(inpath, 'rb'))) as inf:
namelist = set(inf.namelist()) namelist = set(inf.namelist())
if 'META-INF/rights.xml' not in namelist or \ if 'META-INF/rights.xml' not in namelist or \
'META-INF/encryption.xml' not in namelist: 'META-INF/encryption.xml' not in namelist:
print(u"{0:s} is DRM-free.".format(os.path.basename(inpath))) print("{0:s} is DRM-free.".format(os.path.basename(inpath)))
return 1 return 1
for name in META_NAMES: for name in META_NAMES:
namelist.remove(name) namelist.remove(name)
@ -409,12 +407,12 @@ def decryptBook(userkey, inpath, outpath):
expr = './/%s' % (adept('encryptedKey'),) expr = './/%s' % (adept('encryptedKey'),)
bookkey = ''.join(rights.findtext(expr)) bookkey = ''.join(rights.findtext(expr))
if len(bookkey) != 172: if len(bookkey) != 172:
print(u"{0:s} is not a secure Adobe Adept ePub.".format(os.path.basename(inpath))) print("{0:s} is not a secure Adobe Adept ePub.".format(os.path.basename(inpath)))
return 1 return 1
bookkey = rsa.decrypt(codecs.decode(bookkey.encode('ascii'), 'base64')) bookkey = rsa.decrypt(codecs.decode(bookkey.encode('ascii'), 'base64'))
# Padded as per RSAES-PKCS1-v1_5 # Padded as per RSAES-PKCS1-v1_5
if bookkey[-17] != '\x00' and bookkey[-17] != 0: if bookkey[-17] != '\x00' and bookkey[-17] != 0:
print(u"Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath))) print("Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath)))
return 2 return 2
encryption = inf.read('META-INF/encryption.xml') encryption = inf.read('META-INF/encryption.xml')
decryptor = Decryptor(bookkey[-16:], encryption) decryptor = Decryptor(bookkey[-16:], encryption)
@ -455,7 +453,7 @@ def decryptBook(userkey, inpath, outpath):
pass pass
outf.writestr(zi, decryptor.decrypt(path, data)) outf.writestr(zi, decryptor.decrypt(path, data))
except: except:
print(u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc())) print("Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc()))
return 2 return 2
return 0 return 0
@ -466,13 +464,13 @@ def cli_main():
argv=unicode_argv() argv=unicode_argv()
progname = os.path.basename(argv[0]) progname = os.path.basename(argv[0])
if len(argv) != 4: if len(argv) != 4:
print(u"usage: {0} <keyfile.der> <inbook.epub> <outbook.epub>".format(progname)) print("usage: {0} <keyfile.der> <inbook.epub> <outbook.epub>".format(progname))
return 1 return 1
keypath, inpath, outpath = argv[1:] keypath, inpath, outpath = argv[1:]
userkey = open(keypath,'rb').read() userkey = open(keypath,'rb').read()
result = decryptBook(userkey, inpath, outpath) result = decryptBook(userkey, inpath, outpath)
if result == 0: if result == 0:
print(u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath))) print("Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)))
return result return result
def gui_main(): def gui_main():
@ -488,43 +486,43 @@ def gui_main():
class DecryptionDialog(Tkinter.Frame): class DecryptionDialog(Tkinter.Frame):
def __init__(self, root): def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5) Tkinter.Frame.__init__(self, root, border=5)
self.status = Tkinter.Label(self, text=u"Select files for decryption") self.status = Tkinter.Label(self, text="Select files for decryption")
self.status.pack(fill=Tkconstants.X, expand=1) self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self) body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1) body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2) body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text=u"Key file").grid(row=0) Tkinter.Label(body, text="Key file").grid(row=0)
self.keypath = Tkinter.Entry(body, width=30) self.keypath = Tkinter.Entry(body, width=30)
self.keypath.grid(row=0, column=1, sticky=sticky) self.keypath.grid(row=0, column=1, sticky=sticky)
if os.path.exists(u"adeptkey.der"): if os.path.exists("adeptkey.der"):
self.keypath.insert(0, u"adeptkey.der") self.keypath.insert(0, "adeptkey.der")
button = Tkinter.Button(body, text=u"...", command=self.get_keypath) button = Tkinter.Button(body, text="...", command=self.get_keypath)
button.grid(row=0, column=2) button.grid(row=0, column=2)
Tkinter.Label(body, text=u"Input file").grid(row=1) Tkinter.Label(body, text="Input file").grid(row=1)
self.inpath = Tkinter.Entry(body, width=30) self.inpath = Tkinter.Entry(body, width=30)
self.inpath.grid(row=1, column=1, sticky=sticky) self.inpath.grid(row=1, column=1, sticky=sticky)
button = Tkinter.Button(body, text=u"...", command=self.get_inpath) button = Tkinter.Button(body, text="...", command=self.get_inpath)
button.grid(row=1, column=2) button.grid(row=1, column=2)
Tkinter.Label(body, text=u"Output file").grid(row=2) Tkinter.Label(body, text="Output file").grid(row=2)
self.outpath = Tkinter.Entry(body, width=30) self.outpath = Tkinter.Entry(body, width=30)
self.outpath.grid(row=2, column=1, sticky=sticky) self.outpath.grid(row=2, column=1, sticky=sticky)
button = Tkinter.Button(body, text=u"...", command=self.get_outpath) button = Tkinter.Button(body, text="...", command=self.get_outpath)
button.grid(row=2, column=2) button.grid(row=2, column=2)
buttons = Tkinter.Frame(self) buttons = Tkinter.Frame(self)
buttons.pack() buttons.pack()
botton = Tkinter.Button( botton = Tkinter.Button(
buttons, text=u"Decrypt", width=10, command=self.decrypt) buttons, text="Decrypt", width=10, command=self.decrypt)
botton.pack(side=Tkconstants.LEFT) botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button = Tkinter.Button( button = Tkinter.Button(
buttons, text=u"Quit", width=10, command=self.quit) buttons, text="Quit", width=10, command=self.quit)
button.pack(side=Tkconstants.RIGHT) button.pack(side=Tkconstants.RIGHT)
def get_keypath(self): def get_keypath(self):
keypath = tkFileDialog.askopenfilename( keypath = tkFileDialog.askopenfilename(
parent=None, title=u"Select Adobe Adept \'.der\' key file", parent=None, title="Select Adobe Adept \'.der\' key file",
defaultextension=u".der", defaultextension=".der",
filetypes=[('Adobe Adept DER-encoded files', '.der'), filetypes=[('Adobe Adept DER-encoded files', '.der'),
('All Files', '.*')]) ('All Files', '.*')])
if keypath: if keypath:
@ -535,8 +533,8 @@ def gui_main():
def get_inpath(self): def get_inpath(self):
inpath = tkFileDialog.askopenfilename( inpath = tkFileDialog.askopenfilename(
parent=None, title=u"Select ADEPT-encrypted ePub file to decrypt", parent=None, title="Select ADEPT-encrypted ePub file to decrypt",
defaultextension=u".epub", filetypes=[('ePub files', '.epub')]) defaultextension=".epub", filetypes=[('ePub files', '.epub')])
if inpath: if inpath:
inpath = os.path.normpath(inpath) inpath = os.path.normpath(inpath)
self.inpath.delete(0, Tkconstants.END) self.inpath.delete(0, Tkconstants.END)
@ -545,8 +543,8 @@ def gui_main():
def get_outpath(self): def get_outpath(self):
outpath = tkFileDialog.asksaveasfilename( outpath = tkFileDialog.asksaveasfilename(
parent=None, title=u"Select unencrypted ePub file to produce", parent=None, title="Select unencrypted ePub file to produce",
defaultextension=u".epub", filetypes=[('ePub files', '.epub')]) defaultextension=".epub", filetypes=[('ePub files', '.epub')])
if outpath: if outpath:
outpath = os.path.normpath(outpath) outpath = os.path.normpath(outpath)
self.outpath.delete(0, Tkconstants.END) self.outpath.delete(0, Tkconstants.END)
@ -558,31 +556,31 @@ def gui_main():
inpath = self.inpath.get() inpath = self.inpath.get()
outpath = self.outpath.get() outpath = self.outpath.get()
if not keypath or not os.path.exists(keypath): if not keypath or not os.path.exists(keypath):
self.status['text'] = u"Specified key file does not exist" self.status['text'] = "Specified key file does not exist"
return return
if not inpath or not os.path.exists(inpath): if not inpath or not os.path.exists(inpath):
self.status['text'] = u"Specified input file does not exist" self.status['text'] = "Specified input file does not exist"
return return
if not outpath: if not outpath:
self.status['text'] = u"Output file not specified" self.status['text'] = "Output file not specified"
return return
if inpath == outpath: if inpath == outpath:
self.status['text'] = u"Must have different input and output files" self.status['text'] = "Must have different input and output files"
return return
userkey = open(keypath,'rb').read() userkey = open(keypath,'rb').read()
self.status['text'] = u"Decrypting..." self.status['text'] = "Decrypting..."
try: try:
decrypt_status = decryptBook(userkey, inpath, outpath) decrypt_status = decryptBook(userkey, inpath, outpath)
except Exception as e: except Exception as e:
self.status['text'] = u"Error: {0}".format(e.args[0]) self.status['text'] = "Error: {0}".format(e.args[0])
return return
if decrypt_status == 0: if decrypt_status == 0:
self.status['text'] = u"File successfully decrypted" self.status['text'] = "File successfully decrypted"
else: else:
self.status['text'] = u"The was an error decrypting the file." self.status['text'] = "The was an error decrypting the file."
root = Tkinter.Tk() root = Tkinter.Tk()
root.title(u"Adobe Adept ePub Decrypter v.{0}".format(__version__)) root.title("Adobe Adept ePub Decrypter v.{0}".format(__version__))
root.resizable(True, False) root.resizable(True, False)
root.minsize(300, 0) root.minsize(300, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)

@ -1,8 +1,6 @@
#! /usr/bin/python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement
# ineptpdf.py # ineptpdf.py
# Copyright © 2009-2020 by i♥cabbages, Apprentice Harper et al. # Copyright © 2009-2020 by i♥cabbages, Apprentice Harper et al.
@ -115,7 +113,7 @@ def unicode_argv():
start = argc.value - len(sys.argv) start = argc.value - len(sys.argv)
return [argv[i] for i in return [argv[i] for i in
range(start, argc.value)] range(start, argc.value)]
return [u"ineptpdf.py"] return ["ineptpdf.py"]
else: else:
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding is None: if argvencoding is None:
@ -2178,12 +2176,12 @@ class PDFSerializer(object):
def decryptBook(userkey, inpath, outpath): def decryptBook(userkey, inpath, outpath):
if RSA is None: if RSA is None:
raise ADEPTError(u"PyCrypto or OpenSSL must be installed.") raise ADEPTError("PyCrypto or OpenSSL must be installed.")
with open(inpath, 'rb') as inf: with open(inpath, 'rb') as inf:
#try: #try:
serializer = PDFSerializer(inf, userkey) serializer = PDFSerializer(inf, userkey)
#except: #except:
# print u"Error serializing pdf {0}. Probably wrong key.".format(os.path.basename(inpath)) # print "Error serializing pdf {0}. Probably wrong key.".format(os.path.basename(inpath))
# return 2 # return 2
# hope this will fix the 'bad file descriptor' problem # hope this will fix the 'bad file descriptor' problem
with open(outpath, 'wb') as outf: with open(outpath, 'wb') as outf:
@ -2191,7 +2189,7 @@ def decryptBook(userkey, inpath, outpath):
try: try:
serializer.dump(outf) serializer.dump(outf)
except Exception as e: except Exception as e:
print(u"error writing pdf: {0}".format(e.args[0])) print("error writing pdf: {0}".format(e.args[0]))
return 2 return 2
return 0 return 0
@ -2202,13 +2200,13 @@ def cli_main():
argv=unicode_argv() argv=unicode_argv()
progname = os.path.basename(argv[0]) progname = os.path.basename(argv[0])
if len(argv) != 4: if len(argv) != 4:
print(u"usage: {0} <keyfile.der> <inbook.pdf> <outbook.pdf>".format(progname)) print("usage: {0} <keyfile.der> <inbook.pdf> <outbook.pdf>".format(progname))
return 1 return 1
keypath, inpath, outpath = argv[1:] keypath, inpath, outpath = argv[1:]
userkey = open(keypath,'rb').read() userkey = open(keypath,'rb').read()
result = decryptBook(userkey, inpath, outpath) result = decryptBook(userkey, inpath, outpath)
if result == 0: if result == 0:
print(u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath))) print("Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)))
return result return result
@ -2225,43 +2223,43 @@ def gui_main():
class DecryptionDialog(Tkinter.Frame): class DecryptionDialog(Tkinter.Frame):
def __init__(self, root): def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5) Tkinter.Frame.__init__(self, root, border=5)
self.status = Tkinter.Label(self, text=u"Select files for decryption") self.status = Tkinter.Label(self, text="Select files for decryption")
self.status.pack(fill=Tkconstants.X, expand=1) self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self) body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1) body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2) body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text=u"Key file").grid(row=0) Tkinter.Label(body, text="Key file").grid(row=0)
self.keypath = Tkinter.Entry(body, width=30) self.keypath = Tkinter.Entry(body, width=30)
self.keypath.grid(row=0, column=1, sticky=sticky) self.keypath.grid(row=0, column=1, sticky=sticky)
if os.path.exists(u"adeptkey.der"): if os.path.exists("adeptkey.der"):
self.keypath.insert(0, u"adeptkey.der") self.keypath.insert(0, "adeptkey.der")
button = Tkinter.Button(body, text=u"...", command=self.get_keypath) button = Tkinter.Button(body, text="...", command=self.get_keypath)
button.grid(row=0, column=2) button.grid(row=0, column=2)
Tkinter.Label(body, text=u"Input file").grid(row=1) Tkinter.Label(body, text="Input file").grid(row=1)
self.inpath = Tkinter.Entry(body, width=30) self.inpath = Tkinter.Entry(body, width=30)
self.inpath.grid(row=1, column=1, sticky=sticky) self.inpath.grid(row=1, column=1, sticky=sticky)
button = Tkinter.Button(body, text=u"...", command=self.get_inpath) button = Tkinter.Button(body, text="...", command=self.get_inpath)
button.grid(row=1, column=2) button.grid(row=1, column=2)
Tkinter.Label(body, text=u"Output file").grid(row=2) Tkinter.Label(body, text="Output file").grid(row=2)
self.outpath = Tkinter.Entry(body, width=30) self.outpath = Tkinter.Entry(body, width=30)
self.outpath.grid(row=2, column=1, sticky=sticky) self.outpath.grid(row=2, column=1, sticky=sticky)
button = Tkinter.Button(body, text=u"...", command=self.get_outpath) button = Tkinter.Button(body, text="...", command=self.get_outpath)
button.grid(row=2, column=2) button.grid(row=2, column=2)
buttons = Tkinter.Frame(self) buttons = Tkinter.Frame(self)
buttons.pack() buttons.pack()
botton = Tkinter.Button( botton = Tkinter.Button(
buttons, text=u"Decrypt", width=10, command=self.decrypt) buttons, text="Decrypt", width=10, command=self.decrypt)
botton.pack(side=Tkconstants.LEFT) botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button = Tkinter.Button( button = Tkinter.Button(
buttons, text=u"Quit", width=10, command=self.quit) buttons, text="Quit", width=10, command=self.quit)
button.pack(side=Tkconstants.RIGHT) button.pack(side=Tkconstants.RIGHT)
def get_keypath(self): def get_keypath(self):
keypath = tkFileDialog.askopenfilename( keypath = tkFileDialog.askopenfilename(
parent=None, title=u"Select Adobe Adept \'.der\' key file", parent=None, title="Select Adobe Adept \'.der\' key file",
defaultextension=u".der", defaultextension=".der",
filetypes=[('Adobe Adept DER-encoded files', '.der'), filetypes=[('Adobe Adept DER-encoded files', '.der'),
('All Files', '.*')]) ('All Files', '.*')])
if keypath: if keypath:
@ -2272,8 +2270,8 @@ def gui_main():
def get_inpath(self): def get_inpath(self):
inpath = tkFileDialog.askopenfilename( inpath = tkFileDialog.askopenfilename(
parent=None, title=u"Select ADEPT-encrypted PDF file to decrypt", parent=None, title="Select ADEPT-encrypted PDF file to decrypt",
defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')]) defaultextension=".pdf", filetypes=[('PDF files', '.pdf')])
if inpath: if inpath:
inpath = os.path.normpath(inpath) inpath = os.path.normpath(inpath)
self.inpath.delete(0, Tkconstants.END) self.inpath.delete(0, Tkconstants.END)
@ -2282,8 +2280,8 @@ def gui_main():
def get_outpath(self): def get_outpath(self):
outpath = tkFileDialog.asksaveasfilename( outpath = tkFileDialog.asksaveasfilename(
parent=None, title=u"Select unencrypted PDF file to produce", parent=None, title="Select unencrypted PDF file to produce",
defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')]) defaultextension=".pdf", filetypes=[('PDF files', '.pdf')])
if outpath: if outpath:
outpath = os.path.normpath(outpath) outpath = os.path.normpath(outpath)
self.outpath.delete(0, Tkconstants.END) self.outpath.delete(0, Tkconstants.END)
@ -2295,28 +2293,28 @@ def gui_main():
inpath = self.inpath.get() inpath = self.inpath.get()
outpath = self.outpath.get() outpath = self.outpath.get()
if not keypath or not os.path.exists(keypath): if not keypath or not os.path.exists(keypath):
self.status['text'] = u"Specified key file does not exist" self.status['text'] = "Specified key file does not exist"
return return
if not inpath or not os.path.exists(inpath): if not inpath or not os.path.exists(inpath):
self.status['text'] = u"Specified input file does not exist" self.status['text'] = "Specified input file does not exist"
return return
if not outpath: if not outpath:
self.status['text'] = u"Output file not specified" self.status['text'] = "Output file not specified"
return return
if inpath == outpath: if inpath == outpath:
self.status['text'] = u"Must have different input and output files" self.status['text'] = "Must have different input and output files"
return return
userkey = open(keypath,'rb').read() userkey = open(keypath,'rb').read()
self.status['text'] = u"Decrypting..." self.status['text'] = "Decrypting..."
try: try:
decrypt_status = decryptBook(userkey, inpath, outpath) decrypt_status = decryptBook(userkey, inpath, outpath)
except Exception as e: except Exception as e:
self.status['text'] = u"Error; {0}".format(e.args[0]) self.status['text'] = "Error; {0}".format(e.args[0])
return return
if decrypt_status == 0: if decrypt_status == 0:
self.status['text'] = u"File successfully decrypted" self.status['text'] = "File successfully decrypted"
else: else:
self.status['text'] = u"The was an error decrypting the file." self.status['text'] = "The was an error decrypting the file."
root = Tkinter.Tk() root = Tkinter.Tk()
@ -2327,7 +2325,7 @@ def gui_main():
"This script requires OpenSSL or PyCrypto, which must be installed " "This script requires OpenSSL or PyCrypto, which must be installed "
"separately. Read the top-of-script comment for details.") "separately. Read the top-of-script comment for details.")
return 1 return 1
root.title(u"Adobe Adept PDF Decrypter v.{0}".format(__version__)) root.title("Adobe Adept PDF Decrypter v.{0}".format(__version__))
root.resizable(True, False) root.resizable(True, False)
root.minsize(370, 0) root.minsize(370, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)

@ -1,8 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement
# ion.py # ion.py
# Copyright © 2013-2020 Apprentice Harper et al. # Copyright © 2013-2020 Apprentice Harper et al.

@ -1,8 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement
# k4mobidedrm.py # k4mobidedrm.py
# Copyright © 2008-2020 by Apprentice Harper et al. # Copyright © 2008-2020 by Apprentice Harper et al.
@ -146,7 +144,7 @@ def unicode_argv():
range(start, argc.value)] range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name # if we don't have any arguments at all, just pass back script name
# this should never happen # this should never happen
return [u"mobidedrm.py"] return ["mobidedrm.py"]
else: else:
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
@ -161,31 +159,31 @@ def unicode_argv():
# and some improvements suggested by jhaisley # and some improvements suggested by jhaisley
def cleanup_name(name): def cleanup_name(name):
# substitute filename unfriendly characters # substitute filename unfriendly characters
name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" ").replace(u": ",u" ").replace(u":",u"").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'").replace(u"*",u"_").replace(u"?",u"") name = name.replace("<","[").replace(">","]").replace(" : "," ").replace(": "," ").replace(":","").replace("/","_").replace("\\","_").replace("|","_").replace("\"","\'").replace("*","_").replace("?",u"")
# white space to single space, delete leading and trailing while space # white space to single space, delete leading and trailing while space
name = re.sub(r"\s", u" ", name).strip() name = re.sub(r"\s", " ", name).strip()
# delete control characters # delete control characters
name = u"".join(char for char in name if ord(char)>=32) name = u"".join(char for char in name if ord(char)>=32)
# delete non-ascii characters # delete non-ascii characters
name = u"".join(char for char in name if ord(char)<=126) name = u"".join(char for char in name if ord(char)<=126)
# remove leading dots # remove leading dots
while len(name)>0 and name[0] == u".": while len(name)>0 and name[0] == ".":
name = name[1:] name = name[1:]
# remove trailing dots (Windows doesn't like them) # remove trailing dots (Windows doesn't like them)
while name.endswith(u'.'): while name.endswith("."):
name = name[:-1] name = name[:-1]
if len(name)==0: if len(name)==0:
name=u"DecryptedBook" name="DecryptedBook"
return name return name
# must be passed unicode # must be passed unicode
def unescape(text): def unescape(text):
def fixup(m): def fixup(m):
text = m.group(0) text = m.group(0)
if text[:2] == u"&#": if text[:2] == "&#":
# character reference # character reference
try: try:
if text[:3] == u"&#x": if text[:3] == "&#x":
return chr(int(text[3:-1], 16)) return chr(int(text[3:-1], 16))
else: else:
return chr(int(text[2:-1])) return chr(int(text[2:-1]))
@ -198,17 +196,17 @@ def unescape(text):
except KeyError: except KeyError:
pass pass
return text # leave as is return text # leave as is
return re.sub(u"&#?\\w+;", fixup, text) return re.sub("&#?\\w+;", fixup, text)
def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime = time.time()): def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime = time.time()):
# handle the obvious cases at the beginning # handle the obvious cases at the beginning
if not os.path.isfile(infile): if not os.path.isfile(infile):
raise DrmException(u"Input file does not exist.") raise DrmException("Input file does not exist.")
mobi = True mobi = True
magic8 = open(infile,'rb').read(8) magic8 = open(infile,'rb').read(8)
if magic8 == '\xeaDRMION\xee': if magic8 == '\xeaDRMION\xee':
raise DrmException(u"The .kfx DRMION file cannot be decrypted by itself. A .kfx-zip archive containing a DRM voucher is required.") raise DrmException("The .kfx DRMION file cannot be decrypted by itself. A .kfx-zip archive containing a DRM voucher is required.")
magic3 = magic8[:3] magic3 = magic8[:3]
if magic3 == 'TPZ': if magic3 == 'TPZ':
@ -222,7 +220,7 @@ def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime
mb = topazextract.TopazBook(infile) mb = topazextract.TopazBook(infile)
bookname = unescape(mb.getBookTitle()) bookname = unescape(mb.getBookTitle())
print(u"Decrypting {1} ebook: {0}".format(bookname, mb.getBookType())) print("Decrypting {1} ebook: {0}".format(bookname, mb.getBookType()))
# copy list of pids # copy list of pids
totalpids = list(pids) totalpids = list(pids)
@ -234,7 +232,7 @@ def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime
totalpids.extend(kgenpids.getPidList(md1, md2, serials, kDatabases)) totalpids.extend(kgenpids.getPidList(md1, md2, serials, kDatabases))
# remove any duplicates # remove any duplicates
totalpids = list(set(totalpids)) totalpids = list(set(totalpids))
print(u"Found {1:d} keys to try after {0:.1f} seconds".format(time.time()-starttime, len(totalpids))) print("Found {1:d} keys to try after {0:.1f} seconds".format(time.time()-starttime, len(totalpids)))
#print totalpids #print totalpids
try: try:
@ -243,7 +241,7 @@ def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime
mb.cleanup mb.cleanup
raise raise
print(u"Decryption succeeded after {0:.1f} seconds".format(time.time()-starttime)) print("Decryption succeeded after {0:.1f} seconds".format(time.time()-starttime))
return mb return mb
@ -258,7 +256,7 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
kindleDatabase = json.loads(keyfilein.read()) kindleDatabase = json.loads(keyfilein.read())
kDatabases.append([dbfile,kindleDatabase]) kDatabases.append([dbfile,kindleDatabase])
except Exception as e: except Exception as e:
print(u"Error getting database from file {0:s}: {1:s}".format(dbfile,e)) print("Error getting database from file {0:s}: {1:s}".format(dbfile,e))
traceback.print_exc() traceback.print_exc()
@ -266,7 +264,7 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
try: try:
book = GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime) book = GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime)
except Exception as e: except Exception as e:
print(u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime)) print("Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime))
traceback.print_exc() traceback.print_exc()
return 1 return 1
@ -277,7 +275,7 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
re.match('^{0-9A-F-}{36}$', orig_fn_root) re.match('^{0-9A-F-}{36}$', orig_fn_root)
): # Kindle for PC / Mac / Android / Fire / iOS ): # Kindle for PC / Mac / Android / Fire / iOS
clean_title = cleanup_name(book.getBookTitle()) clean_title = cleanup_name(book.getBookTitle())
outfilename = u'{}_{}'.format(orig_fn_root, clean_title) outfilename = "{}_{}".format(orig_fn_root, clean_title)
else: # E Ink Kindle, which already uses a reasonable name else: # E Ink Kindle, which already uses a reasonable name
outfilename = orig_fn_root outfilename = orig_fn_root
@ -285,16 +283,16 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
if len(outfilename)>150: if len(outfilename)>150:
outfilename = outfilename[:99]+"--"+outfilename[-49:] outfilename = outfilename[:99]+"--"+outfilename[-49:]
outfilename = outfilename+u"_nodrm" outfilename = outfilename+"_nodrm"
outfile = os.path.join(outdir, outfilename + book.getBookExtension()) outfile = os.path.join(outdir, outfilename + book.getBookExtension())
book.getFile(outfile) book.getFile(outfile)
print(u"Saved decrypted book {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename)) print("Saved decrypted book {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename))
if book.getBookType()==u"Topaz": if book.getBookType()=="Topaz":
zipname = os.path.join(outdir, outfilename + u"_SVG.zip") zipname = os.path.join(outdir, outfilename + "_SVG.zip")
book.getSVGZip(zipname) book.getSVGZip(zipname)
print(u"Saved SVG ZIP Archive for {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename)) print("Saved SVG ZIP Archive for {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename))
# remove internal temporary directory of Topaz pieces # remove internal temporary directory of Topaz pieces
book.cleanup() book.cleanup()
@ -302,9 +300,9 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
def usage(progname): def usage(progname):
print(u"Removes DRM protection from Mobipocket, Amazon KF8, Amazon Print Replica and Amazon Topaz ebooks") print("Removes DRM protection from Mobipocket, Amazon KF8, Amazon Print Replica and Amazon Topaz ebooks")
print(u"Usage:") print("Usage:")
print(u" {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] [ -a <AmazonSecureStorage.xml|backup.ab> ] <infile> <outdir>".format(progname)) print(" {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] [ -a <AmazonSecureStorage.xml|backup.ab> ] <infile> <outdir>".format(progname))
# #
# Main # Main
@ -312,12 +310,12 @@ def usage(progname):
def cli_main(): def cli_main():
argv=unicode_argv() argv=unicode_argv()
progname = os.path.basename(argv[0]) progname = os.path.basename(argv[0])
print(u"K4MobiDeDrm v{0}.\nCopyright © 2008-2017 Apprentice Harper et al.".format(__version__)) print("K4MobiDeDrm v{0}.\nCopyright © 2008-2017 Apprentice Harper et al.".format(__version__))
try: try:
opts, args = getopt.getopt(argv[1:], "k:p:s:a:") opts, args = getopt.getopt(argv[1:], "k:p:s:a:")
except getopt.GetoptError as err: except getopt.GetoptError as err:
print(u"Error in options or arguments: {0}".format(err.args[0])) print("Error in options or arguments: {0}".format(err.args[0]))
usage(progname) usage(progname)
sys.exit(2) sys.exit(2)
if len(args)<2: if len(args)<2:

@ -1,12 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement
from __future__ import print_function
# Engine to remove drm from Kindle KFX ebooks # Engine to remove drm from Kindle KFX ebooks
# 2.0 - Added Python 3 compatibility for calibre 5.0 # 2.0 - Python 3 for calibre 5.0
import os import os
@ -50,13 +47,13 @@ class KFXZipBook:
data += fh.read() data += fh.read()
if self.voucher is None: if self.voucher is None:
self.decrypt_voucher(totalpids) self.decrypt_voucher(totalpids)
print(u'Decrypting KFX DRMION: {0}'.format(filename)) print("Decrypting KFX DRMION: {0}".format(filename))
outfile = StringIO() outfile = StringIO()
ion.DrmIon(StringIO(data[8:-8]), lambda name: self.voucher).parse(outfile) ion.DrmIon(StringIO(data[8:-8]), lambda name: self.voucher).parse(outfile)
self.decrypted[filename] = outfile.getvalue() self.decrypted[filename] = outfile.getvalue()
if not self.decrypted: if not self.decrypted:
print(u'The .kfx-zip archive does not contain an encrypted DRMION file') print("The .kfx-zip archive does not contain an encrypted DRMION file")
def decrypt_voucher(self, totalpids): def decrypt_voucher(self, totalpids):
with zipfile.ZipFile(self.infile, 'r') as zf: with zipfile.ZipFile(self.infile, 'r') as zf:
@ -70,9 +67,9 @@ class KFXZipBook:
if 'ProtectedData' in data: if 'ProtectedData' in data:
break # found DRM voucher break # found DRM voucher
else: else:
raise Exception(u'The .kfx-zip archive contains an encrypted DRMION file without a DRM voucher') raise Exception("The .kfx-zip archive contains an encrypted DRMION file without a DRM voucher")
print(u'Decrypting KFX DRM voucher: {0}'.format(info.filename)) print("Decrypting KFX DRM voucher: {0}".format(info.filename))
for pid in [''] + totalpids: for pid in [''] + totalpids:
for dsn_len,secret_len in [(0,0), (16,0), (16,40), (32,40), (40,0), (40,40)]: for dsn_len,secret_len in [(0,0), (16,0), (16,40), (32,40), (40,0), (40,40)]:
@ -89,13 +86,13 @@ class KFXZipBook:
except: except:
pass pass
else: else:
raise Exception(u'Failed to decrypt KFX DRM voucher with any key') raise Exception("Failed to decrypt KFX DRM voucher with any key")
print(u'KFX DRM voucher successfully decrypted') print("KFX DRM voucher successfully decrypted")
license_type = voucher.getlicensetype() license_type = voucher.getlicensetype()
if license_type != "Purchase": if license_type != "Purchase":
raise Exception((u'This book is licensed as {0}. ' raise Exception(("This book is licensed as {0}. "
'These tools are intended for use on purchased books.').format(license_type)) 'These tools are intended for use on purchased books.').format(license_type))
self.voucher = voucher self.voucher = voucher

@ -1,9 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement
from __future__ import print_function
# kgenpids.py # kgenpids.py
# Copyright © 2008-2020 Apprentice Harper et al. # Copyright © 2008-2020 Apprentice Harper et al.
@ -14,7 +11,7 @@ __version__ = '3.0'
# 2.0 - Fix for non-ascii Windows user names # 2.0 - Fix for non-ascii Windows user names
# 2.1 - Actual fix for non-ascii WIndows user names. # 2.1 - Actual fix for non-ascii WIndows user names.
# 2.2 - Return information needed for KFX decryption # 2.2 - Return information needed for KFX decryption
# 3.0 - Added Python 3 compatibility for calibre 5.0 # 3.0 - Python 3 for calibre 5.0
import sys import sys
@ -217,18 +214,18 @@ def getK4Pids(rec209, token, kindleDatabase):
try: try:
# Get the DSN token, if present # Get the DSN token, if present
DSN = bytearray.fromhex((kindleDatabase[1])['DSN']).decode() DSN = bytearray.fromhex((kindleDatabase[1])['DSN']).decode()
print(u"Got DSN key from database {0}".format(kindleDatabase[0])) print("Got DSN key from database {0}".format(kindleDatabase[0]))
except KeyError: except KeyError:
# See if we have the info to generate the DSN # See if we have the info to generate the DSN
try: try:
# Get the Mazama Random number # Get the Mazama Random number
MazamaRandomNumber = bytearray.fromhex((kindleDatabase[1])['MazamaRandomNumber']).decode() MazamaRandomNumber = bytearray.fromhex((kindleDatabase[1])['MazamaRandomNumber']).decode()
#print u"Got MazamaRandomNumber from database {0}".format(kindleDatabase[0]) #print "Got MazamaRandomNumber from database {0}".format(kindleDatabase[0])
try: try:
# Get the SerialNumber token, if present # Get the SerialNumber token, if present
IDString = bytearray.fromhex((kindleDatabase[1])['SerialNumber']).decode() IDString = bytearray.fromhex((kindleDatabase[1])['SerialNumber']).decode()
print(u"Got SerialNumber from database {0}".format(kindleDatabase[0])) print("Got SerialNumber from database {0}".format(kindleDatabase[0]))
except KeyError: except KeyError:
# Get the IDString we added # Get the IDString we added
IDString = bytearray.fromhex((kindleDatabase[1])['IDString']).decode() IDString = bytearray.fromhex((kindleDatabase[1])['IDString']).decode()
@ -236,24 +233,24 @@ def getK4Pids(rec209, token, kindleDatabase):
try: try:
# Get the UsernameHash token, if present # Get the UsernameHash token, if present
encodedUsername = bytearray.fromhex((kindleDatabase[1])['UsernameHash']).decode() encodedUsername = bytearray.fromhex((kindleDatabase[1])['UsernameHash']).decode()
print(u"Got UsernameHash from database {0}".format(kindleDatabase[0])) print("Got UsernameHash from database {0}".format(kindleDatabase[0]))
except KeyError: except KeyError:
# Get the UserName we added # Get the UserName we added
UserName = bytearray.fromhex((kindleDatabase[1])['UserName']).decode() UserName = bytearray.fromhex((kindleDatabase[1])['UserName']).decode()
# encode it # encode it
encodedUsername = encodeHash(UserName,charMap1) encodedUsername = encodeHash(UserName,charMap1)
#print u"encodedUsername",encodedUsername.encode('hex') #print "encodedUsername",encodedUsername.encode('hex')
except KeyError: except KeyError:
print(u"Keys not found in the database {0}.".format(kindleDatabase[0])) print("Keys not found in the database {0}.".format(kindleDatabase[0]))
return pids return pids
# Get the ID string used # Get the ID string used
encodedIDString = encodeHash(IDString,charMap1) encodedIDString = encodeHash(IDString,charMap1)
#print u"encodedIDString",encodedIDString.encode('hex') #print "encodedIDString",encodedIDString.encode('hex')
# concat, hash and encode to calculate the DSN # concat, hash and encode to calculate the DSN
DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1) DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
#print u"DSN",DSN.encode('hex') #print "DSN",DSN.encode('hex')
pass pass
if rec209 is None: if rec209 is None:
@ -300,14 +297,14 @@ def getPidList(md1, md2, serials=[], kDatabases=[]):
try: try:
pidlst.extend(getK4Pids(md1, md2, kDatabase)) pidlst.extend(getK4Pids(md1, md2, kDatabase))
except Exception as e: except Exception as e:
print(u"Error getting PIDs from database {0}: {1}".format(kDatabase[0],e.args[0])) print("Error getting PIDs from database {0}: {1}".format(kDatabase[0],e.args[0]))
traceback.print_exc() traceback.print_exc()
for serialnum in serials: for serialnum in serials:
try: try:
pidlst.extend(getKindlePids(md1, md2, serialnum)) pidlst.extend(getKindlePids(md1, md2, serialnum))
except Exception as e: except Exception as e:
print(u"Error getting PIDs from serial number {0}: {1}".format(serialnum ,e.args[0])) print("Error getting PIDs from serial number {0}: {1}".format(serialnum ,e.args[0]))
traceback.print_exc() traceback.print_exc()
return pidlst return pidlst

@ -1,8 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement
# kindlekey.py # kindlekey.py
# Copyright © 2008-2020 Apprentice Harper et al. # Copyright © 2008-2020 Apprentice Harper et al.
@ -30,7 +28,7 @@ __version__ = '3.0'
# 2.5 - Final Fix for Windows user names with non-ascii characters, thanks to oneofusoneofus # 2.5 - Final Fix for Windows user names with non-ascii characters, thanks to oneofusoneofus
# 2.6 - Start adding support for Kindle 1.25+ .kinf2018 file # 2.6 - Start adding support for Kindle 1.25+ .kinf2018 file
# 2.7 - Finish .kinf2018 support, PC & Mac by Apprentice Sakuya # 2.7 - Finish .kinf2018 support, PC & Mac by Apprentice Sakuya
# 3.0 - Added Python 3 compatibility for calibre 5.0 # 3.0 - Python 3 for calibre 5.0
""" """
@ -104,7 +102,7 @@ def unicode_argv():
xrange(start, argc.value)] xrange(start, argc.value)]
# if we don't have any arguments at all, just pass back script name # if we don't have any arguments at all, just pass back script name
# this should never happen # this should never happen
return [u"kindlekey.py"] return ["kindlekey.py"]
else: else:
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
@ -905,11 +903,11 @@ if iswindows:
# replace any non-ASCII values with 0xfffd # replace any non-ASCII values with 0xfffd
for i in xrange(0,len(buffer)): for i in xrange(0,len(buffer)):
if buffer[i]>u"\u007f": if buffer[i]>"\u007f":
#print u"swapping char "+str(i)+" ("+buffer[i]+")" #print "swapping char "+str(i)+" ("+buffer[i]+")"
buffer[i] = u"\ufffd" buffer[i] = "\ufffd"
# return utf-8 encoding of modified username # return utf-8 encoding of modified username
#print u"modified username:"+buffer.value #print "modified username:"+buffer.value
return buffer.value.encode('utf-8') return buffer.value.encode('utf-8')
return GetUserName return GetUserName
GetUserName = GetUserName() GetUserName = GetUserName()
@ -939,7 +937,7 @@ if iswindows:
n = ctypes.windll.kernel32.GetEnvironmentVariableW(name, None, 0) n = ctypes.windll.kernel32.GetEnvironmentVariableW(name, None, 0)
if n == 0: if n == 0:
return None return None
buf = ctypes.create_unicode_buffer(u'\0'*n) buf = ctypes.create_unicode_buffer("\0"*n)
ctypes.windll.kernel32.GetEnvironmentVariableW(name, buf, n) ctypes.windll.kernel32.GetEnvironmentVariableW(name, buf, n)
return buf.value return buf.value
@ -951,7 +949,7 @@ if iswindows:
path = "" path = ""
if 'LOCALAPPDATA' in os.environ.keys(): if 'LOCALAPPDATA' in os.environ.keys():
# Python 2.x does not return unicode env. Use Python 3.x # Python 2.x does not return unicode env. Use Python 3.x
path = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%") path = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%")
# this is just another alternative. # this is just another alternative.
# path = getEnvironmentVariable('LOCALAPPDATA') # path = getEnvironmentVariable('LOCALAPPDATA')
if not os.path.isdir(path): if not os.path.isdir(path):
@ -979,7 +977,7 @@ if iswindows:
print ('Could not find the folder in which to look for kinfoFiles.') print ('Could not find the folder in which to look for kinfoFiles.')
else: else:
# Probably not the best. To Fix (shouldn't ignore in encoding) or use utf-8 # Probably not the best. To Fix (shouldn't ignore in encoding) or use utf-8
print(u'searching for kinfoFiles in ' + path.encode('ascii', 'ignore')) print("searching for kinfoFiles in " + path.encode('ascii', 'ignore'))
# look for (K4PC 1.25.1 and later) .kinf2018 file # look for (K4PC 1.25.1 and later) .kinf2018 file
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2018' kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2018'
@ -1166,9 +1164,9 @@ if iswindows:
# store values used in decryption # store values used in decryption
DB['IDString'] = GetIDString() DB['IDString'] = GetIDString()
DB['UserName'] = GetUserName() DB['UserName'] = GetUserName()
print(u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().encode('hex'))) print("Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().encode('hex')))
else: else:
print(u"Couldn't decrypt file.") print("Couldn't decrypt file.")
DB = {} DB = {}
return DB return DB
elif isosx: elif isosx:
@ -1183,7 +1181,7 @@ elif isosx:
libcrypto = find_library('crypto') libcrypto = find_library('crypto')
if libcrypto is None: if libcrypto is None:
raise DrmException(u"libcrypto not found") raise DrmException("libcrypto not found")
libcrypto = CDLL(libcrypto) libcrypto = CDLL(libcrypto)
# From OpenSSL's crypto aes header # From OpenSSL's crypto aes header
@ -1241,14 +1239,14 @@ elif isosx:
def set_decrypt_key(self, userkey, iv): def set_decrypt_key(self, userkey, iv):
self._blocksize = len(userkey) self._blocksize = len(userkey)
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) : if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
raise DrmException(u"AES improper key used") raise DrmException("AES improper key used")
return return
keyctx = self._keyctx = AES_KEY() keyctx = self._keyctx = AES_KEY()
self._iv = iv self._iv = iv
self._userkey = userkey self._userkey = userkey
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx) rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
if rv < 0: if rv < 0:
raise DrmException(u"Failed to initialize AES key") raise DrmException("Failed to initialize AES key")
def decrypt(self, data): def decrypt(self, data):
out = create_string_buffer(len(data)) out = create_string_buffer(len(data))
@ -1256,7 +1254,7 @@ elif isosx:
keyctx = self._keyctx keyctx = self._keyctx
rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0) rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0)
if rv == 0: if rv == 0:
raise DrmException(u"AES decryption failed") raise DrmException("AES decryption failed")
return out.raw return out.raw
def keyivgen(self, passwd, salt, iter, keylen): def keyivgen(self, passwd, salt, iter, keylen):
@ -1649,16 +1647,16 @@ elif isosx:
pass pass
if len(DB)>6: if len(DB)>6:
# store values used in decryption # store values used in decryption
print(u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(IDString, GetUserName())) print("Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(IDString, GetUserName()))
DB['IDString'] = IDString DB['IDString'] = IDString
DB['UserName'] = GetUserName() DB['UserName'] = GetUserName()
else: else:
print(u"Couldn't decrypt file.") print("Couldn't decrypt file.")
DB = {} DB = {}
return DB return DB
else: else:
def getDBfromFile(kInfoFile): def getDBfromFile(kInfoFile):
raise DrmException(u"This script only runs under Windows or Mac OS X.") raise DrmException("This script only runs under Windows or Mac OS X.")
return {} return {}
def kindlekeys(files = []): def kindlekeys(files = []):
@ -1683,27 +1681,27 @@ def getkey(outpath, files=[]):
outfile = outpath outfile = outpath
with file(outfile, 'w') as keyfileout: with file(outfile, 'w') as keyfileout:
keyfileout.write(json.dumps(keys[0])) keyfileout.write(json.dumps(keys[0]))
print(u"Saved a key to {0}".format(outfile)) print("Saved a key to {0}".format(outfile))
else: else:
keycount = 0 keycount = 0
for key in keys: for key in keys:
while True: while True:
keycount += 1 keycount += 1
outfile = os.path.join(outpath,u"kindlekey{0:d}.k4i".format(keycount)) outfile = os.path.join(outpath,"kindlekey{0:d}.k4i".format(keycount))
if not os.path.exists(outfile): if not os.path.exists(outfile):
break break
with file(outfile, 'w') as keyfileout: with file(outfile, 'w') as keyfileout:
keyfileout.write(json.dumps(key)) keyfileout.write(json.dumps(key))
print(u"Saved a key to {0}".format(outfile)) print("Saved a key to {0}".format(outfile))
return True return True
return False return False
def usage(progname): def usage(progname):
print(u"Finds, decrypts and saves the default Kindle For Mac/PC encryption keys.") print("Finds, decrypts and saves the default Kindle For Mac/PC encryption keys.")
print(u"Keys are saved to the current directory, or a specified output directory.") print("Keys are saved to the current directory, or a specified output directory.")
print(u"If a file name is passed instead of a directory, only the first key is saved, in that file.") print("If a file name is passed instead of a directory, only the first key is saved, in that file.")
print(u"Usage:") print("Usage:")
print(u" {0:s} [-h] [-k <kindle.info>] [<outpath>]".format(progname)) print(" {0:s} [-h] [-k <kindle.info>] [<outpath>]".format(progname))
def cli_main(): def cli_main():
@ -1711,12 +1709,12 @@ def cli_main():
sys.stderr=SafeUnbuffered(sys.stderr) sys.stderr=SafeUnbuffered(sys.stderr)
argv=unicode_argv() argv=unicode_argv()
progname = os.path.basename(argv[0]) progname = os.path.basename(argv[0])
print(u"{0} v{1}\nCopyright © 2010-2016 by some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__)) print("{0} v{1}\nCopyright © 2010-2016 by some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__))
try: try:
opts, args = getopt.getopt(argv[1:], "hk:") opts, args = getopt.getopt(argv[1:], "hk:")
except getopt.GetoptError as err: except getopt.GetoptError as err:
print(u"Error in options or arguments: {0}".format(err.args[0])) print("Error in options or arguments: {0}".format(err.args[0]))
usage(progname) usage(progname)
sys.exit(2) sys.exit(2)
@ -1745,7 +1743,7 @@ def cli_main():
outpath = os.path.realpath(os.path.normpath(outpath)) outpath = os.path.realpath(os.path.normpath(outpath))
if not getkey(outpath, files): if not getkey(outpath, files):
print(u"Could not retrieve Kindle for Mac/PC key.") print("Could not retrieve Kindle for Mac/PC key.")
return 0 return 0
@ -1761,7 +1759,7 @@ def gui_main():
class ExceptionDialog(Tkinter.Frame): class ExceptionDialog(Tkinter.Frame):
def __init__(self, root, text): def __init__(self, root, text):
Tkinter.Frame.__init__(self, root, border=5) Tkinter.Frame.__init__(self, root, border=5)
label = Tkinter.Label(self, text=u"Unexpected error:", label = Tkinter.Label(self, text="Unexpected error:",
anchor=Tkconstants.W, justify=Tkconstants.LEFT) anchor=Tkconstants.W, justify=Tkconstants.LEFT)
label.pack(fill=Tkconstants.X, expand=0) label.pack(fill=Tkconstants.X, expand=0)
self.text = Tkinter.Text(self) self.text = Tkinter.Text(self)
@ -1781,16 +1779,16 @@ def gui_main():
for key in keys: for key in keys:
while True: while True:
keycount += 1 keycount += 1
outfile = os.path.join(progpath,u"kindlekey{0:d}.k4i".format(keycount)) outfile = os.path.join(progpath,"kindlekey{0:d}.k4i".format(keycount))
if not os.path.exists(outfile): if not os.path.exists(outfile):
break break
with file(outfile, 'w') as keyfileout: with file(outfile, 'w') as keyfileout:
keyfileout.write(json.dumps(key)) keyfileout.write(json.dumps(key))
success = True success = True
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile)) tkMessageBox.showinfo(progname, "Key successfully retrieved to {0}".format(outfile))
except DrmException as e: except DrmException as e:
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e))) tkMessageBox.showerror(progname, "Error: {0}".format(str(e)))
except Exception: except Exception:
root.wm_state('normal') root.wm_state('normal')
root.title(progname) root.title(progname)

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Mobipocket PID calculator v0.4 for Amazon Kindle. # Mobipocket PID calculator v0.4 for Amazon Kindle.
@ -10,7 +10,7 @@
# 0.3 updated for unicode # 0.3 updated for unicode
# 0.4 Added support for serial numbers starting with '9', fixed unicode bugs. # 0.4 Added support for serial numbers starting with '9', fixed unicode bugs.
# 0.5 moved unicode_argv call inside main for Windows DeDRM compatibility # 0.5 moved unicode_argv call inside main for Windows DeDRM compatibility
# 1.0 Added Python 3 compatibility for calibre 5.0 # 1.0 Python 3 for calibre 5.0
from __future__ import print_function from __future__ import print_function
import sys import sys
@ -67,7 +67,7 @@ def unicode_argv():
range(start, argc.value)] range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name # if we don't have any arguments at all, just pass back script name
# this should never happen # this should never happen
return [u"kindlepid.py"] return ["kindlepid.py"]
else: else:
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
@ -111,28 +111,28 @@ def pidFromSerial(s, l):
return pid return pid
def cli_main(): def cli_main():
print(u"Mobipocket PID calculator for Amazon Kindle. Copyright © 2007, 2009 Igor Skochinsky") print("Mobipocket PID calculator for Amazon Kindle. Copyright © 2007, 2009 Igor Skochinsky")
argv=unicode_argv() argv=unicode_argv()
if len(argv)==2: if len(argv)==2:
serial = argv[1] serial = argv[1]
else: else:
print(u"Usage: kindlepid.py <Kindle Serial Number>/<iPhone/iPod Touch UDID>") print("Usage: kindlepid.py <Kindle Serial Number>/<iPhone/iPod Touch UDID>")
return 1 return 1
if len(serial)==16: if len(serial)==16:
if serial.startswith("B") or serial.startswith("9"): if serial.startswith("B") or serial.startswith("9"):
print(u"Kindle serial number detected") print("Kindle serial number detected")
else: else:
print(u"Warning: unrecognized serial number. Please recheck input.") print("Warning: unrecognized serial number. Please recheck input.")
return 1 return 1
pid = pidFromSerial(serial.encode("utf-8"),7)+'*' pid = pidFromSerial(serial.encode("utf-8"),7)+'*'
print(u"Mobipocket PID for Kindle serial#{0} is {1}".format(serial,checksumPid(pid))) print("Mobipocket PID for Kindle serial#{0} is {1}".format(serial,checksumPid(pid)))
return 0 return 0
elif len(serial)==40: elif len(serial)==40:
print(u"iPhone serial number (UDID) detected") print("iPhone serial number (UDID) detected")
pid = pidFromSerial(serial.encode("utf-8"),8) pid = pidFromSerial(serial.encode("utf-8"),8)
print(u"Mobipocket PID for iPhone serial#{0} is {1}".format(serial,checksumPid(pid))) print("Mobipocket PID for iPhone serial#{0} is {1}".format(serial,checksumPid(pid)))
return 0 return 0
print(u"Warning: unrecognized serial number. Please recheck input.") print("Warning: unrecognized serial number. Please recheck input.")
return 1 return 1

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# mobidedrm.py # mobidedrm.py
@ -7,7 +7,7 @@
from __future__ import print_function from __future__ import print_function
__license__ = 'GPL v3' __license__ = 'GPL v3'
__version__ = u"1.00" __version__ = "1.00"
# This is a python script. You need a Python interpreter to run it. # This is a python script. You need a Python interpreter to run it.
# For example, ActiveState Python, which exists for windows. # For example, ActiveState Python, which exists for windows.
@ -82,7 +82,7 @@ import binascii
try: try:
from alfcrypto import Pukall_Cipher from alfcrypto import Pukall_Cipher
except: except:
print(u"AlfCrypto not found. Using python PC1 implementation.") print("AlfCrypto not found. Using python PC1 implementation.")
# Wrap a stream so that output gets flushed immediately # Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get # and also make sure that any unicode strings get
@ -135,7 +135,7 @@ def unicode_argv():
range(start, argc.value)] range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name # if we don't have any arguments at all, just pass back script name
# this should never happen # this should never happen
return [u"mobidedrm.py"] return ["mobidedrm.py"]
else: else:
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
@ -166,7 +166,7 @@ def PC1(key, src, decryption=True):
sum2 = 0; sum2 = 0;
keyXorVal = 0; keyXorVal = 0;
if len(key)!=16: if len(key)!=16:
DrmException (u"PC1: Bad key length") DrmException ("PC1: Bad key length")
wkey = [] wkey = []
for i in range(8): for i in range(8):
wkey.append(key[i*2]<<8 | key[i*2+1]) wkey.append(key[i*2]<<8 | key[i*2+1])
@ -246,19 +246,19 @@ class MobiBook:
pass pass
def __init__(self, infile): def __init__(self, infile):
print(u"MobiDeDrm v{0:s}.\nCopyright © 2008-2017 The Dark Reverser, Apprentice Harper et al.".format(__version__)) print("MobiDeDrm v{0:s}.\nCopyright © 2008-2017 The Dark Reverser, Apprentice Harper et al.".format(__version__))
try: try:
from alfcrypto import Pukall_Cipher from alfcrypto import Pukall_Cipher
except: except:
print(u"AlfCrypto not found. Using python PC1 implementation.") print("AlfCrypto not found. Using python PC1 implementation.")
# initial sanity check on file # initial sanity check on file
self.data_file = open(infile, 'rb').read() self.data_file = open(infile, 'rb').read()
self.mobi_data = '' self.mobi_data = ''
self.header = self.data_file[0:78] self.header = self.data_file[0:78]
if self.header[0x3C:0x3C+8] != b'BOOKMOBI' and self.header[0x3C:0x3C+8] != b'TEXtREAd': if self.header[0x3C:0x3C+8] != b'BOOKMOBI' and self.header[0x3C:0x3C+8] != b'TEXtREAd':
raise DrmException(u"Invalid file format") raise DrmException("Invalid file format")
self.magic = self.header[0x3C:0x3C+8] self.magic = self.header[0x3C:0x3C+8]
self.crypto_type = -1 self.crypto_type = -1
@ -284,16 +284,16 @@ class MobiBook:
self.mobi_version = -1 self.mobi_version = -1
if self.magic == 'TEXtREAd': if self.magic == 'TEXtREAd':
print(u"PalmDoc format book detected.") print("PalmDoc format book detected.")
return return
self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18]) self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
self.mobi_codepage, = struct.unpack('>L',self.sect[0x1c:0x20]) self.mobi_codepage, = struct.unpack('>L',self.sect[0x1c:0x20])
self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C]) self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C])
#print u"MOBI header version {0:d}, header length {1:d}".format(self.mobi_version, self.mobi_length) #print "MOBI header version {0:d}, header length {1:d}".format(self.mobi_version, self.mobi_length)
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5): if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4]) self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
#print u"Extra Data Flags: {0:d}".format(self.extra_data_flags) #print "Extra Data Flags: {0:d}".format(self.extra_data_flags)
if (self.compression != 17480): if (self.compression != 17480):
# multibyte utf8 data is included in the encryption for PalmDoc compression # multibyte utf8 data is included in the encryption for PalmDoc compression
# so clear that byte so that we leave it to be decrypted. # so clear that byte so that we leave it to be decrypted.
@ -322,7 +322,7 @@ class MobiBook:
# print type, size, content, content.encode('hex') # print type, size, content, content.encode('hex')
pos += size pos += size
except Exception as e: except Exception as e:
print(u"Cannot set meta_array: Error: {:s}".format(e.args[0])) print("Cannot set meta_array: Error: {:s}".format(e.args[0]))
def getBookTitle(self): def getBookTitle(self):
codec_map = { codec_map = {
@ -411,51 +411,51 @@ class MobiBook:
def getBookType(self): def getBookType(self):
if self.print_replica: if self.print_replica:
return u"Print Replica" return "Print Replica"
if self.mobi_version >= 8: if self.mobi_version >= 8:
return u"Kindle Format 8" return "Kindle Format 8"
if self.mobi_version >= 0: if self.mobi_version >= 0:
return u"Mobipocket {0:d}".format(self.mobi_version) return "Mobipocket {0:d}".format(self.mobi_version)
return u"PalmDoc" return "PalmDoc"
def getBookExtension(self): def getBookExtension(self):
if self.print_replica: if self.print_replica:
return u".azw4" return ".azw4"
if self.mobi_version >= 8: if self.mobi_version >= 8:
return u".azw3" return ".azw3"
return u".mobi" return ".mobi"
def processBook(self, pidlist): def processBook(self, pidlist):
crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2]) crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
print(u"Crypto Type is: {0:d}".format(crypto_type)) print("Crypto Type is: {0:d}".format(crypto_type))
self.crypto_type = crypto_type self.crypto_type = crypto_type
if crypto_type == 0: if crypto_type == 0:
print(u"This book is not encrypted.") print("This book is not encrypted.")
# we must still check for Print Replica # we must still check for Print Replica
self.print_replica = (self.loadSection(1)[0:4] == '%MOP') self.print_replica = (self.loadSection(1)[0:4] == '%MOP')
self.mobi_data = self.data_file self.mobi_data = self.data_file
return return
if crypto_type != 2 and crypto_type != 1: if crypto_type != 2 and crypto_type != 1:
raise DrmException(u"Cannot decode unknown Mobipocket encryption type {0:d}".format(crypto_type)) raise DrmException("Cannot decode unknown Mobipocket encryption type {0:d}".format(crypto_type))
if 406 in self.meta_array: if 406 in self.meta_array:
data406 = self.meta_array[406] data406 = self.meta_array[406]
val406, = struct.unpack('>Q',data406) val406, = struct.unpack('>Q',data406)
if val406 != 0: if val406 != 0:
raise DrmException(u"Cannot decode library or rented ebooks.") raise DrmException("Cannot decode library or rented ebooks.")
goodpids = [] goodpids = []
# print("DEBUG ==== pidlist = ", pidlist) # print("DEBUG ==== pidlist = ", pidlist)
for pid in pidlist: for pid in pidlist:
if len(pid)==10: if len(pid)==10:
if checksumPid(pid[0:-2]) != pid: if checksumPid(pid[0:-2]) != pid:
print(u"Warning: PID {0} has incorrect checksum, should have been {1}".format(pid,checksumPid(pid[0:-2]))) print("Warning: PID {0} has incorrect checksum, should have been {1}".format(pid,checksumPid(pid[0:-2])))
goodpids.append(pid[0:-2]) goodpids.append(pid[0:-2])
elif len(pid)==8: elif len(pid)==8:
goodpids.append(pid) goodpids.append(pid)
else: else:
print(u"Warning: PID {0} has wrong number of digits".format(pid)) print("Warning: PID {0} has wrong number of digits".format(pid))
# print(u"======= DEBUG good pids = ", goodpids) # print("======= DEBUG good pids = ", goodpids)
if self.crypto_type == 1: if self.crypto_type == 1:
t1_keyvec = 'QDCVEPMU675RUBSZ' t1_keyvec = 'QDCVEPMU675RUBSZ'
@ -471,32 +471,32 @@ class MobiBook:
# calculate the keys # calculate the keys
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16]) drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16])
if drm_count == 0: if drm_count == 0:
raise DrmException(u"Encryption not initialised. Must be opened with Mobipocket Reader first.") raise DrmException("Encryption not initialised. Must be opened with Mobipocket Reader first.")
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids) found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
if not found_key: if not found_key:
raise DrmException(u"No key found in {0:d} keys tried.".format(len(goodpids))) raise DrmException("No key found in {0:d} keys tried.".format(len(goodpids)))
# kill the drm keys # kill the drm keys
self.patchSection(0, b'\0' * drm_size, drm_ptr) self.patchSection(0, b'\0' * drm_size, drm_ptr)
# kill the drm pointers # kill the drm pointers
self.patchSection(0, b'\xff' * 4 + b'\0' * 12, 0xA8) self.patchSection(0, b'\xff' * 4 + b'\0' * 12, 0xA8)
if pid=='00000000': if pid=='00000000':
print(u"File has default encryption, no specific key needed.") print("File has default encryption, no specific key needed.")
else: else:
print(u"File is encoded with PID {0}.".format(checksumPid(pid))) print("File is encoded with PID {0}.".format(checksumPid(pid)))
# clear the crypto type # clear the crypto type
self.patchSection(0, b'\0' * 2, 0xC) self.patchSection(0, b'\0' * 2, 0xC)
# decrypt sections # decrypt sections
print(u"Decrypting. Please wait . . .", end=' ') print("Decrypting. Please wait . . .", end=' ')
mobidataList = [] mobidataList = []
mobidataList.append(self.data_file[:self.sections[1][0]]) mobidataList.append(self.data_file[:self.sections[1][0]])
for i in range(1, self.records+1): for i in range(1, self.records+1):
data = self.loadSection(i) data = self.loadSection(i)
extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags) extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags)
if i%100 == 0: if i%100 == 0:
print(u".", end=' ') print(".", end=' ')
# print "record %d, extra_size %d" %(i,extra_size) # print "record %d, extra_size %d" %(i,extra_size)
decoded_data = PC1(found_key, data[0:len(data) - extra_size]) decoded_data = PC1(found_key, data[0:len(data) - extra_size])
if i==1: if i==1:
@ -507,12 +507,12 @@ class MobiBook:
if self.num_sections > self.records+1: if self.num_sections > self.records+1:
mobidataList.append(self.data_file[self.sections[self.records+1][0]:]) mobidataList.append(self.data_file[self.sections[self.records+1][0]:])
self.mobi_data = b''.join(mobidataList) self.mobi_data = b''.join(mobidataList)
print(u"done") print("done")
return return
def getUnencryptedBook(infile,pidlist): def getUnencryptedBook(infile,pidlist):
if not os.path.isfile(infile): if not os.path.isfile(infile):
raise DrmException(u"Input File Not Found.") raise DrmException("Input File Not Found.")
book = MobiBook(infile) book = MobiBook(infile)
book.processBook(pidlist) book.processBook(pidlist)
return book.mobi_data return book.mobi_data
@ -522,10 +522,10 @@ def cli_main():
argv=unicode_argv() argv=unicode_argv()
progname = os.path.basename(argv[0]) progname = os.path.basename(argv[0])
if len(argv)<3 or len(argv)>4: if len(argv)<3 or len(argv)>4:
print(u"MobiDeDrm v{0:s}.\nCopyright © 2008-2017 The Dark Reverser, Apprentice Harper et al.".format(__version__)) print("MobiDeDrm v{0:s}.\nCopyright © 2008-2017 The Dark Reverser, Apprentice Harper et al.".format(__version__))
print(u"Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks") print("Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks")
print(u"Usage:") print("Usage:")
print(u" {0} <infile> <outfile> [<Comma separated list of PIDs to try>]".format(progname)) print(" {0} <infile> <outfile> [<Comma separated list of PIDs to try>]".format(progname))
return 1 return 1
else: else:
infile = argv[1] infile = argv[1]
@ -538,7 +538,7 @@ def cli_main():
stripped_file = getUnencryptedBook(infile, pidlist) stripped_file = getUnencryptedBook(infile, pidlist)
open(outfile, 'wb').write(stripped_file) open(outfile, 'wb').write(stripped_file)
except DrmException as e: except DrmException as e:
print(u"MobiDeDRM v{0} Error: {1:s}".format(__version__,e.args[0])) print("MobiDeDRM v{0} Error: {1:s}".format(__version__,e.args[0]))
return 1 return 1
return 0 return 0

@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*-
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import with_statement
__license__ = 'GPL v3' __license__ = 'GPL v3'
# Standard Python modules. # Standard Python modules.
@ -15,7 +15,7 @@ from calibre.constants import iswindows, isosx
class DeDRM_Prefs(): class DeDRM_Prefs():
def __init__(self): def __init__(self):
JSON_PATH = os.path.join(u"plugins", PLUGIN_NAME.strip().lower().replace(' ', '_') + '.json') JSON_PATH = os.path.join("plugins", PLUGIN_NAME.strip().lower().replace(' ', '_') + '.json')
self.dedrmprefs = JSONConfig(JSON_PATH) self.dedrmprefs = JSONConfig(JSON_PATH)
self.dedrmprefs.defaults['configured'] = False self.dedrmprefs.defaults['configured'] = False
@ -98,7 +98,7 @@ def convertprefs(always = False):
try: try:
name, ccn = keystring.split(',') name, ccn = keystring.split(',')
# Generate Barnes & Noble EPUB user key from name and credit card number. # Generate Barnes & Noble EPUB user key from name and credit card number.
keyname = u"{0}_{1}".format(name.strip(),ccn.strip()[-4:]) keyname = "{0}_{1}".format(name.strip(),ccn.strip()[-4:])
keyvalue = generate_key(name, ccn) keyvalue = generate_key(name, ccn)
userkeys.append([keyname,keyvalue]) userkeys.append([keyname,keyvalue])
except Exception as e: except Exception as e:
@ -115,7 +115,7 @@ def convertprefs(always = False):
try: try:
name, cc = keystring.split(',') name, cc = keystring.split(',')
# Generate eReader user key from name and credit card number. # Generate eReader user key from name and credit card number.
keyname = u"{0}_{1}".format(name.strip(),cc.strip()[-4:]) keyname = "{0}_{1}".format(name.strip(),cc.strip()[-4:])
keyvalue = getuser_key(name,cc).encode('hex') keyvalue = getuser_key(name,cc).encode('hex')
userkeys.append([keyname,keyvalue]) userkeys.append([keyname,keyvalue])
except Exception as e: except Exception as e:
@ -161,15 +161,15 @@ def convertprefs(always = False):
return return
print(u"{0} v{1}: Importing configuration data from old DeDRM plugins".format(PLUGIN_NAME, PLUGIN_VERSION)) print("{0} v{1}: Importing configuration data from old DeDRM plugins".format(PLUGIN_NAME, PLUGIN_VERSION))
IGNOBLEPLUGINNAME = "Ignoble Epub DeDRM" IGNOBLEPLUGINNAME = "Ignoble Epub DeDRM"
EREADERPLUGINNAME = "eReader PDB 2 PML" EREADERPLUGINNAME = "eReader PDB 2 PML"
OLDKINDLEPLUGINNAME = "K4PC, K4Mac, Kindle Mobi and Topaz DeDRM" OLDKINDLEPLUGINNAME = "K4PC, K4Mac, Kindle Mobi and Topaz DeDRM"
# get prefs from older tools # get prefs from older tools
kindleprefs = JSONConfig(os.path.join(u"plugins", u"K4MobiDeDRM")) kindleprefs = JSONConfig(os.path.join("plugins", "K4MobiDeDRM"))
ignobleprefs = JSONConfig(os.path.join(u"plugins", u"ignoble_epub_dedrm")) ignobleprefs = JSONConfig(os.path.join("plugins", "ignoble_epub_dedrm"))
# Handle the old ignoble plugin's customization string by converting the # Handle the old ignoble plugin's customization string by converting the
# old string to stored keys... get that personal data out of plain sight. # old string to stored keys... get that personal data out of plain sight.
@ -177,7 +177,7 @@ def convertprefs(always = False):
sc = config['plugin_customization'] sc = config['plugin_customization']
val = sc.pop(IGNOBLEPLUGINNAME, None) val = sc.pop(IGNOBLEPLUGINNAME, None)
if val is not None: if val is not None:
print(u"{0} v{1}: Converting old Ignoble plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION)) print("{0} v{1}: Converting old Ignoble plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION))
priorkeycount = len(dedrmprefs['bandnkeys']) priorkeycount = len(dedrmprefs['bandnkeys'])
userkeys = parseIgnobleString(str(val)) userkeys = parseIgnobleString(str(val))
for keypair in userkeys: for keypair in userkeys:
@ -185,7 +185,7 @@ def convertprefs(always = False):
value = keypair[1] value = keypair[1]
dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value) dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount
print(u"{0} v{1}: {2:d} Barnes and Noble {3} imported from old Ignoble plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys")) print("{0} v{1}: {2:d} Barnes and Noble {3} imported from old Ignoble plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, "key" if addedkeycount==1 else "keys"))
# Make the json write all the prefs to disk # Make the json write all the prefs to disk
dedrmprefs.writeprefs(False) dedrmprefs.writeprefs(False)
@ -193,7 +193,7 @@ def convertprefs(always = False):
# old string to stored keys... get that personal data out of plain sight. # old string to stored keys... get that personal data out of plain sight.
val = sc.pop(EREADERPLUGINNAME, None) val = sc.pop(EREADERPLUGINNAME, None)
if val is not None: if val is not None:
print(u"{0} v{1}: Converting old eReader plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION)) print("{0} v{1}: Converting old eReader plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION))
priorkeycount = len(dedrmprefs['ereaderkeys']) priorkeycount = len(dedrmprefs['ereaderkeys'])
userkeys = parseeReaderString(str(val)) userkeys = parseeReaderString(str(val))
for keypair in userkeys: for keypair in userkeys:
@ -201,14 +201,14 @@ def convertprefs(always = False):
value = keypair[1] value = keypair[1]
dedrmprefs.addnamedvaluetoprefs('ereaderkeys', name, value) dedrmprefs.addnamedvaluetoprefs('ereaderkeys', name, value)
addedkeycount = len(dedrmprefs['ereaderkeys'])-priorkeycount addedkeycount = len(dedrmprefs['ereaderkeys'])-priorkeycount
print(u"{0} v{1}: {2:d} eReader {3} imported from old eReader plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys")) print("{0} v{1}: {2:d} eReader {3} imported from old eReader plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, "key" if addedkeycount==1 else "keys"))
# Make the json write all the prefs to disk # Make the json write all the prefs to disk
dedrmprefs.writeprefs(False) dedrmprefs.writeprefs(False)
# get old Kindle plugin configuration string # get old Kindle plugin configuration string
val = sc.pop(OLDKINDLEPLUGINNAME, None) val = sc.pop(OLDKINDLEPLUGINNAME, None)
if val is not None: if val is not None:
print(u"{0} v{1}: Converting old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION)) print("{0} v{1}: Converting old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION))
priorpidcount = len(dedrmprefs['pids']) priorpidcount = len(dedrmprefs['pids'])
priorserialcount = len(dedrmprefs['serials']) priorserialcount = len(dedrmprefs['serials'])
pids, serials = parseKindleString(val) pids, serials = parseKindleString(val)
@ -218,7 +218,7 @@ def convertprefs(always = False):
dedrmprefs.addvaluetoprefs('serials',serial) dedrmprefs.addvaluetoprefs('serials',serial)
addedpidcount = len(dedrmprefs['pids']) - priorpidcount addedpidcount = len(dedrmprefs['pids']) - priorpidcount
addedserialcount = len(dedrmprefs['serials']) - priorserialcount addedserialcount = len(dedrmprefs['serials']) - priorserialcount
print(u"{0} v{1}: {2:d} {3} and {4:d} {5} imported from old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, u"PID" if addedpidcount==1 else u"PIDs", addedserialcount, u"serial number" if addedserialcount==1 else u"serial numbers")) print("{0} v{1}: {2:d} {3} and {4:d} {5} imported from old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, "PID" if addedpidcount==1 else "PIDs", addedserialcount, "serial number" if addedserialcount==1 else "serial numbers"))
# Make the json write all the prefs to disk # Make the json write all the prefs to disk
dedrmprefs.writeprefs(False) dedrmprefs.writeprefs(False)
@ -234,7 +234,7 @@ def convertprefs(always = False):
dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value) dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount
if addedkeycount > 0: if addedkeycount > 0:
print(u"{0} v{1}: {2:d} Barnes and Noble {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key file" if addedkeycount==1 else u"key files")) print("{0} v{1}: {2:d} Barnes and Noble {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, "key file" if addedkeycount==1 else "key files"))
# Make the json write all the prefs to disk # Make the json write all the prefs to disk
dedrmprefs.writeprefs(False) dedrmprefs.writeprefs(False)
@ -247,7 +247,7 @@ def convertprefs(always = False):
dedrmprefs.addnamedvaluetoprefs('adeptkeys', name, value) dedrmprefs.addnamedvaluetoprefs('adeptkeys', name, value)
addedkeycount = len(dedrmprefs['adeptkeys'])-priorkeycount addedkeycount = len(dedrmprefs['adeptkeys'])-priorkeycount
if addedkeycount > 0: if addedkeycount > 0:
print(u"{0} v{1}: {2:d} Adobe Adept {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"keyfile" if addedkeycount==1 else u"keyfiles")) print("{0} v{1}: {2:d} Adobe Adept {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, "keyfile" if addedkeycount==1 else "keyfiles"))
# Make the json write all the prefs to disk # Make the json write all the prefs to disk
dedrmprefs.writeprefs(False) dedrmprefs.writeprefs(False)
@ -260,7 +260,7 @@ def convertprefs(always = False):
addedkeycount = len(dedrmprefs['bandnkeys']) - priorkeycount addedkeycount = len(dedrmprefs['bandnkeys']) - priorkeycount
# no need to delete old prefs, since they contain no recoverable private data # no need to delete old prefs, since they contain no recoverable private data
if addedkeycount > 0: if addedkeycount > 0:
print(u"{0} v{1}: {2:d} Barnes and Noble {3} imported from Ignoble plugin preferences.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys")) print("{0} v{1}: {2:d} Barnes and Noble {3} imported from Ignoble plugin preferences.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, "key" if addedkeycount==1 else "keys"))
# Make the json write all the prefs to disk # Make the json write all the prefs to disk
dedrmprefs.writeprefs(False) dedrmprefs.writeprefs(False)
@ -277,19 +277,19 @@ def convertprefs(always = False):
dedrmprefs.addvaluetoprefs('serials',serial) dedrmprefs.addvaluetoprefs('serials',serial)
addedpidcount = len(dedrmprefs['pids']) - priorpidcount addedpidcount = len(dedrmprefs['pids']) - priorpidcount
if addedpidcount > 0: if addedpidcount > 0:
print(u"{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, u"PID" if addedpidcount==1 else u"PIDs")) print("{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, "PID" if addedpidcount==1 else "PIDs"))
addedserialcount = len(dedrmprefs['serials']) - priorserialcount addedserialcount = len(dedrmprefs['serials']) - priorserialcount
if addedserialcount > 0: if addedserialcount > 0:
print(u"{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedserialcount, u"serial number" if addedserialcount==1 else u"serial numbers")) print("{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedserialcount, "serial number" if addedserialcount==1 else "serial numbers"))
try: try:
if 'wineprefix' in kindleprefs and kindleprefs['wineprefix'] != "": if 'wineprefix' in kindleprefs and kindleprefs['wineprefix'] != "":
dedrmprefs.set('adobewineprefix',kindleprefs['wineprefix']) dedrmprefs.set('adobewineprefix',kindleprefs['wineprefix'])
dedrmprefs.set('kindlewineprefix',kindleprefs['wineprefix']) dedrmprefs.set('kindlewineprefix',kindleprefs['wineprefix'])
print(u"{0} v{1}: WINEPREFIX (2) imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, kindleprefs['wineprefix'])) print("{0} v{1}: WINEPREFIX (2) imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, kindleprefs['wineprefix']))
except: except:
traceback.print_exc() traceback.print_exc()
# Make the json write all the prefs to disk # Make the json write all the prefs to disk
dedrmprefs.writeprefs() dedrmprefs.writeprefs()
print(u"{0} v{1}: Finished setting up configuration data.".format(PLUGIN_NAME, PLUGIN_VERSION)) print("{0} v{1}: Finished setting up configuration data.".format(PLUGIN_NAME, PLUGIN_VERSION))

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab

@ -1,4 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*-
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
import sys import sys

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# topazextract.py # topazextract.py
@ -68,7 +68,7 @@ def unicode_argv():
range(start, argc.value)] range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name # if we don't have any arguments at all, just pass back script name
# this should never happen # this should never happen
return [u"mobidedrm.py"] return ["mobidedrm.py"]
else: else:
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
@ -170,11 +170,11 @@ def decryptDkeyRecord(data,PID):
record = decryptRecord(data,PID) record = decryptRecord(data,PID)
fields = unpack('3sB8sB8s3s',record) fields = unpack('3sB8sB8s3s',record)
if fields[0] != 'PID' or fields[5] != 'pid' : if fields[0] != 'PID' or fields[5] != 'pid' :
raise DrmException(u"Didn't find PID magic numbers in record") raise DrmException("Didn't find PID magic numbers in record")
elif fields[1] != 8 or fields[3] != 8 : elif fields[1] != 8 or fields[3] != 8 :
raise DrmException(u"Record didn't contain correct length fields") raise DrmException("Record didn't contain correct length fields")
elif fields[2] != PID : elif fields[2] != PID :
raise DrmException(u"Record didn't contain PID") raise DrmException("Record didn't contain PID")
return fields[4] return fields[4]
# Decrypt all dkey records (contain the book PID) # Decrypt all dkey records (contain the book PID)
@ -191,7 +191,7 @@ def decryptDkeyRecords(data,PID):
pass pass
data = data[1+length:] data = data[1+length:]
if len(records) == 0: if len(records) == 0:
raise DrmException(u"BookKey Not Found") raise DrmException("BookKey Not Found")
return records return records
@ -206,7 +206,7 @@ class TopazBook:
self.bookKey = None self.bookKey = None
magic = unpack('4s',self.fo.read(4))[0] magic = unpack('4s',self.fo.read(4))[0]
if magic != 'TPZ0': if magic != 'TPZ0':
raise DrmException(u"Parse Error : Invalid Header, not a Topaz file") raise DrmException("Parse Error : Invalid Header, not a Topaz file")
self.parseTopazHeaders() self.parseTopazHeaders()
self.parseMetadata() self.parseMetadata()
@ -224,7 +224,7 @@ class TopazBook:
# Read and parse one header record at the current book file position and return the associated data # Read and parse one header record at the current book file position and return the associated data
# [[offset,decompressedLength,compressedLength],...] # [[offset,decompressedLength,compressedLength],...]
if ord(self.fo.read(1)) != 0x63: if ord(self.fo.read(1)) != 0x63:
raise DrmException(u"Parse Error : Invalid Header") raise DrmException("Parse Error : Invalid Header")
tag = bookReadString(self.fo) tag = bookReadString(self.fo)
record = bookReadHeaderRecordData() record = bookReadHeaderRecordData()
return [tag,record] return [tag,record]
@ -235,7 +235,7 @@ class TopazBook:
if debug: print(result[0], ": ", result[1]) if debug: print(result[0], ": ", result[1])
self.bookHeaderRecords[result[0]] = result[1] self.bookHeaderRecords[result[0]] = result[1]
if ord(self.fo.read(1)) != 0x64 : if ord(self.fo.read(1)) != 0x64 :
raise DrmException(u"Parse Error : Invalid Header") raise DrmException("Parse Error : Invalid Header")
self.bookPayloadOffset = self.fo.tell() self.bookPayloadOffset = self.fo.tell()
def parseMetadata(self): def parseMetadata(self):
@ -243,7 +243,7 @@ class TopazBook:
self.fo.seek(self.bookPayloadOffset + self.bookHeaderRecords['metadata'][0][0]) self.fo.seek(self.bookPayloadOffset + self.bookHeaderRecords['metadata'][0][0])
tag = bookReadString(self.fo) tag = bookReadString(self.fo)
if tag != 'metadata' : if tag != 'metadata' :
raise DrmException(u"Parse Error : Record Names Don't Match") raise DrmException("Parse Error : Record Names Don't Match")
flags = ord(self.fo.read(1)) flags = ord(self.fo.read(1))
nbRecords = ord(self.fo.read(1)) nbRecords = ord(self.fo.read(1))
if debug: print("Metadata Records: %d" % nbRecords) if debug: print("Metadata Records: %d" % nbRecords)
@ -321,11 +321,11 @@ class TopazBook:
try: try:
keydata = self.getBookPayloadRecord('dkey', 0) keydata = self.getBookPayloadRecord('dkey', 0)
except DrmException as e: except DrmException as e:
print(u"no dkey record found, book may not be encrypted") print("no dkey record found, book may not be encrypted")
print(u"attempting to extrct files without a book key") print("attempting to extrct files without a book key")
self.createBookDirectory() self.createBookDirectory()
self.extractFiles() self.extractFiles()
print(u"Successfully Extracted Topaz contents") print("Successfully Extracted Topaz contents")
if inCalibre: if inCalibre:
from calibre_plugins.dedrm import genbook from calibre_plugins.dedrm import genbook
else: else:
@ -333,7 +333,7 @@ class TopazBook:
rv = genbook.generateBook(self.outdir, raw, fixedimage) rv = genbook.generateBook(self.outdir, raw, fixedimage)
if rv == 0: if rv == 0:
print(u"Book Successfully generated.") print("Book Successfully generated.")
return rv return rv
# try each pid to decode the file # try each pid to decode the file
@ -341,7 +341,7 @@ class TopazBook:
for pid in pidlst: for pid in pidlst:
# use 8 digit pids here # use 8 digit pids here
pid = pid[0:8] pid = pid[0:8]
print(u"Trying: {0}".format(pid)) print("Trying: {0}".format(pid))
bookKeys = [] bookKeys = []
data = keydata data = keydata
try: try:
@ -350,16 +350,16 @@ class TopazBook:
pass pass
else: else:
bookKey = bookKeys[0] bookKey = bookKeys[0]
print(u"Book Key Found! ({0})".format(bookKey.encode('hex'))) print("Book Key Found! ({0})".format(bookKey.encode('hex')))
break break
if not bookKey: if not bookKey:
raise DrmException(u"No key found in {0:d} keys tried. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(len(pidlst))) raise DrmException("No key found in {0:d} keys tried. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(len(pidlst)))
self.setBookKey(bookKey) self.setBookKey(bookKey)
self.createBookDirectory() self.createBookDirectory()
self.extractFiles() self.extractFiles()
print(u"Successfully Extracted Topaz contents") print("Successfully Extracted Topaz contents")
if inCalibre: if inCalibre:
from calibre_plugins.dedrm import genbook from calibre_plugins.dedrm import genbook
else: else:
@ -367,7 +367,7 @@ class TopazBook:
rv = genbook.generateBook(self.outdir, raw, fixedimage) rv = genbook.generateBook(self.outdir, raw, fixedimage)
if rv == 0: if rv == 0:
print(u"Book Successfully generated") print("Book Successfully generated")
return rv return rv
def createBookDirectory(self): def createBookDirectory(self):
@ -375,16 +375,16 @@ class TopazBook:
# create output directory structure # create output directory structure
if not os.path.exists(outdir): if not os.path.exists(outdir):
os.makedirs(outdir) os.makedirs(outdir)
destdir = os.path.join(outdir,u"img") destdir = os.path.join(outdir,"img")
if not os.path.exists(destdir): if not os.path.exists(destdir):
os.makedirs(destdir) os.makedirs(destdir)
destdir = os.path.join(outdir,u"color_img") destdir = os.path.join(outdir,"color_img")
if not os.path.exists(destdir): if not os.path.exists(destdir):
os.makedirs(destdir) os.makedirs(destdir)
destdir = os.path.join(outdir,u"page") destdir = os.path.join(outdir,"page")
if not os.path.exists(destdir): if not os.path.exists(destdir):
os.makedirs(destdir) os.makedirs(destdir)
destdir = os.path.join(outdir,u"glyphs") destdir = os.path.join(outdir,"glyphs")
if not os.path.exists(destdir): if not os.path.exists(destdir):
os.makedirs(destdir) os.makedirs(destdir)
@ -393,49 +393,49 @@ class TopazBook:
for headerRecord in self.bookHeaderRecords: for headerRecord in self.bookHeaderRecords:
name = headerRecord name = headerRecord
if name != 'dkey': if name != 'dkey':
ext = u".dat" ext = ".dat"
if name == 'img': ext = u".jpg" if name == 'img': ext = ".jpg"
if name == 'color' : ext = u".jpg" if name == 'color' : ext = ".jpg"
print(u"Processing Section: {0}\n. . .".format(name), end=' ') print("Processing Section: {0}\n. . .".format(name), end=' ')
for index in range (0,len(self.bookHeaderRecords[name])) : for index in range (0,len(self.bookHeaderRecords[name])) :
fname = u"{0}{1:04d}{2}".format(name,index,ext) fname = "{0}{1:04d}{2}".format(name,index,ext)
destdir = outdir destdir = outdir
if name == 'img': if name == 'img':
destdir = os.path.join(outdir,u"img") destdir = os.path.join(outdir,"img")
if name == 'color': if name == 'color':
destdir = os.path.join(outdir,u"color_img") destdir = os.path.join(outdir,"color_img")
if name == 'page': if name == 'page':
destdir = os.path.join(outdir,u"page") destdir = os.path.join(outdir,"page")
if name == 'glyphs': if name == 'glyphs':
destdir = os.path.join(outdir,u"glyphs") destdir = os.path.join(outdir,"glyphs")
outputFile = os.path.join(destdir,fname) outputFile = os.path.join(destdir,fname)
print(u".", end=' ') print(".", end=' ')
record = self.getBookPayloadRecord(name,index) record = self.getBookPayloadRecord(name,index)
if record != '': if record != '':
open(outputFile, 'wb').write(record) open(outputFile, 'wb').write(record)
print(u" ") print(" ")
def getFile(self, zipname): def getFile(self, zipname):
htmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) htmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
htmlzip.write(os.path.join(self.outdir,u"book.html"),u"book.html") htmlzip.write(os.path.join(self.outdir,"book.html"),"book.html")
htmlzip.write(os.path.join(self.outdir,u"book.opf"),u"book.opf") htmlzip.write(os.path.join(self.outdir,"book.opf"),"book.opf")
if os.path.isfile(os.path.join(self.outdir,u"cover.jpg")): if os.path.isfile(os.path.join(self.outdir,"cover.jpg")):
htmlzip.write(os.path.join(self.outdir,u"cover.jpg"),u"cover.jpg") htmlzip.write(os.path.join(self.outdir,"cover.jpg"),"cover.jpg")
htmlzip.write(os.path.join(self.outdir,u"style.css"),u"style.css") htmlzip.write(os.path.join(self.outdir,"style.css"),"style.css")
zipUpDir(htmlzip, self.outdir, u"img") zipUpDir(htmlzip, self.outdir, "img")
htmlzip.close() htmlzip.close()
def getBookType(self): def getBookType(self):
return u"Topaz" return "Topaz"
def getBookExtension(self): def getBookExtension(self):
return u".htmlz" return ".htmlz"
def getSVGZip(self, zipname): def getSVGZip(self, zipname):
svgzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) svgzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
svgzip.write(os.path.join(self.outdir,u"index_svg.xhtml"),u"index_svg.xhtml") svgzip.write(os.path.join(self.outdir,"index_svg.xhtml"),"index_svg.xhtml")
zipUpDir(svgzip, self.outdir, u"svg") zipUpDir(svgzip, self.outdir, "svg")
zipUpDir(svgzip, self.outdir, u"img") zipUpDir(svgzip, self.outdir, "img")
svgzip.close() svgzip.close()
def cleanup(self): def cleanup(self):
@ -443,20 +443,20 @@ class TopazBook:
shutil.rmtree(self.outdir, True) shutil.rmtree(self.outdir, True)
def usage(progname): def usage(progname):
print(u"Removes DRM protection from Topaz ebooks and extracts the contents") print("Removes DRM protection from Topaz ebooks and extracts the contents")
print(u"Usage:") print("Usage:")
print(u" {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] <infile> <outdir>".format(progname)) print(" {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] <infile> <outdir>".format(progname))
# Main # Main
def cli_main(): def cli_main():
argv=unicode_argv() argv=unicode_argv()
progname = os.path.basename(argv[0]) progname = os.path.basename(argv[0])
print(u"TopazExtract v{0}.".format(__version__)) print("TopazExtract v{0}.".format(__version__))
try: try:
opts, args = getopt.getopt(argv[1:], "k:p:s:x") opts, args = getopt.getopt(argv[1:], "k:p:s:x")
except getopt.GetoptError as err: except getopt.GetoptError as err:
print(u"Error in options or arguments: {0}".format(err.args[0])) print("Error in options or arguments: {0}".format(err.args[0]))
usage(progname) usage(progname)
return 1 return 1
if len(args)<2: if len(args)<2:
@ -466,11 +466,11 @@ def cli_main():
infile = args[0] infile = args[0]
outdir = args[1] outdir = args[1]
if not os.path.isfile(infile): if not os.path.isfile(infile):
print(u"Input File {0} Does Not Exist.".format(infile)) print("Input File {0} Does Not Exist.".format(infile))
return 1 return 1
if not os.path.exists(outdir): if not os.path.exists(outdir):
print(u"Output Directory {0} Does Not Exist.".format(outdir)) print("Output Directory {0} Does Not Exist.".format(outdir))
return 1 return 1
kDatabaseFiles = [] kDatabaseFiles = []
@ -495,27 +495,27 @@ def cli_main():
tb = TopazBook(infile) tb = TopazBook(infile)
title = tb.getBookTitle() title = tb.getBookTitle()
print(u"Processing Book: {0}".format(title)) print("Processing Book: {0}".format(title))
md1, md2 = tb.getPIDMetaInfo() md1, md2 = tb.getPIDMetaInfo()
pids.extend(kgenpids.getPidList(md1, md2, serials, kDatabaseFiles)) pids.extend(kgenpids.getPidList(md1, md2, serials, kDatabaseFiles))
try: try:
print(u"Decrypting Book") print("Decrypting Book")
tb.processBook(pids) tb.processBook(pids)
print(u" Creating HTML ZIP Archive") print(" Creating HTML ZIP Archive")
zipname = os.path.join(outdir, bookname + u"_nodrm.htmlz") zipname = os.path.join(outdir, bookname + "_nodrm.htmlz")
tb.getFile(zipname) tb.getFile(zipname)
print(u" Creating SVG ZIP Archive") print(" Creating SVG ZIP Archive")
zipname = os.path.join(outdir, bookname + u"_SVG.zip") zipname = os.path.join(outdir, bookname + "_SVG.zip")
tb.getSVGZip(zipname) tb.getSVGZip(zipname)
# removing internal temporary directory of pieces # removing internal temporary directory of pieces
tb.cleanup() tb.cleanup()
except DrmException as e: except DrmException as e:
print(u"Decryption failed\n{0}".format(traceback.format_exc())) print("Decryption failed\n{0}".format(traceback.format_exc()))
try: try:
tb.cleanup() tb.cleanup()
@ -524,7 +524,7 @@ def cli_main():
return 1 return 1
except Exception as e: except Exception as e:
print(u"Decryption failed\n{0}".format(traceback.format_exc())) print("Decryption failed\n{0}".format(traceback.format_exc()))
try: try:
tb.cleanup() tb.cleanup()
except: except:

@ -1,8 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement
from calibre_plugins.dedrm.ignoblekeygen import generate_key from calibre_plugins.dedrm.ignoblekeygen import generate_key
__license__ = 'GPL v3' __license__ = 'GPL v3'

@ -1,9 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement
from __future__ import print_function
__license__ = 'GPL v3' __license__ = 'GPL v3'
# Standard Python modules. # Standard Python modules.
@ -17,13 +14,13 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
import subasyncio import subasyncio
from subasyncio import Process from subasyncio import Process
if extension == u".k4i": if extension == ".k4i":
import json import json
basepath, script = os.path.split(scriptpath) basepath, script = os.path.split(scriptpath)
print(u"{0} v{1}: Running {2} under Wine".format(PLUGIN_NAME, PLUGIN_VERSION, script)) print("{0} v{1}: Running {2} under Wine".format(PLUGIN_NAME, PLUGIN_VERSION, script))
outdirpath = os.path.join(basepath, u"winekeysdir") outdirpath = os.path.join(basepath, "winekeysdir")
if not os.path.exists(outdirpath): if not os.path.exists(outdirpath):
os.makedirs(outdirpath) os.makedirs(outdirpath)
@ -31,29 +28,29 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
wineprefix = os.path.abspath(os.path.expanduser(os.path.expandvars(wineprefix))) wineprefix = os.path.abspath(os.path.expanduser(os.path.expandvars(wineprefix)))
if wineprefix != "" and os.path.exists(wineprefix): if wineprefix != "" and os.path.exists(wineprefix):
cmdline = u"WINEPREFIX=\"{2}\" wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix) cmdline = "WINEPREFIX=\"{2}\" wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
else: else:
cmdline = u"wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath) cmdline = "wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath)
print(u"{0} v{1}: Command line: '{2}'".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline)) print("{0} v{1}: Command line: '{2}'".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline))
try: try:
cmdline = cmdline.encode(sys.getfilesystemencoding()) cmdline = cmdline.encode(sys.getfilesystemencoding())
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False) p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False)
result = p2.wait("wait") result = p2.wait("wait")
except Exception as e: except Exception as e:
print(u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])) print("{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0]))
if wineprefix != "" and os.path.exists(wineprefix): if wineprefix != "" and os.path.exists(wineprefix):
cmdline = u"WINEPREFIX=\"{2}\" wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix) cmdline = "WINEPREFIX=\"{2}\" wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
else: else:
cmdline = u"wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath) cmdline = "wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath)
print(u"{0} v{1}: Command line: “{2}".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline)) print("{0} v{1}: Command line: “{2}".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline))
try: try:
cmdline = cmdline.encode(sys.getfilesystemencoding()) cmdline = cmdline.encode(sys.getfilesystemencoding())
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False) p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False)
result = p2.wait("wait") result = p2.wait("wait")
except Exception as e: except Exception as e:
print(u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])) print("{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0]))
# try finding winekeys anyway, even if above code errored # try finding winekeys anyway, even if above code errored
winekeys = [] winekeys = []
@ -63,14 +60,14 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
try: try:
fpath = os.path.join(outdirpath, filename) fpath = os.path.join(outdirpath, filename)
with open(fpath, 'rb') as keyfile: with open(fpath, 'rb') as keyfile:
if extension == u".k4i": if extension == ".k4i":
new_key_value = json.loads(keyfile.read()) new_key_value = json.loads(keyfile.read())
else: else:
new_key_value = keyfile.read() new_key_value = keyfile.read()
winekeys.append(new_key_value) winekeys.append(new_key_value)
except: except:
print(u"{0} v{1}: Error loading file {2}".format(PLUGIN_NAME, PLUGIN_VERSION, filename)) print("{0} v{1}: Error loading file {2}".format(PLUGIN_NAME, PLUGIN_VERSION, filename))
traceback.print_exc() traceback.print_exc()
os.remove(fpath) os.remove(fpath)
print(u"{0} v{1}: Found and decrypted {2} {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(winekeys), u"key file" if len(winekeys) == 1 else u"key files")) print("{0} v{1}: Found and decrypted {2} {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(winekeys), "key file" if len(winekeys) == 1 else "key files"))
return winekeys return winekeys

@ -1,3 +1,6 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
""" """
Read and write ZIP files. Read and write ZIP files.
""" """
@ -824,8 +827,8 @@ class ZipFile:
def open(self, name, mode="r", pwd=None): def open(self, name, mode="r", pwd=None):
"""Return file-like object for 'name'.""" """Return file-like object for 'name'."""
if mode not in ("r", "U", "rU"): if mode not in ("r", "", "rU"):
raise RuntimeError('open() requires mode "r", "U", or "rU"') raise RuntimeError('open() requires mode "r", "", or "rU"')
if not self.fp: if not self.fp:
raise RuntimeError( raise RuntimeError(
"Attempt to read ZIP archive that was already closed") "Attempt to read ZIP archive that was already closed")

@ -1,8 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# zipfix.py # zipfix.py
# Copyright © 2010-2020 by some_updates, DiapDealer and Apprentice Alf # Copyright © 2010-2020 by Apprentice Harper et al.
# Released under the terms of the GNU General Public Licence, version 3 # Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/> # <http://www.gnu.org/licenses/>
@ -10,7 +10,7 @@
# Revision history: # Revision history:
# 1.0 - Initial release # 1.0 - Initial release
# 1.1 - Updated to handle zip file metadata correctly # 1.1 - Updated to handle zip file metadata correctly
# 2.0 - Added Python 3 compatibility for calibre 5.0 # 2.0 - Python 3 for calibre 5.0
""" """
Re-write zip (or ePub) fixing problems with file names (and mimetype entry). Re-write zip (or ePub) fixing problems with file names (and mimetype entry).

@ -1,8 +1,8 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3' _license__ = 'GPL v3'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'

@ -1,7 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*-
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2012, David Forrester <davidfor@internode.on.net>' __copyright__ = '2012, David Forrester <davidfor@internode.on.net>'
@ -9,13 +8,7 @@ __docformat__ = 'restructuredtext en'
import os, time, re, sys import os, time, re, sys
from datetime import datetime from datetime import datetime
try: from PyQt5.Qt import (Qt, QIcon, QPixmap, QLabel, QDialog, QHBoxLayout, QProgressBar,
from PyQt5.Qt import (Qt, QIcon, QPixmap, QLabel, QDialog, QHBoxLayout, QProgressBar,
QTableWidgetItem, QFont, QLineEdit, QComboBox,
QVBoxLayout, QDialogButtonBox, QStyledItemDelegate, QDateTime,
QRegExpValidator, QRegExp, QDate, QDateEdit)
except ImportError:
from PyQt4.Qt import (Qt, QIcon, QPixmap, QLabel, QDialog, QHBoxLayout, QProgressBar,
QTableWidgetItem, QFont, QLineEdit, QComboBox, QTableWidgetItem, QFont, QLineEdit, QComboBox,
QVBoxLayout, QDialogButtonBox, QStyledItemDelegate, QDateTime, QVBoxLayout, QDialogButtonBox, QStyledItemDelegate, QDateTime,
QRegExpValidator, QRegExp, QDate, QDateEdit) QRegExpValidator, QRegExp, QDate, QDateEdit)

@ -1,16 +1,10 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai #!/usr/bin/env python3
from __future__ import (unicode_literals, division, absolute_import, # -*- coding: utf-8 -*-
print_function)
try: # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from PyQt5.Qt import (Qt, QGroupBox, QListWidget, QLineEdit, QDialogButtonBox, QWidget, QLabel, QDialog, QVBoxLayout, QAbstractItemView, QIcon, QHBoxLayout, QComboBox, QListWidgetItem, QFileDialog)
except ImportError:
from PyQt4.Qt import (Qt, QGroupBox, QListWidget, QLineEdit, QDialogButtonBox, QWidget, QLabel, QDialog, QVBoxLayout, QAbstractItemView, QIcon, QHBoxLayout, QComboBox, QListWidgetItem, QFileDialog)
try: from PyQt5.Qt import (Qt, QGroupBox, QListWidget, QLineEdit, QDialogButtonBox, QWidget, QLabel, QDialog, QVBoxLayout, QAbstractItemView, QIcon, QHBoxLayout, QComboBox, QListWidgetItem, QFileDialog)
from PyQt5 import Qt as QtGui from PyQt5 import Qt as QtGui
except ImportError:
from PyQt4 import QtGui
from calibre.gui2 import (error_dialog, question_dialog, info_dialog, open_url) from calibre.gui2 import (error_dialog, question_dialog, info_dialog, open_url)
from calibre.utils.config import JSONConfig, config_dir from calibre.utils.config import JSONConfig, config_dir
@ -50,25 +44,25 @@ class ConfigWidget(QWidget):
self.find_homes.setCurrentIndex(index) self.find_homes.setCurrentIndex(index)
self.serials_button = QtGui.QPushButton(self) self.serials_button = QtGui.QPushButton(self)
self.serials_button.setToolTip(_(u"Click to manage Kobo serial numbers for Kobo ebooks")) self.serials_button.setToolTip(_("Click to manage Kobo serial numbers for Kobo ebooks"))
self.serials_button.setText(u"Kobo devices serials") self.serials_button.setText("Kobo devices serials")
self.serials_button.clicked.connect(self.edit_serials) self.serials_button.clicked.connect(self.edit_serials)
layout.addWidget(self.serials_button) layout.addWidget(self.serials_button)
self.kobo_directory_button = QtGui.QPushButton(self) self.kobo_directory_button = QtGui.QPushButton(self)
self.kobo_directory_button.setToolTip(_(u"Click to specify the Kobo directory")) self.kobo_directory_button.setToolTip(_("Click to specify the Kobo directory"))
self.kobo_directory_button.setText(u"Kobo directory") self.kobo_directory_button.setText("Kobo directory")
self.kobo_directory_button.clicked.connect(self.edit_kobo_directory) self.kobo_directory_button.clicked.connect(self.edit_kobo_directory)
layout.addWidget(self.kobo_directory_button) layout.addWidget(self.kobo_directory_button)
def edit_serials(self): def edit_serials(self):
d = ManageKeysDialog(self,u"Kobo device serial number",self.tmpserials, AddSerialDialog) d = ManageKeysDialog(self,"Kobo device serial number",self.tmpserials, AddSerialDialog)
d.exec_() d.exec_()
def edit_kobo_directory(self): def edit_kobo_directory(self):
tmpkobodirectory = QFileDialog.getExistingDirectory(self, u"Select Kobo directory", self.kobodirectory or "/home", QFileDialog.ShowDirsOnly) tmpkobodirectory = QFileDialog.getExistingDirectory(self, "Select Kobo directory", self.kobodirectory or "/home", QFileDialog.ShowDirsOnly)
if tmpkobodirectory != u"" and tmpkobodirectory is not None: if tmpkobodirectory != u"" and tmpkobodirectory is not None:
self.kobodirectory = tmpkobodirectory self.kobodirectory = tmpkobodirectory
@ -91,7 +85,7 @@ class ManageKeysDialog(QDialog):
self.plugin_keys = plugin_keys self.plugin_keys = plugin_keys
self.create_key = create_key self.create_key = create_key
self.keyfile_ext = keyfile_ext self.keyfile_ext = keyfile_ext
self.json_file = (keyfile_ext == u"k4i") self.json_file = (keyfile_ext == "k4i")
self.setWindowTitle("{0} {1}: Manage {2}s".format(PLUGIN_NAME, PLUGIN_VERSION, self.key_type_name)) self.setWindowTitle("{0} {1}: Manage {2}s".format(PLUGIN_NAME, PLUGIN_VERSION, self.key_type_name))
@ -99,13 +93,13 @@ class ManageKeysDialog(QDialog):
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
self.setLayout(layout) self.setLayout(layout)
keys_group_box = QGroupBox(_(u"{0}s".format(self.key_type_name)), self) keys_group_box = QGroupBox(_("{0}s".format(self.key_type_name)), self)
layout.addWidget(keys_group_box) layout.addWidget(keys_group_box)
keys_group_box_layout = QHBoxLayout() keys_group_box_layout = QHBoxLayout()
keys_group_box.setLayout(keys_group_box_layout) keys_group_box.setLayout(keys_group_box_layout)
self.listy = QListWidget(self) self.listy = QListWidget(self)
self.listy.setToolTip(u"{0}s that will be used to decrypt ebooks".format(self.key_type_name)) self.listy.setToolTip("{0}s that will be used to decrypt ebooks".format(self.key_type_name))
self.listy.setSelectionMode(QAbstractItemView.SingleSelection) self.listy.setSelectionMode(QAbstractItemView.SingleSelection)
self.populate_list() self.populate_list()
keys_group_box_layout.addWidget(self.listy) keys_group_box_layout.addWidget(self.listy)
@ -114,12 +108,12 @@ class ManageKeysDialog(QDialog):
keys_group_box_layout.addLayout(button_layout) keys_group_box_layout.addLayout(button_layout)
self._add_key_button = QtGui.QToolButton(self) self._add_key_button = QtGui.QToolButton(self)
self._add_key_button.setIcon(QIcon(I('plus.png'))) self._add_key_button.setIcon(QIcon(I('plus.png')))
self._add_key_button.setToolTip(u"Create new {0}".format(self.key_type_name)) self._add_key_button.setToolTip("Create new {0}".format(self.key_type_name))
self._add_key_button.clicked.connect(self.add_key) self._add_key_button.clicked.connect(self.add_key)
button_layout.addWidget(self._add_key_button) button_layout.addWidget(self._add_key_button)
self._delete_key_button = QtGui.QToolButton(self) self._delete_key_button = QtGui.QToolButton(self)
self._delete_key_button.setToolTip(_(u"Delete highlighted key")) self._delete_key_button.setToolTip(_("Delete highlighted key"))
self._delete_key_button.setIcon(QIcon(I('list_remove.png'))) self._delete_key_button.setIcon(QIcon(I('list_remove.png')))
self._delete_key_button.clicked.connect(self.delete_key) self._delete_key_button.clicked.connect(self.delete_key)
button_layout.addWidget(self._delete_key_button) button_layout.addWidget(self._delete_key_button)
@ -155,7 +149,7 @@ class ManageKeysDialog(QDialog):
new_key_value = d.key_value new_key_value = d.key_value
if new_key_value in self.plugin_keys: if new_key_value in self.plugin_keys:
info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name), info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
u"This {0} is already in the list of {0}s has not been added.".format(self.key_type_name), show=True) "This {0} is already in the list of {0}s has not been added.".format(self.key_type_name), show=True)
return return
self.plugin_keys.append(d.key_value) self.plugin_keys.append(d.key_value)
@ -166,7 +160,7 @@ class ManageKeysDialog(QDialog):
if not self.listy.currentItem(): if not self.listy.currentItem():
return return
keyname = self.listy.currentItem().text() keyname = self.listy.currentItem().text()
if not question_dialog(self, "{0} {1}: Confirm Delete".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to delete the {1} <strong>{0}</strong>?".format(keyname, self.key_type_name), show_copy_button=False, default_yes=False): if not question_dialog(self, "{0} {1}: Confirm Delete".format(PLUGIN_NAME, PLUGIN_VERSION), "Do you really want to delete the {1} <strong>{0}</strong>?".format(keyname, self.key_type_name), show_copy_button=False, default_yes=False):
return return
self.plugin_keys.remove(keyname) self.plugin_keys.remove(keyname)
@ -177,7 +171,7 @@ class AddSerialDialog(QDialog):
def __init__(self, parent=None,): def __init__(self, parent=None,):
QDialog.__init__(self, parent) QDialog.__init__(self, parent)
self.parent = parent self.parent = parent
self.setWindowTitle(u"{0} {1}: Add New eInk Kobo Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION)) self.setWindowTitle("{0} {1}: Add New eInk Kobo Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION))
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
self.setLayout(layout) self.setLayout(layout)
@ -188,9 +182,9 @@ class AddSerialDialog(QDialog):
key_group = QHBoxLayout() key_group = QHBoxLayout()
data_group_box_layout.addLayout(key_group) data_group_box_layout.addLayout(key_group)
key_group.addWidget(QLabel(u"EInk Kobo Serial Number:", self)) key_group.addWidget(QLabel("EInk Kobo Serial Number:", self))
self.key_ledit = QLineEdit("", self) self.key_ledit = QLineEdit("", self)
self.key_ledit.setToolTip(u"Enter an eInk Kobo serial number. EInk Kobo serial numbers are 13 characters long and usually start with a 'N'. Kobo Serial Numbers are case-sensitive, so be sure to enter the upper and lower case letters unchanged.") self.key_ledit.setToolTip("Enter an eInk Kobo serial number. EInk Kobo serial numbers are 13 characters long and usually start with a 'N'. Kobo Serial Numbers are case-sensitive, so be sure to enter the upper and lower case letters unchanged.")
key_group.addWidget(self.key_ledit) key_group.addWidget(self.key_ledit)
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
@ -210,9 +204,9 @@ class AddSerialDialog(QDialog):
def accept(self): def accept(self):
if len(self.key_name) == 0 or self.key_name.isspace(): if len(self.key_name) == 0 or self.key_name.isspace():
errmsg = u"Please enter an eInk Kindle Serial Number or click Cancel in the dialog." errmsg = "Please enter an eInk Kindle Serial Number or click Cancel in the dialog."
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
if len(self.key_name) != 13: if len(self.key_name) != 13:
errmsg = u"EInk Kobo Serial Numbers must be 13 characters long. This is {0:d} characters long.".format(len(self.key_name)) errmsg = "EInk Kobo Serial Numbers must be 13 characters long. This is {0:d} characters long.".format(len(self.key_name))
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
QDialog.accept(self) QDialog.accept(self)

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Version 4.0.0 September 2020 # Version 4.0.0 September 2020
@ -156,7 +156,7 @@
from __future__ import print_function from __future__ import print_function
__version__ = '4.0.0' __version__ = '4.0.0'
__about__ = u"Obok v{0}\nCopyright © 2012-2020 Physisticated et al.".format(__version__) __about__ = "Obok v{0}\nCopyright © 2012-2020 Physisticated et al.".format(__version__)
import sys import sys
import os import os
@ -176,10 +176,10 @@ import tempfile
can_parse_xml = True can_parse_xml = True
try: try:
from xml.etree import ElementTree as ET from xml.etree import ElementTree as ET
# print u"using xml.etree for xml parsing" # print "using xml.etree for xml parsing"
except ImportError: except ImportError:
can_parse_xml = False can_parse_xml = False
# print u"Cannot find xml.etree, disabling extraction of serial numbers" # print "Cannot find xml.etree, disabling extraction of serial numbers"
# List of all known hash keys # List of all known hash keys
KOBO_HASH_KEYS = ['88b3a2e13', 'XzUhGYdFp', 'NoCanLook','QJhwzAtXL'] KOBO_HASH_KEYS = ['88b3a2e13', 'XzUhGYdFp', 'NoCanLook','QJhwzAtXL']
@ -279,10 +279,10 @@ class SafeUnbuffered:
if self.encoding == None: if self.encoding == None:
self.encoding = "utf-8" self.encoding = "utf-8"
def write(self, data): def write(self, data):
if isinstance(data,bytes): if isinstance(data,str):
data = data.encode(self.encoding,"replace") data = data.encode(self.encoding,"replace")
self.stream.write(data) self.stream.buffer.write(data)
self.stream.flush() self.stream.buffer.flush()
def __getattr__(self, attr): def __getattr__(self, attr):
return getattr(self.stream, attr) return getattr(self.stream, attr)
@ -312,9 +312,9 @@ class KoboLibrary(object):
# step 1. check whether this looks like a real device # step 1. check whether this looks like a real device
if (device_path): if (device_path):
# we got a device path # we got a device path
self.kobodir = os.path.join(device_path, u".kobo") self.kobodir = os.path.join(device_path, ".kobo")
# devices use KoboReader.sqlite # devices use KoboReader.sqlite
kobodb = os.path.join(self.kobodir, u"KoboReader.sqlite") kobodb = os.path.join(self.kobodir, "KoboReader.sqlite")
if (not(os.path.isfile(kobodb))): if (not(os.path.isfile(kobodb))):
# device path seems to be wrong, unset it # device path seems to be wrong, unset it
device_path = u"" device_path = u""
@ -326,22 +326,22 @@ class KoboLibrary(object):
if (len(serials) == 0): if (len(serials) == 0):
# we got a device path but no saved serial # we got a device path but no saved serial
# try to get the serial from the device # try to get the serial from the device
# print u"get_device_settings - device_path = {0}".format(device_path) # print "get_device_settings - device_path = {0}".format(device_path)
# get serial from device_path/.adobe-digital-editions/device.xml # get serial from device_path/.adobe-digital-editions/device.xml
if can_parse_xml: if can_parse_xml:
devicexml = os.path.join(device_path, '.adobe-digital-editions', 'device.xml') devicexml = os.path.join(device_path, '.adobe-digital-editions', 'device.xml')
# print u"trying to load {0}".format(devicexml) # print "trying to load {0}".format(devicexml)
if (os.path.exists(devicexml)): if (os.path.exists(devicexml)):
# print u"trying to parse {0}".format(devicexml) # print "trying to parse {0}".format(devicexml)
xmltree = ET.parse(devicexml) xmltree = ET.parse(devicexml)
for node in xmltree.iter(): for node in xmltree.iter():
if "deviceSerial" in node.tag: if "deviceSerial" in node.tag:
serial = node.text serial = node.text
# print u"found serial {0}".format(serial) # print "found serial {0}".format(serial)
serials.append(serial) serials.append(serial)
break break
else: else:
# print u"cannot get serials from device." # print "cannot get serials from device."
device_path = u"" device_path = u""
self.kobodir = u"" self.kobodir = u""
kobodb = u"" kobodb = u""
@ -357,19 +357,19 @@ class KoboLibrary(object):
if sys.getwindowsversion().major > 5: if sys.getwindowsversion().major > 5:
if 'LOCALAPPDATA' in os.environ.keys(): if 'LOCALAPPDATA' in os.environ.keys():
# Python 2.x does not return unicode env. Use Python 3.x # Python 2.x does not return unicode env. Use Python 3.x
self.kobodir = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%") self.kobodir = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%")
if (self.kobodir == u""): if (self.kobodir == u""):
if 'USERPROFILE' in os.environ.keys(): if 'USERPROFILE' in os.environ.keys():
# Python 2.x does not return unicode env. Use Python 3.x # Python 2.x does not return unicode env. Use Python 3.x
self.kobodir = os.path.join(winreg.ExpandEnvironmentStrings(u"%USERPROFILE%"), u"Local Settings", u"Application Data") self.kobodir = os.path.join(winreg.ExpandEnvironmentStrings("%USERPROFILE%"), "Local Settings", "Application Data")
self.kobodir = os.path.join(self.kobodir, u"Kobo", u"Kobo Desktop Edition") self.kobodir = os.path.join(self.kobodir, "Kobo", "Kobo Desktop Edition")
elif sys.platform.startswith('darwin'): elif sys.platform.startswith('darwin'):
self.kobodir = os.path.join(os.environ['HOME'], u"Library", u"Application Support", u"Kobo", u"Kobo Desktop Edition") self.kobodir = os.path.join(os.environ['HOME'], "Library", "Application Support", "Kobo", "Kobo Desktop Edition")
#elif linux_path != None: #elif linux_path != None:
# Probably Linux, let's get the wine prefix and path to Kobo. # Probably Linux, let's get the wine prefix and path to Kobo.
# self.kobodir = os.path.join(linux_path, u"Local Settings", u"Application Data", u"Kobo", u"Kobo Desktop Edition") # self.kobodir = os.path.join(linux_path, "Local Settings", "Application Data", "Kobo", "Kobo Desktop Edition")
# desktop versions use Kobo.sqlite # desktop versions use Kobo.sqlite
kobodb = os.path.join(self.kobodir, u"Kobo.sqlite") kobodb = os.path.join(self.kobodir, "Kobo.sqlite")
# check for existence of file # check for existence of file
if (not(os.path.isfile(kobodb))): if (not(os.path.isfile(kobodb))):
# give up here, we haven't found anything useful # give up here, we haven't found anything useful
@ -377,7 +377,7 @@ class KoboLibrary(object):
kobodb = u"" kobodb = u""
if (self.kobodir != u""): if (self.kobodir != u""):
self.bookdir = os.path.join(self.kobodir, u"kepub") self.bookdir = os.path.join(self.kobodir, "kepub")
# make a copy of the database in a temporary file # make a copy of the database in a temporary file
# so we can ensure it's not using WAL logging which sqlite3 can't do. # so we can ensure it's not using WAL logging which sqlite3 can't do.
self.newdb = tempfile.NamedTemporaryFile(mode='wb', delete=False) self.newdb = tempfile.NamedTemporaryFile(mode='wb', delete=False)
@ -437,7 +437,7 @@ class KoboLibrary(object):
def __bookfile (self, volumeid): def __bookfile (self, volumeid):
"""The filename needed to open a given book.""" """The filename needed to open a given book."""
return os.path.join(self.kobodir, u"kepub", volumeid) return os.path.join(self.kobodir, "kepub", volumeid)
def __getmacaddrs (self): def __getmacaddrs (self):
"""The list of all MAC addresses on this machine.""" """The list of all MAC addresses on this machine."""
@ -454,7 +454,7 @@ class KoboLibrary(object):
output = subprocess.check_output('/sbin/ifconfig -a', shell=True) output = subprocess.check_output('/sbin/ifconfig -a', shell=True)
matches = c.findall(output) matches = c.findall(output)
for m in matches: for m in matches:
# print u"m:{0}".format(m[0]) # print "m:{0}".format(m[0])
macaddrs.append(m[0].upper()) macaddrs.append(m[0].upper())
else: else:
# probably linux # probably linux
@ -607,32 +607,32 @@ class KoboFile(object):
# assume utf-8 with no BOM # assume utf-8 with no BOM
textoffset = 0 textoffset = 0
stride = 1 stride = 1
print(u"Checking text:{0}:".format(contents[:10])) print("Checking text:{0}:".format(contents[:10]))
# check for byte order mark # check for byte order mark
if contents[:3]==b"\xef\xbb\xbf": if contents[:3]==b"\xef\xbb\xbf":
# seems to be utf-8 with BOM # seems to be utf-8 with BOM
print(u"Could be utf-8 with BOM") print("Could be utf-8 with BOM")
textoffset = 3 textoffset = 3
elif contents[:2]==b"\xfe\xff": elif contents[:2]==b"\xfe\xff":
# seems to be utf-16BE # seems to be utf-16BE
print(u"Could be utf-16BE") print("Could be utf-16BE")
textoffset = 3 textoffset = 3
stride = 2 stride = 2
elif contents[:2]==b"\xff\xfe": elif contents[:2]==b"\xff\xfe":
# seems to be utf-16LE # seems to be utf-16LE
print(u"Could be utf-16LE") print("Could be utf-16LE")
textoffset = 2 textoffset = 2
stride = 2 stride = 2
else: else:
print(u"Perhaps utf-8 without BOM") print("Perhaps utf-8 without BOM")
# now check that the first few characters are in the ASCII range # now check that the first few characters are in the ASCII range
for i in range(textoffset,textoffset+5*stride,stride): for i in range(textoffset,textoffset+5*stride,stride):
if contents[i]<32 or contents[i]>127: if contents[i]<32 or contents[i]>127:
# Non-ascii, so decryption probably failed # Non-ascii, so decryption probably failed
print(u"Bad character at {0}, value {1}".format(i,contents[i])) print("Bad character at {0}, value {1}".format(i,contents[i]))
raise ValueError raise ValueError
print(u"Seems to be good text") print("Seems to be good text")
return True return True
if contents[:5]==b"<?xml" or contents[:8]==b"\xef\xbb\xbf<?xml": if contents[:5]==b"<?xml" or contents[:8]==b"\xef\xbb\xbf<?xml":
# utf-8 # utf-8
@ -653,13 +653,13 @@ class KoboFile(object):
# utf-16LE of weird <!DOCTYPE start # utf-16LE of weird <!DOCTYPE start
return True return True
else: else:
print(u"Bad XML: {0}".format(contents[:8])) print("Bad XML: {0}".format(contents[:8]))
raise ValueError raise ValueError
elif self.mimetype == 'image/jpeg': elif self.mimetype == 'image/jpeg':
if contents[:3] == b'\xff\xd8\xff': if contents[:3] == b'\xff\xd8\xff':
return True return True
else: else:
print(u"Bad JPEG: {0}".format(contents[:3].hex())) print("Bad JPEG: {0}".format(contents[:3].hex()))
raise ValueError() raise ValueError()
return False return False
@ -682,18 +682,18 @@ class KoboFile(object):
return contents return contents
def decrypt_book(book, lib): def decrypt_book(book, lib):
print(u"Converting {0}".format(book.title)) print("Converting {0}".format(book.title))
zin = zipfile.ZipFile(book.filename, "r") zin = zipfile.ZipFile(book.filename, "r")
# make filename out of Unicode alphanumeric and whitespace equivalents from title # make filename out of Unicode alphanumeric and whitespace equivalents from title
outname = u"{0}.epub".format(re.sub('[^\s\w]', '_', book.title, 0, re.UNICODE)) outname = "{0}.epub".format(re.sub('[^\s\w]', '_', book.title, 0, re.UNICODE))
if (book.type == 'drm-free'): if (book.type == 'drm-free'):
print(u"DRM-free book, conversion is not needed") print("DRM-free book, conversion is not needed")
shutil.copyfile(book.filename, outname) shutil.copyfile(book.filename, outname)
print(u"Book saved as {0}".format(os.path.join(os.getcwd(), outname))) print("Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
return 0 return 0
result = 1 result = 1
for userkey in lib.userkeys: for userkey in lib.userkeys:
print(u"Trying key: {0}".format(userkey.hex())) print("Trying key: {0}".format(userkey.hex()))
try: try:
zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED) zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
for filename in zin.namelist(): for filename in zin.namelist():
@ -705,12 +705,12 @@ def decrypt_book(book, lib):
file.check(contents) file.check(contents)
zout.writestr(filename, contents) zout.writestr(filename, contents)
zout.close() zout.close()
print(u"Decryption succeeded.") print("Decryption succeeded.")
print(u"Book saved as {0}".format(os.path.join(os.getcwd(), outname))) print("Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
result = 0 result = 0
break break
except ValueError: except ValueError:
print(u"Decryption failed.") print("Decryption failed.")
zout.close() zout.close()
os.remove(outname) os.remove(outname)
zin.close() zin.close()
@ -719,7 +719,7 @@ def decrypt_book(book, lib):
def cli_main(): def cli_main():
description = __about__ description = __about__
epilog = u"Parsing of arguments failed." epilog = "Parsing of arguments failed."
parser = argparse.ArgumentParser(prog=sys.argv[0], description=description, epilog=epilog) parser = argparse.ArgumentParser(prog=sys.argv[0], description=description, epilog=epilog)
parser.add_argument('--devicedir', default='/media/KOBOeReader', help="directory of connected Kobo device") parser.add_argument('--devicedir', default='/media/KOBOeReader', help="directory of connected Kobo device")
parser.add_argument('--all', action='store_true', help="flag for converting all books on device") parser.add_argument('--all', action='store_true', help="flag for converting all books on device")
@ -735,25 +735,25 @@ def cli_main():
books = lib.books books = lib.books
else: else:
for i, book in enumerate(lib.books): for i, book in enumerate(lib.books):
print(u"{0}: {1}".format(i + 1, book.title)) print("{0}: {1}".format(i + 1, book.title))
print(u"Or 'all'") print("Or 'all'")
choice = input(u"Convert book number... ") choice = input("Convert book number... ")
if choice == u'all': if choice == "all":
books = list(lib.books) books = list(lib.books)
else: else:
try: try:
num = int(choice) num = int(choice)
books = [lib.books[num - 1]] books = [lib.books[num - 1]]
except (ValueError, IndexError): except (ValueError, IndexError):
print(u"Invalid choice. Exiting...") print("Invalid choice. Exiting...")
exit() exit()
results = [decrypt_book(book, lib) for book in books] results = [decrypt_book(book, lib) for book in books]
lib.close() lib.close()
overall_result = all(result != 0 for result in results) overall_result = all(result != 0 for result in results)
if overall_result != 0: if overall_result != 0:
print(u"Could not decrypt book with any of the keys found.") print("Could not decrypt book with any of the keys found.")
return overall_result return overall_result

@ -1,6 +1,6 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3' __license__ = 'GPL v3'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
@ -13,10 +13,7 @@ except ImportError:
from io import StringIO from io import StringIO
from traceback import print_exc from traceback import print_exc
try: from PyQt5.Qt import (Qt, QDialog, QPixmap, QIcon, QLabel, QHBoxLayout, QFont, QTableWidgetItem)
from PyQt5.Qt import (Qt, QDialog, QPixmap, QIcon, QLabel, QHBoxLayout, QFont, QTableWidgetItem)
except ImportError:
from PyQt4.Qt import (Qt, QDialog, QPixmap, QIcon, QLabel, QHBoxLayout, QFont, QTableWidgetItem)
from calibre.utils.config import config_dir from calibre.utils.config import config_dir
from calibre.constants import iswindows, DEBUG from calibre.constants import iswindows, DEBUG

@ -153,7 +153,7 @@
from __future__ import print_function from __future__ import print_function
__version__ = '3.2.4' __version__ = '3.2.4'
__about__ = u"Obok v{0}\nCopyright © 2012-2016 Physisticated et al.".format(__version__) __about__ = "Obok v{0}\nCopyright © 2012-2016 Physisticated et al.".format(__version__)
import sys import sys
import os import os
@ -173,10 +173,10 @@ import tempfile
can_parse_xml = True can_parse_xml = True
try: try:
from xml.etree import ElementTree as ET from xml.etree import ElementTree as ET
# print u"using xml.etree for xml parsing" # print "using xml.etree for xml parsing"
except ImportError: except ImportError:
can_parse_xml = False can_parse_xml = False
# print u"Cannot find xml.etree, disabling extraction of serial numbers" # print "Cannot find xml.etree, disabling extraction of serial numbers"
# List of all known hash keys # List of all known hash keys
KOBO_HASH_KEYS = ['88b3a2e13', 'XzUhGYdFp', 'NoCanLook','QJhwzAtXL'] KOBO_HASH_KEYS = ['88b3a2e13', 'XzUhGYdFp', 'NoCanLook','QJhwzAtXL']
@ -309,9 +309,9 @@ class KoboLibrary(object):
# step 1. check whether this looks like a real device # step 1. check whether this looks like a real device
if (device_path): if (device_path):
# we got a device path # we got a device path
self.kobodir = os.path.join(device_path, u".kobo") self.kobodir = os.path.join(device_path, ".kobo")
# devices use KoboReader.sqlite # devices use KoboReader.sqlite
kobodb = os.path.join(self.kobodir, u"KoboReader.sqlite") kobodb = os.path.join(self.kobodir, "KoboReader.sqlite")
if (not(os.path.isfile(kobodb))): if (not(os.path.isfile(kobodb))):
# device path seems to be wrong, unset it # device path seems to be wrong, unset it
device_path = u"" device_path = u""
@ -323,22 +323,22 @@ class KoboLibrary(object):
if (len(serials) == 0): if (len(serials) == 0):
# we got a device path but no saved serial # we got a device path but no saved serial
# try to get the serial from the device # try to get the serial from the device
# print u"get_device_settings - device_path = {0}".format(device_path) # print "get_device_settings - device_path = {0}".format(device_path)
# get serial from device_path/.adobe-digital-editions/device.xml # get serial from device_path/.adobe-digital-editions/device.xml
if can_parse_xml: if can_parse_xml:
devicexml = os.path.join(device_path, '.adobe-digital-editions', 'device.xml') devicexml = os.path.join(device_path, '.adobe-digital-editions', 'device.xml')
# print u"trying to load {0}".format(devicexml) # print "trying to load {0}".format(devicexml)
if (os.path.exists(devicexml)): if (os.path.exists(devicexml)):
# print u"trying to parse {0}".format(devicexml) # print "trying to parse {0}".format(devicexml)
xmltree = ET.parse(devicexml) xmltree = ET.parse(devicexml)
for node in xmltree.iter(): for node in xmltree.iter():
if "deviceSerial" in node.tag: if "deviceSerial" in node.tag:
serial = node.text serial = node.text
# print u"found serial {0}".format(serial) # print "found serial {0}".format(serial)
serials.append(serial) serials.append(serial)
break break
else: else:
# print u"cannot get serials from device." # print "cannot get serials from device."
device_path = u"" device_path = u""
self.kobodir = u"" self.kobodir = u""
kobodb = u"" kobodb = u""
@ -350,19 +350,19 @@ class KoboLibrary(object):
if sys.getwindowsversion().major > 5: if sys.getwindowsversion().major > 5:
if 'LOCALAPPDATA' in os.environ.keys(): if 'LOCALAPPDATA' in os.environ.keys():
# Python 2.x does not return unicode env. Use Python 3.x # Python 2.x does not return unicode env. Use Python 3.x
self.kobodir = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%") self.kobodir = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%")
if (self.kobodir == u""): if (self.kobodir == u""):
if 'USERPROFILE' in os.environ.keys(): if 'USERPROFILE' in os.environ.keys():
# Python 2.x does not return unicode env. Use Python 3.x # Python 2.x does not return unicode env. Use Python 3.x
self.kobodir = os.path.join(winreg.ExpandEnvironmentStrings(u"%USERPROFILE%"), u"Local Settings", u"Application Data") self.kobodir = os.path.join(winreg.ExpandEnvironmentStrings("%USERPROFILE%"), "Local Settings", "Application Data")
self.kobodir = os.path.join(self.kobodir, u"Kobo", u"Kobo Desktop Edition") self.kobodir = os.path.join(self.kobodir, "Kobo", "Kobo Desktop Edition")
elif sys.platform.startswith('darwin'): elif sys.platform.startswith('darwin'):
self.kobodir = os.path.join(os.environ['HOME'], u"Library", u"Application Support", u"Kobo", u"Kobo Desktop Edition") self.kobodir = os.path.join(os.environ['HOME'], "Library", "Application Support", "Kobo", "Kobo Desktop Edition")
#elif linux_path != None: #elif linux_path != None:
# Probably Linux, let's get the wine prefix and path to Kobo. # Probably Linux, let's get the wine prefix and path to Kobo.
# self.kobodir = os.path.join(linux_path, u"Local Settings", u"Application Data", u"Kobo", u"Kobo Desktop Edition") # self.kobodir = os.path.join(linux_path, "Local Settings", "Application Data", "Kobo", "Kobo Desktop Edition")
# desktop versions use Kobo.sqlite # desktop versions use Kobo.sqlite
kobodb = os.path.join(self.kobodir, u"Kobo.sqlite") kobodb = os.path.join(self.kobodir, "Kobo.sqlite")
# check for existence of file # check for existence of file
if (not(os.path.isfile(kobodb))): if (not(os.path.isfile(kobodb))):
# give up here, we haven't found anything useful # give up here, we haven't found anything useful
@ -371,7 +371,7 @@ class KoboLibrary(object):
if (self.kobodir != u""): if (self.kobodir != u""):
self.bookdir = os.path.join(self.kobodir, u"kepub") self.bookdir = os.path.join(self.kobodir, "kepub")
# make a copy of the database in a temporary file # make a copy of the database in a temporary file
# so we can ensure it's not using WAL logging which sqlite3 can't do. # so we can ensure it's not using WAL logging which sqlite3 can't do.
self.newdb = tempfile.NamedTemporaryFile(mode='wb', delete=False) self.newdb = tempfile.NamedTemporaryFile(mode='wb', delete=False)
@ -431,7 +431,7 @@ class KoboLibrary(object):
def __bookfile (self, volumeid): def __bookfile (self, volumeid):
"""The filename needed to open a given book.""" """The filename needed to open a given book."""
return os.path.join(self.kobodir, u"kepub", volumeid) return os.path.join(self.kobodir, "kepub", volumeid)
def __getmacaddrs (self): def __getmacaddrs (self):
"""The list of all MAC addresses on this machine.""" """The list of all MAC addresses on this machine."""
@ -448,7 +448,7 @@ class KoboLibrary(object):
output = subprocess.check_output('/sbin/ifconfig -a', shell=True) output = subprocess.check_output('/sbin/ifconfig -a', shell=True)
matches = c.findall(output) matches = c.findall(output)
for m in matches: for m in matches:
# print u"m:{0}".format(m[0]) # print "m:{0}".format(m[0])
macaddrs.append(m[0].upper()) macaddrs.append(m[0].upper())
elif sys.platform.startswith('linux'): elif sys.platform.startswith('linux'):
p_out = subprocess.check_output("ip -br link show | awk '{print $3}'", shell=True) p_out = subprocess.check_output("ip -br link show | awk '{print $3}'", shell=True)
@ -596,32 +596,32 @@ class KoboFile(object):
# assume utf-8 with no BOM # assume utf-8 with no BOM
textoffset = 0 textoffset = 0
stride = 1 stride = 1
print(u"Checking text:{0}:".format(contents[:10])) print("Checking text:{0}:".format(contents[:10]))
# check for byte order mark # check for byte order mark
if contents[:3]=="\xef\xbb\xbf": if contents[:3]=="\xef\xbb\xbf":
# seems to be utf-8 with BOM # seems to be utf-8 with BOM
print(u"Could be utf-8 with BOM") print("Could be utf-8 with BOM")
textoffset = 3 textoffset = 3
elif contents[:2]=="\xfe\xff": elif contents[:2]=="\xfe\xff":
# seems to be utf-16BE # seems to be utf-16BE
print(u"Could be utf-16BE") print("Could be utf-16BE")
textoffset = 3 textoffset = 3
stride = 2 stride = 2
elif contents[:2]=="\xff\xfe": elif contents[:2]=="\xff\xfe":
# seems to be utf-16LE # seems to be utf-16LE
print(u"Could be utf-16LE") print("Could be utf-16LE")
textoffset = 2 textoffset = 2
stride = 2 stride = 2
else: else:
print(u"Perhaps utf-8 without BOM") print("Perhaps utf-8 without BOM")
# now check that the first few characters are in the ASCII range # now check that the first few characters are in the ASCII range
for i in xrange(textoffset,textoffset+5*stride,stride): for i in xrange(textoffset,textoffset+5*stride,stride):
if ord(contents[i])<32 or ord(contents[i])>127: if ord(contents[i])<32 or ord(contents[i])>127:
# Non-ascii, so decryption probably failed # Non-ascii, so decryption probably failed
print(u"Bad character at {0}, value {1}".format(i,ord(contents[i]))) print("Bad character at {0}, value {1}".format(i,ord(contents[i])))
raise ValueError raise ValueError
print(u"Seems to be good text") print("Seems to be good text")
return True return True
if contents[:5]=="<?xml" or contents[:8]=="\xef\xbb\xbf<?xml": if contents[:5]=="<?xml" or contents[:8]=="\xef\xbb\xbf<?xml":
# utf-8 # utf-8
@ -642,13 +642,13 @@ class KoboFile(object):
# utf-16LE of weird <!DOCTYPE start # utf-16LE of weird <!DOCTYPE start
return True return True
else: else:
print(u"Bad XML: {0}".format(contents[:8])) print("Bad XML: {0}".format(contents[:8]))
raise ValueError raise ValueError
elif self.mimetype == 'image/jpeg': elif self.mimetype == 'image/jpeg':
if contents[:3] == '\xff\xd8\xff': if contents[:3] == '\xff\xd8\xff':
return True return True
else: else:
print(u"Bad JPEG: {0}".format(contents[:3].encode('hex'))) print("Bad JPEG: {0}".format(contents[:3].encode('hex')))
raise ValueError() raise ValueError()
return False return False
@ -671,18 +671,18 @@ class KoboFile(object):
return contents return contents
def decrypt_book(book, lib): def decrypt_book(book, lib):
print(u"Converting {0}".format(book.title)) print("Converting {0}".format(book.title))
zin = zipfile.ZipFile(book.filename, "r") zin = zipfile.ZipFile(book.filename, "r")
# make filename out of Unicode alphanumeric and whitespace equivalents from title # make filename out of Unicode alphanumeric and whitespace equivalents from title
outname = u"{0}.epub".format(re.sub('[^\s\w]', '_', book.title, 0, re.UNICODE)) outname = "{0}.epub".format(re.sub('[^\s\w]', '_', book.title, 0, re.UNICODE))
if (book.type == 'drm-free'): if (book.type == 'drm-free'):
print(u"DRM-free book, conversion is not needed") print("DRM-free book, conversion is not needed")
shutil.copyfile(book.filename, outname) shutil.copyfile(book.filename, outname)
print(u"Book saved as {0}".format(os.path.join(os.getcwd(), outname))) print("Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
return 0 return 0
result = 1 result = 1
for userkey in lib.userkeys: for userkey in lib.userkeys:
print(u"Trying key: {0}".format(userkey.encode('hex_codec'))) print("Trying key: {0}".format(userkey.encode('hex_codec')))
try: try:
zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED) zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
for filename in zin.namelist(): for filename in zin.namelist():
@ -694,12 +694,12 @@ def decrypt_book(book, lib):
file.check(contents) file.check(contents)
zout.writestr(filename, contents) zout.writestr(filename, contents)
zout.close() zout.close()
print(u"Decryption succeeded.") print("Decryption succeeded.")
print(u"Book saved as {0}".format(os.path.join(os.getcwd(), outname))) print("Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
result = 0 result = 0
break break
except ValueError: except ValueError:
print(u"Decryption failed.") print("Decryption failed.")
zout.close() zout.close()
os.remove(outname) os.remove(outname)
zin.close() zin.close()
@ -708,7 +708,7 @@ def decrypt_book(book, lib):
def cli_main(): def cli_main():
description = __about__ description = __about__
epilog = u"Parsing of arguments failed." epilog = "Parsing of arguments failed."
parser = argparse.ArgumentParser(prog=sys.argv[0], description=description, epilog=epilog) parser = argparse.ArgumentParser(prog=sys.argv[0], description=description, epilog=epilog)
parser.add_argument('--devicedir', default='/media/KOBOeReader', help="directory of connected Kobo device") parser.add_argument('--devicedir', default='/media/KOBOeReader', help="directory of connected Kobo device")
parser.add_argument('--all', action='store_true', help="flag for converting all books on device") parser.add_argument('--all', action='store_true', help="flag for converting all books on device")
@ -724,25 +724,25 @@ def cli_main():
books = lib.books books = lib.books
else: else:
for i, book in enumerate(lib.books): for i, book in enumerate(lib.books):
print(u"{0}: {1}".format(i + 1, book.title)) print("{0}: {1}".format(i + 1, book.title))
print(u"Or 'all'") print("Or 'all'")
choice = raw_input(u"Convert book number... ") choice = raw_input("Convert book number... ")
if choice == u'all': if choice == "all":
books = list(lib.books) books = list(lib.books)
else: else:
try: try:
num = int(choice) num = int(choice)
books = [lib.books[num - 1]] books = [lib.books[num - 1]]
except (ValueError, IndexError): except (ValueError, IndexError):
print(u"Invalid choice. Exiting...") print("Invalid choice. Exiting...")
exit() exit()
results = [decrypt_book(book, lib) for book in books] results = [decrypt_book(book, lib) for book in books]
lib.close() lib.close()
overall_result = all(result != 0 for result in results) overall_result = all(result != 0 for result in results)
if overall_result != 0: if overall_result != 0:
print(u"Could not decrypt book with any of the keys found.") print("Could not decrypt book with any of the keys found.")
return overall_result return overall_result

@ -1,5 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python3
# code: utf-8 # -*- coding: utf-8 -*-
''' '''
A wrapper script to generate zip files for GitHub releases. A wrapper script to generate zip files for GitHub releases.

Loading…
Cancel
Save