More generic 3.0 changes, to be tested.
This commit is contained in:
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 2010–2016 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,
|
QGroupBox, QPushButton, QListWidget, QListWidgetItem,
|
||||||
QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl)
|
QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl)
|
||||||
except ImportError:
|
|
||||||
from PyQt4.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
|
|
||||||
QGroupBox, QPushButton, QListWidget, QListWidgetItem,
|
|
||||||
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
|
|
||||||
# Added Python 3 compatibility, September 2020
|
|
||||||
|
|
||||||
from __future__ import print_function
|
# For use with Topaz Scripts Version 2.6
|
||||||
|
# Python 3, September 2020
|
||||||
|
|
||||||
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 2008–2012 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 © 2009–2012 The Dark Reverser et al.".format(__version__))
|
print("eRdr2Pml v{0}. Copyright © 2009–2020 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 2010–2013 by some_updates, DiapDealer and Apprentice Alf
|
|
||||||
# Modified 2015–2017 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 @@
|
||||||
|
#!/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)
|
|
||||||
|
|
||||||
try:
|
from PyQt5.Qt import (Qt, QGroupBox, QListWidget, QLineEdit, QDialogButtonBox, QWidget, QLabel, QDialog, QVBoxLayout, QAbstractItemView, QIcon, QHBoxLayout, QComboBox, QListWidgetItem, QFileDialog)
|
||||||
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
|
||||||
except ImportError:
|
|
||||||
from PyQt4.Qt import (Qt, QGroupBox, QListWidget, QLineEdit, QDialogButtonBox, QWidget, QLabel, QDialog, QVBoxLayout, QAbstractItemView, QIcon, QHBoxLayout, QComboBox, QListWidgetItem, QFileDialog)
|
|
||||||
|
|
||||||
try:
|
|
||||||
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…
Reference in New Issue