More generic 3.0 changes, to be tested.

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

@ -1,8 +1,6 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import with_statement
# __init__.py for DeDRM_plugin
# Copyright © 2008-2020 Apprentice Harper et al.
@ -77,9 +75,9 @@ __docformat__ = 'restructuredtext en'
Decrypt DRMed ebooks.
"""
PLUGIN_NAME = u"DeDRM"
PLUGIN_NAME = "DeDRM"
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.
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
@ -109,11 +107,11 @@ class SafeUnbuffered:
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,bytes):
if isinstance(data,str):
data = data.encode(self.encoding,"replace")
try:
self.stream.write(data)
self.stream.flush()
self.stream.buffer.write(data)
self.stream.buffer.flush()
except:
# We can do nothing if a write fails
pass
@ -122,11 +120,11 @@ class SafeUnbuffered:
class DeDRM(FileTypePlugin):
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']
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
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'])
on_import = True
on_preprocess = True
@ -146,27 +144,27 @@ class DeDRM(FileTypePlugin):
Also perform upgrade of preferences once per version
"""
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):
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):
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):
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):
os.mkdir(self.alfdir)
# only continue if we've never run this version of the plugin before
self.verdir = os.path.join(self.maindir,PLUGIN_VERSION)
if not os.path.exists(self.verdir):
if iswindows:
names = [u"alfcrypto.dll",u"alfcrypto64.dll"]
names = ["alfcrypto.dll","alfcrypto64.dll"]
elif isosx:
names = [u"libalfcrypto.dylib"]
names = ["libalfcrypto.dylib"]
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)
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.
import calibre_plugins.dedrm.zipfix
inf = self.temporary_file(u".epub")
inf = self.temporary_file(".epub")
try:
print("{0} v{1}: Verifying zip archive integrity".format(PLUGIN_NAME, PLUGIN_VERSION))
fr = zipfix.fixZip(path_to_ebook, inf.name)
fr.fix()
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)
# import the decryption keys
@ -222,9 +220,9 @@ class DeDRM(FileTypePlugin):
# Attempt to decrypt epub with each encryption key (generated or provided).
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))
of = self.temporary_file(u".epub")
of = self.temporary_file(".epub")
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
try:
@ -255,10 +253,10 @@ class DeDRM(FileTypePlugin):
defaultkeys = nookkeys()
else: # linux
from wineutils import WineGetKeys
from .wineutils import WineGetKeys
scriptpath = os.path.join(self.alfdir,u"ignoblekey.py")
defaultkeys = WineGetKeys(scriptpath, u".b64",dedrmprefs['adobewineprefix'])
scriptpath = os.path.join(self.alfdir,"ignoblekey.py")
defaultkeys = WineGetKeys(scriptpath, ".b64",dedrmprefs['adobewineprefix'])
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))
@ -274,7 +272,7 @@ class DeDRM(FileTypePlugin):
for i,userkey in enumerate(newkeys):
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.
try:
@ -300,12 +298,12 @@ class DeDRM(FileTypePlugin):
# Return the modified PersistentTemporary file to calibre.
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:
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))
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 calibre_plugins.dedrm.ineptepub as ineptepub
@ -316,8 +314,8 @@ class DeDRM(FileTypePlugin):
# Attempt to decrypt epub with each encryption key (generated or provided).
for keyname, userkeyhex in dedrmprefs['adeptkeys'].items():
userkey = codecs.decode(userkeyhex, 'hex')
print(u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname))
of = self.temporary_file(u".epub")
print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname))
of = self.temporary_file(".epub")
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
try:
@ -352,10 +350,10 @@ class DeDRM(FileTypePlugin):
defaultkeys = adeptkeys()
else: # linux
from wineutils import WineGetKeys
from .wineutils import WineGetKeys
scriptpath = os.path.join(self.alfdir,u"adobekey.py")
defaultkeys = WineGetKeys(scriptpath, u".der",dedrmprefs['adobewineprefix'])
scriptpath = os.path.join(self.alfdir,"adobekey.py")
defaultkeys = WineGetKeys(scriptpath, ".der",dedrmprefs['adobewineprefix'])
self.default_key = defaultkeys[0]
except:
@ -365,14 +363,14 @@ class DeDRM(FileTypePlugin):
newkeys = []
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)
if len(newkeys) > 0:
try:
for i,userkey in enumerate(newkeys):
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.
try:
@ -389,7 +387,7 @@ class DeDRM(FileTypePlugin):
# Store the new successful key in the defaults
print("{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
try:
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex'))
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',codecs.encode(keyvalue, 'hex').decode('ascii'))
dedrmprefs.writeprefs()
print("{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
except:
@ -399,20 +397,20 @@ class DeDRM(FileTypePlugin):
# Return the modified PersistentTemporary file to calibre.
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:
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()
pass
# 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))
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
# 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)))
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):
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).
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():
userkey = codecs.decode(userkeyhex, 'hex')
userkey = userkeyhex.decode('hex')
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.
try:
@ -455,10 +453,10 @@ class DeDRM(FileTypePlugin):
defaultkeys = adeptkeys()
else: # linux
from wineutils import WineGetKeys
from .wineutils import WineGetKeys
scriptpath = os.path.join(self.alfdir,u"adobekey.py")
defaultkeys = WineGetKeys(scriptpath, u".der",dedrmprefs['adobewineprefix'])
scriptpath = os.path.join(self.alfdir,"adobekey.py")
defaultkeys = WineGetKeys(scriptpath, ".der",dedrmprefs['adobewineprefix'])
self.default_key = defaultkeys[0]
except:
@ -475,7 +473,7 @@ class DeDRM(FileTypePlugin):
try:
for i,userkey in enumerate(newkeys):
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.
try:
@ -501,13 +499,13 @@ class DeDRM(FileTypePlugin):
# Return the modified PersistentTemporary file to calibre.
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:
pass
# 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))
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):
@ -529,7 +527,7 @@ class DeDRM(FileTypePlugin):
serials.extend(android_serials_list)
#print serials
androidFiles = []
kindleDatabases = dedrmprefs['kindlekeys'].items()
kindleDatabases = list(dedrmprefs['kindlekeys'].items())
try:
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,kindleDatabases,androidFiles,serials,pids,self.starttime)
@ -546,10 +544,10 @@ class DeDRM(FileTypePlugin):
defaultkeys = kindlekeys()
else: # linux
from wineutils import WineGetKeys
from .wineutils import WineGetKeys
scriptpath = os.path.join(self.alfdir,u"kindlekey.py")
defaultkeys = WineGetKeys(scriptpath, u".k4i",dedrmprefs['kindlewineprefix'])
scriptpath = os.path.join(self.alfdir,"kindlekey.py")
defaultkeys = WineGetKeys(scriptpath, ".k4i",dedrmprefs['kindlewineprefix'])
except:
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()
@ -557,16 +555,16 @@ class DeDRM(FileTypePlugin):
newkeys = {}
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():
newkeys[keyname] = keyvalue
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:
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],[],self.starttime)
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,list(newkeys.items()),[],[],[],self.starttime)
decoded = True
# 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():
dedrmprefs.addnamedvaluetoprefs('kindlekeys','default_key',keyvalue)
dedrmprefs.writeprefs()
@ -575,7 +573,7 @@ class DeDRM(FileTypePlugin):
if not decoded:
#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))
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())
book.getFile(of.name)
@ -592,9 +590,9 @@ class DeDRM(FileTypePlugin):
dedrmprefs = prefs.DeDRM_Prefs()
# Attempt to decrypt epub with each encryption key (generated or provided).
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))
of = self.temporary_file(u".pmlz")
of = self.temporary_file(".pmlz")
# Give the userkey, ebook and TemporaryPersistent file to the decryption function.
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}: 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):

@ -1,30 +1,12 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 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
# <http://www.gnu.org/licenses/>
# Modified 20102016 by several people
# Windows users: Before running this program, you must first install Python.
# We recommend ActiveState Python 2.7.X for Windows (x86) from
# http://www.activestate.com/activepython/downloads.
# You must also install PyCrypto from
# http://www.voidspace.org.uk/python/modules.shtml#pycrypto
# (make certain to install the version for Python 2.7).
# Then save this script file as adobekey.pyw and double-click on it to run it.
# It will create a file named adobekey_1.der in in the same directory as the script.
# This is your Adobe Digital Editions user key.
#
# Mac OS X users: Save this script file as adobekey.pyw. You can run this
# program from the command line (python adobekey.pyw) or by double-clicking
# it when it has been associated with PythonLauncher. It will create a file
# named adobekey_1.der in the same directory as the script.
# This is your Adobe Digital Editions user key.
# Revision history:
# 1 - Initial release, for Adobe Digital Editions 1.7
# 2 - Better algorithm for finding pLK; improved error handling
@ -46,7 +28,7 @@
# 5.8 - Added getkey interface for Windows DeDRM application
# 5.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
# 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.

@ -1,4 +1,5 @@
#! /usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Routines for doing AES CBC in one file
@ -14,7 +15,7 @@
See the wonderful pure python package cryptopy-1.2.5
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):

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

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

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

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

@ -1,30 +1,18 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import with_statement
from __future__ import print_function
__license__ = 'GPL v3'
# Added Python 3 compatibility, September 2020
# Python 3, September 2020
# Standard Python modules.
import os, traceback, json
# PyQT4 modules (part of calibre).
try:
from PyQt5.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
QGroupBox, QPushButton, QListWidget, QListWidgetItem,
QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl)
except ImportError:
from PyQt4.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
from PyQt5.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
# calibre modules and constants.
@ -85,32 +73,32 @@ class ConfigWidget(QWidget):
button_layout = QVBoxLayout()
keys_group_box_layout.addLayout(button_layout)
self.bandn_button = QtGui.QPushButton(self)
self.bandn_button.setToolTip(_(u"Click to manage keys for Barnes and Noble ebooks"))
self.bandn_button.setText(u"Barnes and Noble ebooks")
self.bandn_button.setToolTip(_("Click to manage keys for Barnes and Noble ebooks"))
self.bandn_button.setText("Barnes and Noble ebooks")
self.bandn_button.clicked.connect(self.bandn_keys)
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.setText(u"Kindle for Android ebooks")
self.kindle_android_button.setToolTip(_("Click to manage keys for Kindle for Android ebooks"))
self.kindle_android_button.setText("Kindle for Android ebooks")
self.kindle_android_button.clicked.connect(self.kindle_android)
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.setText(u"eInk Kindle ebooks")
self.kindle_serial_button.setToolTip(_("Click to manage eInk Kindle serial numbers for Kindle ebooks"))
self.kindle_serial_button.setText("eInk Kindle ebooks")
self.kindle_serial_button.clicked.connect(self.kindle_serials)
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.setText(u"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("Kindle for Mac/PC ebooks")
self.kindle_key_button.clicked.connect(self.kindle_keys)
self.adept_button = QtGui.QPushButton(self)
self.adept_button.setToolTip(_(u"Click to manage keys for Adobe Digital Editions ebooks"))
self.adept_button.setText(u"Adobe Digital Editions ebooks")
self.adept_button.setToolTip(_("Click to manage keys for Adobe Digital Editions ebooks"))
self.adept_button.setText("Adobe Digital Editions ebooks")
self.adept_button.clicked.connect(self.adept_keys)
self.mobi_button = QtGui.QPushButton(self)
self.mobi_button.setToolTip(_(u"Click to manage PIDs for Mobipocket ebooks"))
self.mobi_button.setText(u"Mobipocket ebooks")
self.mobi_button.setToolTip(_("Click to manage PIDs for Mobipocket ebooks"))
self.mobi_button.setText("Mobipocket ebooks")
self.mobi_button.clicked.connect(self.mobi_keys)
self.ereader_button = QtGui.QPushButton(self)
self.ereader_button.setToolTip(_(u"Click to manage keys for eReader ebooks"))
self.ereader_button.setText(u"eReader ebooks")
self.ereader_button.setToolTip(_("Click to manage keys for eReader ebooks"))
self.ereader_button.setText("eReader ebooks")
self.ereader_button.clicked.connect(self.ereader_keys)
button_layout.addWidget(self.kindle_serial_button)
button_layout.addWidget(self.kindle_android_button)
@ -123,48 +111,48 @@ class ConfigWidget(QWidget):
self.resize(self.sizeHint())
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_()
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_()
def kindle_keys(self):
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:
# 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_()
self.tempdedrmprefs['kindlewineprefix'] = d.getwineprefix()
def adept_keys(self):
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:
# 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_()
self.tempdedrmprefs['adobewineprefix'] = d.getwineprefix()
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_()
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_()
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_()
def help_link_activated(self, url):
def get_help_file_resource():
# Copy the HTML helpfile to the plugin directory each time the
# 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:
f.write(self.load_resource(help_file_name))
return file_path
@ -201,9 +189,9 @@ class ManageKeysDialog(QDialog):
self.create_key = create_key
self.keyfile_ext = keyfile_ext
self.import_key = (keyfile_ext != u"")
self.binary_file = (keyfile_ext == u"der")
self.json_file = (keyfile_ext == u"k4i")
self.android_file = (keyfile_ext == u"k4a")
self.binary_file = (keyfile_ext == "der")
self.json_file = (keyfile_ext == "k4i")
self.android_file = (keyfile_ext == "k4a")
self.wineprefix = wineprefix
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_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)
keys_group_box_layout = QHBoxLayout()
keys_group_box.setLayout(keys_group_box_layout)
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.populate_list()
keys_group_box_layout.addWidget(self.listy)
@ -236,25 +224,25 @@ class ManageKeysDialog(QDialog):
keys_group_box_layout.addLayout(button_layout)
self._add_key_button = QtGui.QToolButton(self)
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)
button_layout.addWidget(self._add_key_button)
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.clicked.connect(self.delete_key)
button_layout.addWidget(self._delete_key_button)
if type(self.plugin_keys) == dict and self.import_key:
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.clicked.connect(self.rename_key)
button_layout.addWidget(self._rename_key_button)
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.clicked.connect(self.export_key)
button_layout.addWidget(self.export_key_button)
@ -266,7 +254,7 @@ class ManageKeysDialog(QDialog):
wineprefix_layout = QHBoxLayout()
layout.addLayout(wineprefix_layout)
wineprefix_layout.setAlignment(Qt.AlignCenter)
self.wp_label = QLabel(u"WINEPREFIX:")
self.wp_label = QLabel("WINEPREFIX:")
wineprefix_layout.addWidget(self.wp_label)
self.wp_lineedit = QLineEdit(self)
wineprefix_layout.addWidget(self.wp_lineedit)
@ -278,8 +266,8 @@ class ManageKeysDialog(QDialog):
layout.addLayout(migrate_layout)
if self.import_key:
migrate_layout.setAlignment(Qt.AlignJustify)
self.migrate_btn = QPushButton(u"Import Existing Keyfiles", self)
self.migrate_btn.setToolTip(u"Import *.{0} files (created using other tools).".format(self.keyfile_ext))
self.migrate_btn = QPushButton("Import Existing Keyfiles", self)
self.migrate_btn.setToolTip("Import *.{0} files (created using other tools).".format(self.keyfile_ext))
self.migrate_btn.clicked.connect(self.migrate_wrapper)
migrate_layout.addWidget(self.migrate_btn)
migrate_layout.addStretch()
@ -314,13 +302,13 @@ class ManageKeysDialog(QDialog):
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]
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
self.plugin_keys[d.key_name] = new_key_value
else:
if new_key_value in self.plugin_keys:
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
self.plugin_keys.append(d.key_value)
@ -329,7 +317,7 @@ class ManageKeysDialog(QDialog):
def rename_key(self):
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),
_(errmsg), show=True, show_copy_button=False)
return
@ -341,7 +329,7 @@ class ManageKeysDialog(QDialog):
# rename cancelled or moot.
return
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
self.plugin_keys[d.key_name] = self.plugin_keys[keyname]
del self.plugin_keys[keyname]
@ -353,7 +341,7 @@ class ManageKeysDialog(QDialog):
if not self.listy.currentItem():
return
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
if type(self.plugin_keys) == dict:
del self.plugin_keys[keyname]
@ -367,8 +355,8 @@ class ManageKeysDialog(QDialog):
def get_help_file_resource():
# Copy the HTML helpfile to the plugin directory each time the
# 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)
file_path = os.path.join(config_dir, u"plugins", u"DeDRM", u"help", help_file_name)
help_file_name = "{0}_{1}_Help.htm".format(PLUGIN_NAME, self.key_type_name)
file_path = os.path.join(config_dir, "plugins", "DeDRM", "help", help_file_name)
with open(file_path,'w') as f:
f.write(self.parent.load_resource(help_file_name))
return file_path
@ -376,9 +364,9 @@ class ManageKeysDialog(QDialog):
open_url(QUrl(url))
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
caption = u"Select {0} files to import".format(self.key_type_name)
filters = [(u"{0} files".format(self.key_type_name), [self.keyfile_ext])]
unique_dlg_name = PLUGIN_NAME + "import {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory
caption = "Select {0} files to import".format(self.key_type_name)
filters = [("{0} files".format(self.key_type_name), [self.keyfile_ext])]
files = choose_files(self, unique_dlg_name, caption, filters, all_files=False)
counter = 0
skipped = 0
@ -400,7 +388,7 @@ class ManageKeysDialog(QDialog):
for key in self.plugin_keys.keys():
if uStrCmp(new_key_name, key, True):
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),
_(msg), show_copy_button=False, show=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]
skipped += 1
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:
counter += 1
self.plugin_keys[new_key_name] = new_key_value
@ -418,9 +406,9 @@ class ManageKeysDialog(QDialog):
msg = u""
if counter+skipped > 1:
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:
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),
_(msg), show_copy_button=False, show=True)
return counter > 0
@ -432,15 +420,15 @@ class ManageKeysDialog(QDialog):
def export_key(self):
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),
_(errmsg), show=True, show_copy_button=False)
return
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
caption = u"Save {0} File as...".format(self.key_type_name)
filters = [(u"{0} Files".format(self.key_type_name), [u"{0}".format(self.keyfile_ext)])]
defaultname = u"{0}.{1}".format(keyname, self.keyfile_ext)
unique_dlg_name = PLUGIN_NAME + "export {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory
caption = "Save {0} File as...".format(self.key_type_name)
filters = [("{0} Files".format(self.key_type_name), ["{0}".format(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)
if filename:
with file(filename, 'wb') as fname:
@ -474,7 +462,7 @@ class RenameKeyDialog(QDialog):
data_group_box_layout.addWidget(QLabel('New Key Name:', 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)
layout.addSpacing(20)
@ -488,11 +476,11 @@ class RenameKeyDialog(QDialog):
def accept(self):
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),
_(errmsg), show=True, show_copy_button=False)
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),
_(errmsg), show=True, show_copy_button=False)
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():
if (uStrCmp(self.key_ledit.text(), k, True) and
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),
_(errmsg), show=True, show_copy_button=False)
QDialog.accept(self)
@ -521,7 +509,7 @@ class AddBandNKeyDialog(QDialog):
def __init__(self, parent=None,):
QDialog.__init__(self, 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)
self.setLayout(layout)
@ -532,37 +520,37 @@ class AddBandNKeyDialog(QDialog):
key_group = QHBoxLayout()
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.setToolTip(_(u"<p>Enter an identifying name for this new key.</p>" +
u"<p>It should be something that will help you remember " +
u"what personal information was used to create it."))
self.key_ledit.setToolTip(_("<p>Enter an identifying name for this new key.</p>" +
"<p>It should be something that will help you remember " +
"what personal information was used to create it."))
key_group.addWidget(self.key_ledit)
name_group = QHBoxLayout()
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.setToolTip(_(u"<p>Enter your email address as it appears in your B&N " +
u"account.</p>" +
u"<p>It will only be used to generate this " +
u"key and won\'t be stored anywhere " +
u"in calibre or on your computer.</p>" +
u"<p>eg: apprenticeharper@gmail.com</p>"))
self.name_ledit.setToolTip(_("<p>Enter your email address as it appears in your B&N " +
"account.</p>" +
"<p>It will only be used to generate this " +
"key and won\'t be stored anywhere " +
"in calibre or on your computer.</p>" +
"<p>eg: apprenticeharper@gmail.com</p>"))
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)
data_group_box_layout.addWidget(name_disclaimer_label)
ccn_group = QHBoxLayout()
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.setToolTip(_(u"<p>Enter the password " +
u"for your B&N account.</p>" +
u"<p>The password will only be used to generate this " +
u"key and won\'t be stored anywhere in " +
u"calibre or on your computer."))
self.cc_ledit.setToolTip(_("<p>Enter the password " +
"for your B&N account.</p>" +
"<p>The password will only be used to generate this " +
"key and won\'t be stored anywhere in " +
"calibre or on your computer."))
ccn_group.addWidget(self.cc_ledit)
ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self)
ccn_disclaimer_label.setAlignment(Qt.AlignHCenter)
@ -571,13 +559,13 @@ class AddBandNKeyDialog(QDialog):
key_group = QHBoxLayout()
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.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)
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.setText(u"Retrieve Key")
self.retrieve_button.setToolTip(_("Click to retrieve your B&N encryption key from the B&N servers"))
self.retrieve_button.setText("Retrieve Key")
self.retrieve_button.clicked.connect(self.retrieve_key)
key_group.addWidget(self.retrieve_button)
layout.addSpacing(10)
@ -609,17 +597,17 @@ class AddBandNKeyDialog(QDialog):
from calibre_plugins.dedrm.ignoblekeyfetch import fetch_key as fetch_bandn_key
fetched_key = fetch_bandn_key(self.user_name,self.cc_number)
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)
else:
self.key_display.setText(fetched_key)
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():
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)
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)
if len(self.key_value) == 0:
self.retrieve_key()
@ -631,7 +619,7 @@ class AddEReaderDialog(QDialog):
def __init__(self, parent=None,):
QDialog.__init__(self, 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)
self.setLayout(layout)
@ -642,26 +630,26 @@ class AddEReaderDialog(QDialog):
key_group = QHBoxLayout()
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.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)
name_group = QHBoxLayout()
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.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_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)
data_group_box_layout.addWidget(name_disclaimer_label)
ccn_group = QHBoxLayout()
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.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_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self)
ccn_disclaimer_label.setAlignment(Qt.AlignHCenter)
@ -695,13 +683,13 @@ class AddEReaderDialog(QDialog):
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():
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)
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)
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)
QDialog.accept(self)
@ -710,7 +698,7 @@ class AddAdeptDialog(QDialog):
def __init__(self, parent=None,):
QDialog.__init__(self, 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)
self.setLayout(layout)
@ -722,8 +710,8 @@ class AddAdeptDialog(QDialog):
else: # linux
from wineutils import WineGetKeys
scriptpath = os.path.join(parent.parent.alfdir,u"adobekey.py")
defaultkeys = WineGetKeys(scriptpath, u".der",parent.getwineprefix())
scriptpath = os.path.join(parent.parent.alfdir,"adobekey.py")
defaultkeys = WineGetKeys(scriptpath, ".der",parent.getwineprefix())
self.default_key = defaultkeys[0]
except:
@ -740,14 +728,14 @@ class AddAdeptDialog(QDialog):
key_group = QHBoxLayout()
data_group_box_layout.addLayout(key_group)
key_group.addWidget(QLabel(u"Unique Key Name:", self))
self.key_ledit = QLineEdit(u"default_key", self)
self.key_ledit.setToolTip(u"<p>Enter an identifying name for the current default Adobe Digital Editions key.")
key_group.addWidget(QLabel("Unique Key Name:", self))
self.key_ledit = QLineEdit("default_key", self)
self.key_ledit.setToolTip("<p>Enter an identifying name for the current default Adobe Digital Editions key.")
key_group.addWidget(self.key_ledit)
self.button_box.accepted.connect(self.accept)
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)
layout.addWidget(default_key_error)
# if no default, bot buttons do the same
@ -769,10 +757,10 @@ class AddAdeptDialog(QDialog):
def accept(self):
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)
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)
QDialog.accept(self)
@ -781,7 +769,7 @@ class AddKindleDialog(QDialog):
def __init__(self, parent=None,):
QDialog.__init__(self, 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)
self.setLayout(layout)
@ -793,8 +781,8 @@ class AddKindleDialog(QDialog):
else: # linux
from wineutils import WineGetKeys
scriptpath = os.path.join(parent.parent.alfdir,u"kindlekey.py")
defaultkeys = WineGetKeys(scriptpath, u".k4i",parent.getwineprefix())
scriptpath = os.path.join(parent.parent.alfdir,"kindlekey.py")
defaultkeys = WineGetKeys(scriptpath, ".k4i",parent.getwineprefix())
self.default_key = defaultkeys[0]
except:
@ -811,14 +799,14 @@ class AddKindleDialog(QDialog):
key_group = QHBoxLayout()
data_group_box_layout.addLayout(key_group)
key_group.addWidget(QLabel(u"Unique Key Name:", self))
self.key_ledit = QLineEdit(u"default_key", self)
self.key_ledit.setToolTip(u"<p>Enter an identifying name for the current default Kindle for Mac/PC key.")
key_group.addWidget(QLabel("Unique Key Name:", self))
self.key_ledit = QLineEdit("default_key", self)
self.key_ledit.setToolTip("<p>Enter an identifying name for the current default Kindle for Mac/PC key.")
key_group.addWidget(self.key_ledit)
self.button_box.accepted.connect(self.accept)
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)
layout.addWidget(default_key_error)
@ -841,10 +829,10 @@ class AddKindleDialog(QDialog):
def accept(self):
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)
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)
QDialog.accept(self)
@ -853,7 +841,7 @@ class AddSerialDialog(QDialog):
def __init__(self, parent=None,):
QDialog.__init__(self, 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)
self.setLayout(layout)
@ -864,9 +852,9 @@ class AddSerialDialog(QDialog):
key_group = QHBoxLayout()
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.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)
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
@ -886,10 +874,10 @@ class AddSerialDialog(QDialog):
def accept(self):
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)
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)
QDialog.accept(self)
@ -899,7 +887,7 @@ class AddAndroidDialog(QDialog):
QDialog.__init__(self, 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)
self.setLayout(layout)
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
@ -911,8 +899,8 @@ class AddAndroidDialog(QDialog):
file_group = QHBoxLayout()
data_group_box_layout.addLayout(file_group)
add_btn = QPushButton(u"Choose Backup File", self)
add_btn.setToolTip(u"Import Kindle for Android backup file.")
add_btn = QPushButton("Choose Backup File", self)
add_btn.setToolTip("Import Kindle for Android backup file.")
add_btn.clicked.connect(self.get_android_file)
file_group.addWidget(add_btn)
self.selected_file_name = QLabel(u"",self)
@ -921,9 +909,9 @@ class AddAndroidDialog(QDialog):
key_group = QHBoxLayout()
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.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_label = QLabel(_(''), self)
#key_label.setAlignment(Qt.AlignHCenter)
@ -947,9 +935,9 @@ class AddAndroidDialog(QDialog):
return self.serials_from_file
def get_android_file(self):
unique_dlg_name = PLUGIN_NAME + u"Import Kindle for Android backup file" #takes care of automatically remembering last directory
caption = u"Select Kindle for Android backup file to add"
filters = [(u"Kindle for Android backup files", ['db','ab','xml'])]
unique_dlg_name = PLUGIN_NAME + "Import Kindle for Android backup file" #takes care of automatically remembering last directory
caption = "Select Kindle for Android backup file to add"
filters = [("Kindle for Android backup files", ['db','ab','xml'])]
files = choose_files(self, unique_dlg_name, caption, filters, all_files=False)
self.serials_from_file = []
file_name = u""
@ -967,13 +955,13 @@ class AddAndroidDialog(QDialog):
def accept(self):
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)
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)
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)
QDialog.accept(self)
@ -981,7 +969,7 @@ class AddPIDDialog(QDialog):
def __init__(self, parent=None,):
QDialog.__init__(self, 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)
self.setLayout(layout)
@ -992,9 +980,9 @@ class AddPIDDialog(QDialog):
key_group = QHBoxLayout()
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.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)
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
@ -1014,10 +1002,10 @@ class AddPIDDialog(QDialog):
def accept(self):
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)
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)
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
# For use with Topaz Scripts Version 2.6
# Added Python 3 compatibility, September 2020
# Python 3, September 2020
from __future__ import print_function
class Unbuffered:
def __init__(self, stream):
self.stream = stream
def write(self, data):
self.stream.write(data)
self.stream.flush()
self.stream.buffer.write(data)
self.stream.buffer.flush()
def __getattr__(self, 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.
# For example, ActiveState Python, which exists for windows.
@ -10,7 +11,7 @@
# Changelog epubtest
# 1.00 - Cut to epubtest.py, testing ePub files only by Apprentice Alf
# 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
# Released with unlicense. See http://unlicense.org/
@ -45,9 +46,6 @@
# 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'
import sys, struct, os, traceback
@ -112,7 +110,7 @@ def unicode_argv():
xrange(start, argc.value)]
# if we don't have any arguments at all, just pass back script name
# this should never happen
return [u"epubtest.py"]
return ["epubtest.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:

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

@ -1,28 +1,13 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import with_statement
from __future__ import print_function
# ignobleepub.pyw, version 4.1
# Copyright © 2009-2010 by i♥cabbages
# ignobleepub.py
# Copyright © 2009-2020 by i♥cabbages, Apprentice Harper et al.
# Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/>
# Modified 20102013 by some_updates, DiapDealer and Apprentice Alf
# Modified 20152017 by Apprentice Harper
# Windows users: Before running this program, you must first install Python 2.6
# from <http://www.python.org/download/> and PyCrypto from
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
# install the version for Python 2.6). Save this script file as
# ineptepub.pyw and double-click on it to run it.
#
# Mac OS X users: Save this script file as ineptepub.pyw. You can run this
# program from the command line (pythonw ineptepub.pyw) or by double-clicking
# it when it has been associated with PythonLauncher.
# Revision history:
# 1 - Initial release
# 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
# 4.0 - Work if TkInter is missing
# 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.
@ -66,10 +51,10 @@ class SafeUnbuffered:
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,bytes):
if isinstance(data,str):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
self.stream.buffer.write(data)
self.stream.buffer.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
@ -108,7 +93,7 @@ def unicode_argv():
start = argc.value - len(sys.argv)
return [argv[i] for i in
range(start, argc.value)]
return [u"ineptepub.py"]
return ["ineptepub.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
@ -257,14 +242,14 @@ def ignobleBook(inpath):
def decryptBook(keyb64, inpath, outpath):
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]
aes = AES(key)
with closing(ZipFile(open(inpath, 'rb'))) as inf:
namelist = set(inf.namelist())
if 'META-INF/rights.xml' not in namelist or \
'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
for name in META_NAMES:
namelist.remove(name)
@ -274,7 +259,7 @@ def decryptBook(keyb64, inpath, outpath):
expr = './/%s' % (adept('encryptedKey'),)
bookkey = ''.join(rights.findtext(expr))
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
bookkey = aes.decrypt(bookkey.decode('base64'))
bookkey = bookkey[:-ord(bookkey[-1])]
@ -317,7 +302,7 @@ def decryptBook(keyb64, inpath, outpath):
pass
outf.writestr(zi, decryptor.decrypt(path, data))
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 0
@ -328,13 +313,13 @@ def cli_main():
argv=unicode_argv()
progname = os.path.basename(argv[0])
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
keypath, inpath, outpath = argv[1:]
userkey = open(keypath,'rb').read()
result = decryptBook(userkey, inpath, outpath)
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
def gui_main():
@ -350,43 +335,43 @@ def gui_main():
class DecryptionDialog(Tkinter.Frame):
def __init__(self, root):
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)
body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W
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.grid(row=0, column=1, sticky=sticky)
if os.path.exists(u"bnepubkey.b64"):
self.keypath.insert(0, u"bnepubkey.b64")
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
if os.path.exists("bnepubkey.b64"):
self.keypath.insert(0, "bnepubkey.b64")
button = Tkinter.Button(body, text="...", command=self.get_keypath)
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.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)
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.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)
buttons = Tkinter.Frame(self)
buttons.pack()
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)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
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)
def get_keypath(self):
keypath = tkFileDialog.askopenfilename(
parent=None, title=u"Select Barnes & Noble \'.b64\' key file",
defaultextension=u".b64",
parent=None, title="Select Barnes & Noble \'.b64\' key file",
defaultextension=".b64",
filetypes=[('base64-encoded files', '.b64'),
('All Files', '.*')])
if keypath:
@ -397,8 +382,8 @@ def gui_main():
def get_inpath(self):
inpath = tkFileDialog.askopenfilename(
parent=None, title=u"Select B&N-encrypted ePub file to decrypt",
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
parent=None, title="Select B&N-encrypted ePub file to decrypt",
defaultextension=".epub", filetypes=[('ePub files', '.epub')])
if inpath:
inpath = os.path.normpath(inpath)
self.inpath.delete(0, Tkconstants.END)
@ -407,8 +392,8 @@ def gui_main():
def get_outpath(self):
outpath = tkFileDialog.asksaveasfilename(
parent=None, title=u"Select unencrypted ePub file to produce",
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
parent=None, title="Select unencrypted ePub file to produce",
defaultextension=".epub", filetypes=[('ePub files', '.epub')])
if outpath:
outpath = os.path.normpath(outpath)
self.outpath.delete(0, Tkconstants.END)
@ -420,31 +405,31 @@ def gui_main():
inpath = self.inpath.get()
outpath = self.outpath.get()
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
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
if not outpath:
self.status['text'] = u"Output file not specified"
self.status['text'] = "Output file not specified"
return
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
userkey = open(keypath,'rb').read()
self.status['text'] = u"Decrypting..."
self.status['text'] = "Decrypting..."
try:
decrypt_status = decryptBook(userkey, inpath, outpath)
except Exception as e:
self.status['text'] = u"Error: {0}".format(e.args[0])
self.status['text'] = "Error: {0}".format(e.args[0])
return
if decrypt_status == 0:
self.status['text'] = u"File successfully decrypted"
self.status['text'] = "File successfully decrypted"
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.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.minsize(300, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)

@ -1,9 +1,6 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import with_statement
from __future__ import print_function
# ignoblekey.py
# Copyright © 2015-2020 Apprentice Alf, Apprentice Harper et al.
@ -15,7 +12,7 @@ from __future__ import print_function
# Revision history:
# 1.0 - Initial release
# 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
@ -40,10 +37,10 @@ class SafeUnbuffered:
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,bytes):
if isinstance(data,str):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
self.stream.buffer.write(data)
self.stream.buffer.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
@ -84,7 +81,7 @@ def unicode_argv():
range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name
# this should never happen
return [u"ignoblekey.py"]
return ["ignoblekey.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
@ -106,15 +103,15 @@ def getNookLogFiles():
paths = set()
if 'LOCALAPPDATA' in os.environ.keys():
# 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):
paths.add(path)
if 'USERPROFILE' in os.environ.keys():
# 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):
paths.add(path)
path = winreg.ExpandEnvironmentStrings(u"%USERPROFILE%")+u"\\AppData\\Roaming"
path = winreg.ExpandEnvironmentStrings("%USERPROFILE%")+"\\AppData\\Roaming"
if os.path.isdir(path):
paths.add(path)
# User Shell Folders show take precedent over Shell Folders if present
@ -200,7 +197,7 @@ def nookkeys(files = []):
for file in files:
fileKeys = getKeysFromLog(file)
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)
return list(set(keys))
@ -213,27 +210,27 @@ def getkey(outpath, files=[]):
outfile = outpath
with open(outfile, 'w') as keyfileout:
keyfileout.write(keys[-1])
print(u"Saved a key to {0}".format(outfile))
print("Saved a key to {0}".format(outfile))
else:
keycount = 0
for key in keys:
while True:
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):
break
with open(outfile, 'w') as keyfileout:
keyfileout.write(key)
print(u"Saved a key to {0}".format(outfile))
print("Saved a key to {0}".format(outfile))
return True
return False
def usage(progname):
print(u"Finds the nook Study encryption keys.")
print(u"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(u"Usage:")
print(u" {0:s} [-h] [-k <logFile>] [<outpath>]".format(progname))
print("Finds the nook Study encryption keys.")
print("Keys are saved to the current directory, or a specified output directory.")
print("If a file name is passed instead of a directory, only the first key is saved, in that file.")
print("Usage:")
print(" {0:s} [-h] [-k <logFile>] [<outpath>]".format(progname))
def cli_main():
@ -241,12 +238,12 @@ def cli_main():
sys.stderr=SafeUnbuffered(sys.stderr)
argv=unicode_argv()
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:
opts, args = getopt.getopt(argv[1:], "hk:")
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)
sys.exit(2)
@ -275,7 +272,7 @@ def cli_main():
outpath = os.path.realpath(os.path.normpath(outpath))
if not getkey(outpath, files):
print(u"Could not retrieve nook Study key.")
print("Could not retrieve nook Study key.")
return 0
@ -291,7 +288,7 @@ def gui_main():
class ExceptionDialog(Tkinter.Frame):
def __init__(self, root, text):
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)
label.pack(fill=Tkconstants.X, expand=0)
self.text = Tkinter.Text(self)
@ -312,16 +309,16 @@ def gui_main():
print(key)
while True:
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):
break
with open(outfile, 'w') as keyfileout:
keyfileout.write(key)
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:
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
tkMessageBox.showerror(progname, "Error: {0}".format(str(e)))
except Exception:
root.wm_state('normal')
root.title(progname)

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

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

@ -1,7 +1,6 @@
#! /usr/bin/python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import with_statement
# ignoblepdf.py
# Copyright © 2009-2020 by Apprentice Harper et al.
@ -14,6 +13,7 @@ from __future__ import with_statement
# Revision history:
# 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)
return [argv[i] for i in
xrange(start, argc.value)]
return [u"ignoblepdf.py"]
return ["ignoblepdf.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
@ -2005,12 +2005,12 @@ class PDFSerializer(object):
def decryptBook(userkey, inpath, outpath):
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:
#try:
serializer = PDFSerializer(inf, userkey)
#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
# hope this will fix the 'bad file descriptor' problem
with open(outpath, 'wb') as outf:
@ -2018,7 +2018,7 @@ def decryptBook(userkey, inpath, outpath):
try:
serializer.dump(outf)
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 0
@ -2029,13 +2029,13 @@ def cli_main():
argv=unicode_argv()
progname = os.path.basename(argv[0])
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
keypath, inpath, outpath = argv[1:]
userkey = open(keypath,'rb').read()
result = decryptBook(userkey, inpath, outpath)
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
@ -2052,43 +2052,43 @@ def gui_main():
class DecryptionDialog(Tkinter.Frame):
def __init__(self, root):
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)
body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W
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.grid(row=0, column=1, sticky=sticky)
if os.path.exists(u"bnpdfkey.b64"):
self.keypath.insert(0, u"bnpdfkey.b64")
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
if os.path.exists("bnpdfkey.b64"):
self.keypath.insert(0, "bnpdfkey.b64")
button = Tkinter.Button(body, text="...", command=self.get_keypath)
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.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)
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.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)
buttons = Tkinter.Frame(self)
buttons.pack()
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)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
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)
def get_keypath(self):
keypath = tkFileDialog.askopenfilename(
parent=None, title=u"Select Barnes & Noble \'.b64\' key file",
defaultextension=u".b64",
parent=None, title="Select Barnes & Noble \'.b64\' key file",
defaultextension=".b64",
filetypes=[('base64-encoded files', '.b64'),
('All Files', '.*')])
if keypath:
@ -2099,8 +2099,8 @@ def gui_main():
def get_inpath(self):
inpath = tkFileDialog.askopenfilename(
parent=None, title=u"Select B&N-encrypted PDF file to decrypt",
defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')])
parent=None, title="Select B&N-encrypted PDF file to decrypt",
defaultextension=".pdf", filetypes=[('PDF files', '.pdf')])
if inpath:
inpath = os.path.normpath(inpath)
self.inpath.delete(0, Tkconstants.END)
@ -2109,8 +2109,8 @@ def gui_main():
def get_outpath(self):
outpath = tkFileDialog.asksaveasfilename(
parent=None, title=u"Select unencrypted PDF file to produce",
defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')])
parent=None, title="Select unencrypted PDF file to produce",
defaultextension=".pdf", filetypes=[('PDF files', '.pdf')])
if outpath:
outpath = os.path.normpath(outpath)
self.outpath.delete(0, Tkconstants.END)
@ -2122,28 +2122,28 @@ def gui_main():
inpath = self.inpath.get()
outpath = self.outpath.get()
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
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
if not outpath:
self.status['text'] = u"Output file not specified"
self.status['text'] = "Output file not specified"
return
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
userkey = open(keypath,'rb').read()
self.status['text'] = u"Decrypting..."
self.status['text'] = "Decrypting..."
try:
decrypt_status = decryptBook(userkey, inpath, outpath)
except Exception, e:
self.status['text'] = u"Error; {0}".format(e.args[0])
self.status['text'] = "Error; {0}".format(e.args[0])
return
if decrypt_status == 0:
self.status['text'] = u"File successfully decrypted"
self.status['text'] = "File successfully decrypted"
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()
@ -2154,7 +2154,7 @@ def gui_main():
"This script requires OpenSSL or PyCrypto, which must be installed "
"separately. Read the top-of-script comment for details.")
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.minsize(370, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)

@ -1,9 +1,7 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import with_statement
# ineptepub.pyw
# ineptepub.py
# Copyright © 2009-2020 by i♥cabbages, Apprentice Harper et al.
# 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)
return [argv[i] for i in
range(start, argc.value)]
return [u"ineptepub.py"]
return ["ineptepub.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
@ -393,13 +391,13 @@ def adeptBook(inpath):
def decryptBook(userkey, inpath, outpath):
if AES is None:
raise ADEPTError(u"PyCrypto or OpenSSL must be installed.")
raise ADEPTError("PyCrypto or OpenSSL must be installed.")
rsa = RSA(userkey)
with closing(ZipFile(open(inpath, 'rb'))) as inf:
namelist = set(inf.namelist())
if 'META-INF/rights.xml' not in namelist or \
'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
for name in META_NAMES:
namelist.remove(name)
@ -409,12 +407,12 @@ def decryptBook(userkey, inpath, outpath):
expr = './/%s' % (adept('encryptedKey'),)
bookkey = ''.join(rights.findtext(expr))
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
bookkey = rsa.decrypt(codecs.decode(bookkey.encode('ascii'), 'base64'))
# Padded as per RSAES-PKCS1-v1_5
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
encryption = inf.read('META-INF/encryption.xml')
decryptor = Decryptor(bookkey[-16:], encryption)
@ -455,7 +453,7 @@ def decryptBook(userkey, inpath, outpath):
pass
outf.writestr(zi, decryptor.decrypt(path, data))
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 0
@ -466,13 +464,13 @@ def cli_main():
argv=unicode_argv()
progname = os.path.basename(argv[0])
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
keypath, inpath, outpath = argv[1:]
userkey = open(keypath,'rb').read()
result = decryptBook(userkey, inpath, outpath)
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
def gui_main():
@ -488,43 +486,43 @@ def gui_main():
class DecryptionDialog(Tkinter.Frame):
def __init__(self, root):
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)
body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W
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.grid(row=0, column=1, sticky=sticky)
if os.path.exists(u"adeptkey.der"):
self.keypath.insert(0, u"adeptkey.der")
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
if os.path.exists("adeptkey.der"):
self.keypath.insert(0, "adeptkey.der")
button = Tkinter.Button(body, text="...", command=self.get_keypath)
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.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)
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.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)
buttons = Tkinter.Frame(self)
buttons.pack()
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)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
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)
def get_keypath(self):
keypath = tkFileDialog.askopenfilename(
parent=None, title=u"Select Adobe Adept \'.der\' key file",
defaultextension=u".der",
parent=None, title="Select Adobe Adept \'.der\' key file",
defaultextension=".der",
filetypes=[('Adobe Adept DER-encoded files', '.der'),
('All Files', '.*')])
if keypath:
@ -535,8 +533,8 @@ def gui_main():
def get_inpath(self):
inpath = tkFileDialog.askopenfilename(
parent=None, title=u"Select ADEPT-encrypted ePub file to decrypt",
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
parent=None, title="Select ADEPT-encrypted ePub file to decrypt",
defaultextension=".epub", filetypes=[('ePub files', '.epub')])
if inpath:
inpath = os.path.normpath(inpath)
self.inpath.delete(0, Tkconstants.END)
@ -545,8 +543,8 @@ def gui_main():
def get_outpath(self):
outpath = tkFileDialog.asksaveasfilename(
parent=None, title=u"Select unencrypted ePub file to produce",
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
parent=None, title="Select unencrypted ePub file to produce",
defaultextension=".epub", filetypes=[('ePub files', '.epub')])
if outpath:
outpath = os.path.normpath(outpath)
self.outpath.delete(0, Tkconstants.END)
@ -558,31 +556,31 @@ def gui_main():
inpath = self.inpath.get()
outpath = self.outpath.get()
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
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
if not outpath:
self.status['text'] = u"Output file not specified"
self.status['text'] = "Output file not specified"
return
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
userkey = open(keypath,'rb').read()
self.status['text'] = u"Decrypting..."
self.status['text'] = "Decrypting..."
try:
decrypt_status = decryptBook(userkey, inpath, outpath)
except Exception as e:
self.status['text'] = u"Error: {0}".format(e.args[0])
self.status['text'] = "Error: {0}".format(e.args[0])
return
if decrypt_status == 0:
self.status['text'] = u"File successfully decrypted"
self.status['text'] = "File successfully decrypted"
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.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.minsize(300, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)

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

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

@ -1,8 +1,6 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import with_statement
# k4mobidedrm.py
# Copyright © 2008-2020 by Apprentice Harper et al.
@ -146,7 +144,7 @@ def unicode_argv():
range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name
# this should never happen
return [u"mobidedrm.py"]
return ["mobidedrm.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
@ -161,31 +159,31 @@ def unicode_argv():
# and some improvements suggested by jhaisley
def cleanup_name(name):
# 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
name = re.sub(r"\s", u" ", name).strip()
name = re.sub(r"\s", " ", name).strip()
# delete control characters
name = u"".join(char for char in name if ord(char)>=32)
# delete non-ascii characters
name = u"".join(char for char in name if ord(char)<=126)
# remove leading dots
while len(name)>0 and name[0] == u".":
while len(name)>0 and name[0] == ".":
name = name[1:]
# remove trailing dots (Windows doesn't like them)
while name.endswith(u'.'):
while name.endswith("."):
name = name[:-1]
if len(name)==0:
name=u"DecryptedBook"
name="DecryptedBook"
return name
# must be passed unicode
def unescape(text):
def fixup(m):
text = m.group(0)
if text[:2] == u"&#":
if text[:2] == "&#":
# character reference
try:
if text[:3] == u"&#x":
if text[:3] == "&#x":
return chr(int(text[3:-1], 16))
else:
return chr(int(text[2:-1]))
@ -198,17 +196,17 @@ def unescape(text):
except KeyError:
pass
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()):
# handle the obvious cases at the beginning
if not os.path.isfile(infile):
raise DrmException(u"Input file does not exist.")
raise DrmException("Input file does not exist.")
mobi = True
magic8 = open(infile,'rb').read(8)
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]
if magic3 == 'TPZ':
@ -222,7 +220,7 @@ def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime
mb = topazextract.TopazBook(infile)
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
totalpids = list(pids)
@ -234,7 +232,7 @@ def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime
totalpids.extend(kgenpids.getPidList(md1, md2, serials, kDatabases))
# remove any duplicates
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
try:
@ -243,7 +241,7 @@ def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime
mb.cleanup
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
@ -258,7 +256,7 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
kindleDatabase = json.loads(keyfilein.read())
kDatabases.append([dbfile,kindleDatabase])
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()
@ -266,7 +264,7 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
try:
book = GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime)
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()
return 1
@ -277,7 +275,7 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
re.match('^{0-9A-F-}{36}$', orig_fn_root)
): # Kindle for PC / Mac / Android / Fire / iOS
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
outfilename = orig_fn_root
@ -285,16 +283,16 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
if len(outfilename)>150:
outfilename = outfilename[:99]+"--"+outfilename[-49:]
outfilename = outfilename+u"_nodrm"
outfilename = outfilename+"_nodrm"
outfile = os.path.join(outdir, outfilename + book.getBookExtension())
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":
zipname = os.path.join(outdir, outfilename + u"_SVG.zip")
if book.getBookType()=="Topaz":
zipname = os.path.join(outdir, outfilename + "_SVG.zip")
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
book.cleanup()
@ -302,9 +300,9 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
def usage(progname):
print(u"Removes DRM protection from Mobipocket, Amazon KF8, Amazon Print Replica and Amazon Topaz ebooks")
print(u"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("Removes DRM protection from Mobipocket, Amazon KF8, Amazon Print Replica and Amazon Topaz ebooks")
print("Usage:")
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
@ -312,12 +310,12 @@ def usage(progname):
def cli_main():
argv=unicode_argv()
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:
opts, args = getopt.getopt(argv[1:], "k:p:s:a:")
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)
sys.exit(2)
if len(args)<2:

@ -1,12 +1,9 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import with_statement
from __future__ import print_function
# 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
@ -50,13 +47,13 @@ class KFXZipBook:
data += fh.read()
if self.voucher is None:
self.decrypt_voucher(totalpids)
print(u'Decrypting KFX DRMION: {0}'.format(filename))
print("Decrypting KFX DRMION: {0}".format(filename))
outfile = StringIO()
ion.DrmIon(StringIO(data[8:-8]), lambda name: self.voucher).parse(outfile)
self.decrypted[filename] = outfile.getvalue()
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):
with zipfile.ZipFile(self.infile, 'r') as zf:
@ -70,9 +67,9 @@ class KFXZipBook:
if 'ProtectedData' in data:
break # found DRM voucher
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 dsn_len,secret_len in [(0,0), (16,0), (16,40), (32,40), (40,0), (40,40)]:
@ -89,13 +86,13 @@ class KFXZipBook:
except:
pass
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()
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))
self.voucher = voucher

@ -1,9 +1,6 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import with_statement
from __future__ import print_function
# kgenpids.py
# Copyright © 2008-2020 Apprentice Harper et al.
@ -14,7 +11,7 @@ __version__ = '3.0'
# 2.0 - 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
# 3.0 - Added Python 3 compatibility for calibre 5.0
# 3.0 - Python 3 for calibre 5.0
import sys
@ -217,18 +214,18 @@ def getK4Pids(rec209, token, kindleDatabase):
try:
# Get the DSN token, if present
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:
# See if we have the info to generate the DSN
try:
# Get the Mazama Random number
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:
# Get the SerialNumber token, if present
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:
# Get the IDString we added
IDString = bytearray.fromhex((kindleDatabase[1])['IDString']).decode()
@ -236,24 +233,24 @@ def getK4Pids(rec209, token, kindleDatabase):
try:
# Get the UsernameHash token, if present
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:
# Get the UserName we added
UserName = bytearray.fromhex((kindleDatabase[1])['UserName']).decode()
# encode it
encodedUsername = encodeHash(UserName,charMap1)
#print u"encodedUsername",encodedUsername.encode('hex')
#print "encodedUsername",encodedUsername.encode('hex')
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
# Get the ID string used
encodedIDString = encodeHash(IDString,charMap1)
#print u"encodedIDString",encodedIDString.encode('hex')
#print "encodedIDString",encodedIDString.encode('hex')
# concat, hash and encode to calculate the DSN
DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
#print u"DSN",DSN.encode('hex')
#print "DSN",DSN.encode('hex')
pass
if rec209 is None:
@ -300,14 +297,14 @@ def getPidList(md1, md2, serials=[], kDatabases=[]):
try:
pidlst.extend(getK4Pids(md1, md2, kDatabase))
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()
for serialnum in serials:
try:
pidlst.extend(getKindlePids(md1, md2, serialnum))
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()
return pidlst

@ -1,8 +1,6 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import with_statement
# kindlekey.py
# 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.6 - Start adding support for Kindle 1.25+ .kinf2018 file
# 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)]
# if we don't have any arguments at all, just pass back script name
# this should never happen
return [u"kindlekey.py"]
return ["kindlekey.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
@ -905,11 +903,11 @@ if iswindows:
# replace any non-ASCII values with 0xfffd
for i in xrange(0,len(buffer)):
if buffer[i]>u"\u007f":
#print u"swapping char "+str(i)+" ("+buffer[i]+")"
buffer[i] = u"\ufffd"
if buffer[i]>"\u007f":
#print "swapping char "+str(i)+" ("+buffer[i]+")"
buffer[i] = "\ufffd"
# 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 GetUserName
GetUserName = GetUserName()
@ -939,7 +937,7 @@ if iswindows:
n = ctypes.windll.kernel32.GetEnvironmentVariableW(name, None, 0)
if n == 0:
return None
buf = ctypes.create_unicode_buffer(u'\0'*n)
buf = ctypes.create_unicode_buffer("\0"*n)
ctypes.windll.kernel32.GetEnvironmentVariableW(name, buf, n)
return buf.value
@ -951,7 +949,7 @@ if iswindows:
path = ""
if 'LOCALAPPDATA' in os.environ.keys():
# 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.
# path = getEnvironmentVariable('LOCALAPPDATA')
if not os.path.isdir(path):
@ -979,7 +977,7 @@ if iswindows:
print ('Could not find the folder in which to look for kinfoFiles.')
else:
# 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
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2018'
@ -1166,9 +1164,9 @@ if iswindows:
# store values used in decryption
DB['IDString'] = GetIDString()
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:
print(u"Couldn't decrypt file.")
print("Couldn't decrypt file.")
DB = {}
return DB
elif isosx:
@ -1183,7 +1181,7 @@ elif isosx:
libcrypto = find_library('crypto')
if libcrypto is None:
raise DrmException(u"libcrypto not found")
raise DrmException("libcrypto not found")
libcrypto = CDLL(libcrypto)
# From OpenSSL's crypto aes header
@ -1241,14 +1239,14 @@ elif isosx:
def set_decrypt_key(self, userkey, iv):
self._blocksize = len(userkey)
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
keyctx = self._keyctx = AES_KEY()
self._iv = iv
self._userkey = userkey
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
if rv < 0:
raise DrmException(u"Failed to initialize AES key")
raise DrmException("Failed to initialize AES key")
def decrypt(self, data):
out = create_string_buffer(len(data))
@ -1256,7 +1254,7 @@ elif isosx:
keyctx = self._keyctx
rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0)
if rv == 0:
raise DrmException(u"AES decryption failed")
raise DrmException("AES decryption failed")
return out.raw
def keyivgen(self, passwd, salt, iter, keylen):
@ -1649,16 +1647,16 @@ elif isosx:
pass
if len(DB)>6:
# 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['UserName'] = GetUserName()
else:
print(u"Couldn't decrypt file.")
print("Couldn't decrypt file.")
DB = {}
return DB
else:
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 {}
def kindlekeys(files = []):
@ -1683,27 +1681,27 @@ def getkey(outpath, files=[]):
outfile = outpath
with file(outfile, 'w') as keyfileout:
keyfileout.write(json.dumps(keys[0]))
print(u"Saved a key to {0}".format(outfile))
print("Saved a key to {0}".format(outfile))
else:
keycount = 0
for key in keys:
while True:
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):
break
with file(outfile, 'w') as keyfileout:
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 False
def usage(progname):
print(u"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(u"If a file name is passed instead of a directory, only the first key is saved, in that file.")
print(u"Usage:")
print(u" {0:s} [-h] [-k <kindle.info>] [<outpath>]".format(progname))
print("Finds, decrypts and saves the default Kindle For Mac/PC encryption keys.")
print("Keys are saved to the current directory, or a specified output directory.")
print("If a file name is passed instead of a directory, only the first key is saved, in that file.")
print("Usage:")
print(" {0:s} [-h] [-k <kindle.info>] [<outpath>]".format(progname))
def cli_main():
@ -1711,12 +1709,12 @@ def cli_main():
sys.stderr=SafeUnbuffered(sys.stderr)
argv=unicode_argv()
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:
opts, args = getopt.getopt(argv[1:], "hk:")
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)
sys.exit(2)
@ -1745,7 +1743,7 @@ def cli_main():
outpath = os.path.realpath(os.path.normpath(outpath))
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
@ -1761,7 +1759,7 @@ def gui_main():
class ExceptionDialog(Tkinter.Frame):
def __init__(self, root, text):
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)
label.pack(fill=Tkconstants.X, expand=0)
self.text = Tkinter.Text(self)
@ -1781,16 +1779,16 @@ def gui_main():
for key in keys:
while True:
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):
break
with file(outfile, 'w') as keyfileout:
keyfileout.write(json.dumps(key))
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:
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
tkMessageBox.showerror(progname, "Error: {0}".format(str(e)))
except Exception:
root.wm_state('normal')
root.title(progname)

@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Mobipocket PID calculator v0.4 for Amazon Kindle.
@ -10,7 +10,7 @@
# 0.3 updated for unicode
# 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
# 1.0 Added Python 3 compatibility for calibre 5.0
# 1.0 Python 3 for calibre 5.0
from __future__ import print_function
import sys
@ -67,7 +67,7 @@ def unicode_argv():
range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name
# this should never happen
return [u"kindlepid.py"]
return ["kindlepid.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
@ -111,28 +111,28 @@ def pidFromSerial(s, l):
return pid
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()
if len(argv)==2:
serial = argv[1]
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
if len(serial)==16:
if serial.startswith("B") or serial.startswith("9"):
print(u"Kindle serial number detected")
print("Kindle serial number detected")
else:
print(u"Warning: unrecognized serial number. Please recheck input.")
print("Warning: unrecognized serial number. Please recheck input.")
return 1
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
elif len(serial)==40:
print(u"iPhone serial number (UDID) detected")
print("iPhone serial number (UDID) detected")
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
print(u"Warning: unrecognized serial number. Please recheck input.")
print("Warning: unrecognized serial number. Please recheck input.")
return 1

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# mobidedrm.py
@ -7,7 +7,7 @@
from __future__ import print_function
__license__ = 'GPL v3'
__version__ = u"1.00"
__version__ = "1.00"
# This is a python script. You need a Python interpreter to run it.
# For example, ActiveState Python, which exists for windows.
@ -82,7 +82,7 @@ import binascii
try:
from alfcrypto import Pukall_Cipher
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
# and also make sure that any unicode strings get
@ -135,7 +135,7 @@ def unicode_argv():
range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name
# this should never happen
return [u"mobidedrm.py"]
return ["mobidedrm.py"]
else:
argvencoding = sys.stdin.encoding
if argvencoding == None:
@ -166,7 +166,7 @@ def PC1(key, src, decryption=True):
sum2 = 0;
keyXorVal = 0;
if len(key)!=16:
DrmException (u"PC1: Bad key length")
DrmException ("PC1: Bad key length")
wkey = []
for i in range(8):
wkey.append(key[i*2]<<8 | key[i*2+1])
@ -246,19 +246,19 @@ class MobiBook:
pass
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:
from alfcrypto import Pukall_Cipher
except:
print(u"AlfCrypto not found. Using python PC1 implementation.")
print("AlfCrypto not found. Using python PC1 implementation.")
# initial sanity check on file
self.data_file = open(infile, 'rb').read()
self.mobi_data = ''
self.header = self.data_file[0:78]
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.crypto_type = -1
@ -284,16 +284,16 @@ class MobiBook:
self.mobi_version = -1
if self.magic == 'TEXtREAd':
print(u"PalmDoc format book detected.")
print("PalmDoc format book detected.")
return
self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
self.mobi_codepage, = struct.unpack('>L',self.sect[0x1c:0x20])
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):
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):
# multibyte utf8 data is included in the encryption for PalmDoc compression
# 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')
pos += size
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):
codec_map = {
@ -411,51 +411,51 @@ class MobiBook:
def getBookType(self):
if self.print_replica:
return u"Print Replica"
return "Print Replica"
if self.mobi_version >= 8:
return u"Kindle Format 8"
return "Kindle Format 8"
if self.mobi_version >= 0:
return u"Mobipocket {0:d}".format(self.mobi_version)
return u"PalmDoc"
return "Mobipocket {0:d}".format(self.mobi_version)
return "PalmDoc"
def getBookExtension(self):
if self.print_replica:
return u".azw4"
return ".azw4"
if self.mobi_version >= 8:
return u".azw3"
return u".mobi"
return ".azw3"
return ".mobi"
def processBook(self, pidlist):
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
if crypto_type == 0:
print(u"This book is not encrypted.")
print("This book is not encrypted.")
# we must still check for Print Replica
self.print_replica = (self.loadSection(1)[0:4] == '%MOP')
self.mobi_data = self.data_file
return
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:
data406 = self.meta_array[406]
val406, = struct.unpack('>Q',data406)
if val406 != 0:
raise DrmException(u"Cannot decode library or rented ebooks.")
raise DrmException("Cannot decode library or rented ebooks.")
goodpids = []
# print("DEBUG ==== pidlist = ", pidlist)
for pid in pidlist:
if len(pid)==10:
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])
elif len(pid)==8:
goodpids.append(pid)
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:
t1_keyvec = 'QDCVEPMU675RUBSZ'
@ -471,32 +471,32 @@ class MobiBook:
# calculate the keys
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16])
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)
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
self.patchSection(0, b'\0' * drm_size, drm_ptr)
# kill the drm pointers
self.patchSection(0, b'\xff' * 4 + b'\0' * 12, 0xA8)
if pid=='00000000':
print(u"File has default encryption, no specific key needed.")
print("File has default encryption, no specific key needed.")
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
self.patchSection(0, b'\0' * 2, 0xC)
# decrypt sections
print(u"Decrypting. Please wait . . .", end=' ')
print("Decrypting. Please wait . . .", end=' ')
mobidataList = []
mobidataList.append(self.data_file[:self.sections[1][0]])
for i in range(1, self.records+1):
data = self.loadSection(i)
extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags)
if i%100 == 0:
print(u".", end=' ')
print(".", end=' ')
# print "record %d, extra_size %d" %(i,extra_size)
decoded_data = PC1(found_key, data[0:len(data) - extra_size])
if i==1:
@ -507,12 +507,12 @@ class MobiBook:
if self.num_sections > self.records+1:
mobidataList.append(self.data_file[self.sections[self.records+1][0]:])
self.mobi_data = b''.join(mobidataList)
print(u"done")
print("done")
return
def getUnencryptedBook(infile,pidlist):
if not os.path.isfile(infile):
raise DrmException(u"Input File Not Found.")
raise DrmException("Input File Not Found.")
book = MobiBook(infile)
book.processBook(pidlist)
return book.mobi_data
@ -522,10 +522,10 @@ def cli_main():
argv=unicode_argv()
progname = os.path.basename(argv[0])
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(u"Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks")
print(u"Usage:")
print(u" {0} <infile> <outfile> [<Comma separated list of PIDs to try>]".format(progname))
print("MobiDeDrm v{0:s}.\nCopyright © 2008-2017 The Dark Reverser, Apprentice Harper et al.".format(__version__))
print("Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks")
print("Usage:")
print(" {0} <infile> <outfile> [<Comma separated list of PIDs to try>]".format(progname))
return 1
else:
infile = argv[1]
@ -538,7 +538,7 @@ def cli_main():
stripped_file = getUnencryptedBook(infile, pidlist)
open(outfile, 'wb').write(stripped_file)
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 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
from __future__ import with_statement
__license__ = 'GPL v3'
# Standard Python modules.
@ -15,7 +15,7 @@ from calibre.constants import iswindows, isosx
class DeDRM_Prefs():
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.defaults['configured'] = False
@ -98,7 +98,7 @@ def convertprefs(always = False):
try:
name, ccn = keystring.split(',')
# 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)
userkeys.append([keyname,keyvalue])
except Exception as e:
@ -115,7 +115,7 @@ def convertprefs(always = False):
try:
name, cc = keystring.split(',')
# 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')
userkeys.append([keyname,keyvalue])
except Exception as e:
@ -161,15 +161,15 @@ def convertprefs(always = False):
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"
EREADERPLUGINNAME = "eReader PDB 2 PML"
OLDKINDLEPLUGINNAME = "K4PC, K4Mac, Kindle Mobi and Topaz DeDRM"
# get prefs from older tools
kindleprefs = JSONConfig(os.path.join(u"plugins", u"K4MobiDeDRM"))
ignobleprefs = JSONConfig(os.path.join(u"plugins", u"ignoble_epub_dedrm"))
kindleprefs = JSONConfig(os.path.join("plugins", "K4MobiDeDRM"))
ignobleprefs = JSONConfig(os.path.join("plugins", "ignoble_epub_dedrm"))
# Handle the old ignoble plugin's customization string by converting the
# 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']
val = sc.pop(IGNOBLEPLUGINNAME, 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'])
userkeys = parseIgnobleString(str(val))
for keypair in userkeys:
@ -185,7 +185,7 @@ def convertprefs(always = False):
value = keypair[1]
dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
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
dedrmprefs.writeprefs(False)
@ -193,7 +193,7 @@ def convertprefs(always = False):
# old string to stored keys... get that personal data out of plain sight.
val = sc.pop(EREADERPLUGINNAME, 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'])
userkeys = parseeReaderString(str(val))
for keypair in userkeys:
@ -201,14 +201,14 @@ def convertprefs(always = False):
value = keypair[1]
dedrmprefs.addnamedvaluetoprefs('ereaderkeys', name, value)
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
dedrmprefs.writeprefs(False)
# get old Kindle plugin configuration string
val = sc.pop(OLDKINDLEPLUGINNAME, 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'])
priorserialcount = len(dedrmprefs['serials'])
pids, serials = parseKindleString(val)
@ -218,7 +218,7 @@ def convertprefs(always = False):
dedrmprefs.addvaluetoprefs('serials',serial)
addedpidcount = len(dedrmprefs['pids']) - priorpidcount
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
dedrmprefs.writeprefs(False)
@ -234,7 +234,7 @@ def convertprefs(always = False):
dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount
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
dedrmprefs.writeprefs(False)
@ -247,7 +247,7 @@ def convertprefs(always = False):
dedrmprefs.addnamedvaluetoprefs('adeptkeys', name, value)
addedkeycount = len(dedrmprefs['adeptkeys'])-priorkeycount
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
dedrmprefs.writeprefs(False)
@ -260,7 +260,7 @@ def convertprefs(always = False):
addedkeycount = len(dedrmprefs['bandnkeys']) - priorkeycount
# no need to delete old prefs, since they contain no recoverable private data
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
dedrmprefs.writeprefs(False)
@ -277,19 +277,19 @@ def convertprefs(always = False):
dedrmprefs.addvaluetoprefs('serials',serial)
addedpidcount = len(dedrmprefs['pids']) - priorpidcount
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
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:
if 'wineprefix' in kindleprefs and kindleprefs['wineprefix'] != "":
dedrmprefs.set('adobewineprefix',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:
traceback.print_exc()
# Make the json write all the prefs to disk
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 -*-
# 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
import sys

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

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

@ -1,9 +1,6 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import with_statement
from __future__ import print_function
__license__ = 'GPL v3'
# Standard Python modules.
@ -17,13 +14,13 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
import subasyncio
from subasyncio import Process
if extension == u".k4i":
if extension == ".k4i":
import json
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):
os.makedirs(outdirpath)
@ -31,29 +28,29 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
wineprefix = os.path.abspath(os.path.expanduser(os.path.expandvars(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:
cmdline = u"wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath)
print(u"{0} v{1}: Command line: '{2}'".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline))
cmdline = "wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath)
print("{0} v{1}: Command line: '{2}'".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline))
try:
cmdline = cmdline.encode(sys.getfilesystemencoding())
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False)
result = p2.wait("wait")
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):
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:
cmdline = u"wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath)
print(u"{0} v{1}: Command line: “{2}".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline))
cmdline = "wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath)
print("{0} v{1}: Command line: “{2}".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline))
try:
cmdline = cmdline.encode(sys.getfilesystemencoding())
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False)
result = p2.wait("wait")
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
winekeys = []
@ -63,14 +60,14 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
try:
fpath = os.path.join(outdirpath, filename)
with open(fpath, 'rb') as keyfile:
if extension == u".k4i":
if extension == ".k4i":
new_key_value = json.loads(keyfile.read())
else:
new_key_value = keyfile.read()
winekeys.append(new_key_value)
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()
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

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

@ -1,8 +1,8 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 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
# <http://www.gnu.org/licenses/>
@ -10,7 +10,7 @@
# Revision history:
# 1.0 - Initial release
# 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).

@ -1,8 +1,8 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 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'

@ -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
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2012, David Forrester <davidfor@internode.on.net>'
@ -9,13 +8,7 @@ __docformat__ = 'restructuredtext en'
import os, time, re, sys
from datetime import datetime
try:
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,
from PyQt5.Qt import (Qt, QIcon, QPixmap, QLabel, QDialog, QHBoxLayout, QProgressBar,
QTableWidgetItem, QFont, QLineEdit, QComboBox,
QVBoxLayout, QDialogButtonBox, QStyledItemDelegate, QDateTime,
QRegExpValidator, QRegExp, QDate, QDateEdit)

@ -1,16 +1,10 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
try:
from PyQt5.Qt import (Qt, QGroupBox, QListWidget, QLineEdit, QDialogButtonBox, QWidget, QLabel, QDialog, QVBoxLayout, QAbstractItemView, QIcon, QHBoxLayout, QComboBox, QListWidgetItem, QFileDialog)
except ImportError:
from PyQt4.Qt import (Qt, QGroupBox, QListWidget, QLineEdit, QDialogButtonBox, QWidget, QLabel, QDialog, QVBoxLayout, QAbstractItemView, QIcon, QHBoxLayout, QComboBox, QListWidgetItem, QFileDialog)
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
try:
from PyQt5 import Qt as QtGui
except ImportError:
from PyQt4 import QtGui
from PyQt5.Qt import (Qt, QGroupBox, QListWidget, QLineEdit, QDialogButtonBox, QWidget, QLabel, QDialog, QVBoxLayout, QAbstractItemView, QIcon, QHBoxLayout, QComboBox, QListWidgetItem, QFileDialog)
from PyQt5 import Qt as QtGui
from calibre.gui2 import (error_dialog, question_dialog, info_dialog, open_url)
from calibre.utils.config import JSONConfig, config_dir
@ -50,25 +44,25 @@ class ConfigWidget(QWidget):
self.find_homes.setCurrentIndex(index)
self.serials_button = QtGui.QPushButton(self)
self.serials_button.setToolTip(_(u"Click to manage Kobo serial numbers for Kobo ebooks"))
self.serials_button.setText(u"Kobo devices serials")
self.serials_button.setToolTip(_("Click to manage Kobo serial numbers for Kobo ebooks"))
self.serials_button.setText("Kobo devices serials")
self.serials_button.clicked.connect(self.edit_serials)
layout.addWidget(self.serials_button)
self.kobo_directory_button = QtGui.QPushButton(self)
self.kobo_directory_button.setToolTip(_(u"Click to specify the Kobo directory"))
self.kobo_directory_button.setText(u"Kobo directory")
self.kobo_directory_button.setToolTip(_("Click to specify the Kobo directory"))
self.kobo_directory_button.setText("Kobo directory")
self.kobo_directory_button.clicked.connect(self.edit_kobo_directory)
layout.addWidget(self.kobo_directory_button)
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_()
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:
self.kobodirectory = tmpkobodirectory
@ -91,7 +85,7 @@ class ManageKeysDialog(QDialog):
self.plugin_keys = plugin_keys
self.create_key = create_key
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))
@ -99,13 +93,13 @@ class ManageKeysDialog(QDialog):
layout = QVBoxLayout(self)
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)
keys_group_box_layout = QHBoxLayout()
keys_group_box.setLayout(keys_group_box_layout)
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.populate_list()
keys_group_box_layout.addWidget(self.listy)
@ -114,12 +108,12 @@ class ManageKeysDialog(QDialog):
keys_group_box_layout.addLayout(button_layout)
self._add_key_button = QtGui.QToolButton(self)
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)
button_layout.addWidget(self._add_key_button)
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.clicked.connect(self.delete_key)
button_layout.addWidget(self._delete_key_button)
@ -155,7 +149,7 @@ class ManageKeysDialog(QDialog):
new_key_value = d.key_value
if new_key_value in self.plugin_keys:
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
self.plugin_keys.append(d.key_value)
@ -166,7 +160,7 @@ class ManageKeysDialog(QDialog):
if not self.listy.currentItem():
return
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
self.plugin_keys.remove(keyname)
@ -177,7 +171,7 @@ class AddSerialDialog(QDialog):
def __init__(self, parent=None,):
QDialog.__init__(self, 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)
self.setLayout(layout)
@ -188,9 +182,9 @@ class AddSerialDialog(QDialog):
key_group = QHBoxLayout()
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.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)
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
@ -210,9 +204,9 @@ class AddSerialDialog(QDialog):
def accept(self):
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)
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)
QDialog.accept(self)

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Version 4.0.0 September 2020
@ -156,7 +156,7 @@
from __future__ import print_function
__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 os
@ -176,10 +176,10 @@ import tempfile
can_parse_xml = True
try:
from xml.etree import ElementTree as ET
# print u"using xml.etree for xml parsing"
# print "using xml.etree for xml parsing"
except ImportError:
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
KOBO_HASH_KEYS = ['88b3a2e13', 'XzUhGYdFp', 'NoCanLook','QJhwzAtXL']
@ -279,10 +279,10 @@ class SafeUnbuffered:
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,bytes):
if isinstance(data,str):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
self.stream.buffer.write(data)
self.stream.buffer.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
@ -312,9 +312,9 @@ class KoboLibrary(object):
# step 1. check whether this looks like a real device
if (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
kobodb = os.path.join(self.kobodir, u"KoboReader.sqlite")
kobodb = os.path.join(self.kobodir, "KoboReader.sqlite")
if (not(os.path.isfile(kobodb))):
# device path seems to be wrong, unset it
device_path = u""
@ -326,22 +326,22 @@ class KoboLibrary(object):
if (len(serials) == 0):
# we got a device path but no saved serial
# 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
if can_parse_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)):
# print u"trying to parse {0}".format(devicexml)
# print "trying to parse {0}".format(devicexml)
xmltree = ET.parse(devicexml)
for node in xmltree.iter():
if "deviceSerial" in node.tag:
serial = node.text
# print u"found serial {0}".format(serial)
# print "found serial {0}".format(serial)
serials.append(serial)
break
else:
# print u"cannot get serials from device."
# print "cannot get serials from device."
device_path = u""
self.kobodir = u""
kobodb = u""
@ -357,19 +357,19 @@ class KoboLibrary(object):
if sys.getwindowsversion().major > 5:
if 'LOCALAPPDATA' in os.environ.keys():
# 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 'USERPROFILE' in os.environ.keys():
# 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(self.kobodir, u"Kobo", u"Kobo Desktop Edition")
self.kobodir = os.path.join(winreg.ExpandEnvironmentStrings("%USERPROFILE%"), "Local Settings", "Application Data")
self.kobodir = os.path.join(self.kobodir, "Kobo", "Kobo Desktop Edition")
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:
# 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
kobodb = os.path.join(self.kobodir, u"Kobo.sqlite")
kobodb = os.path.join(self.kobodir, "Kobo.sqlite")
# check for existence of file
if (not(os.path.isfile(kobodb))):
# give up here, we haven't found anything useful
@ -377,7 +377,7 @@ class KoboLibrary(object):
kobodb = 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
# so we can ensure it's not using WAL logging which sqlite3 can't do.
self.newdb = tempfile.NamedTemporaryFile(mode='wb', delete=False)
@ -437,7 +437,7 @@ class KoboLibrary(object):
def __bookfile (self, volumeid):
"""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):
"""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)
matches = c.findall(output)
for m in matches:
# print u"m:{0}".format(m[0])
# print "m:{0}".format(m[0])
macaddrs.append(m[0].upper())
else:
# probably linux
@ -607,32 +607,32 @@ class KoboFile(object):
# assume utf-8 with no BOM
textoffset = 0
stride = 1
print(u"Checking text:{0}:".format(contents[:10]))
print("Checking text:{0}:".format(contents[:10]))
# check for byte order mark
if contents[:3]==b"\xef\xbb\xbf":
# seems to be utf-8 with BOM
print(u"Could be utf-8 with BOM")
print("Could be utf-8 with BOM")
textoffset = 3
elif contents[:2]==b"\xfe\xff":
# seems to be utf-16BE
print(u"Could be utf-16BE")
print("Could be utf-16BE")
textoffset = 3
stride = 2
elif contents[:2]==b"\xff\xfe":
# seems to be utf-16LE
print(u"Could be utf-16LE")
print("Could be utf-16LE")
textoffset = 2
stride = 2
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
for i in range(textoffset,textoffset+5*stride,stride):
if contents[i]<32 or contents[i]>127:
# 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
print(u"Seems to be good text")
print("Seems to be good text")
return True
if contents[:5]==b"<?xml" or contents[:8]==b"\xef\xbb\xbf<?xml":
# utf-8
@ -653,13 +653,13 @@ class KoboFile(object):
# utf-16LE of weird <!DOCTYPE start
return True
else:
print(u"Bad XML: {0}".format(contents[:8]))
print("Bad XML: {0}".format(contents[:8]))
raise ValueError
elif self.mimetype == 'image/jpeg':
if contents[:3] == b'\xff\xd8\xff':
return True
else:
print(u"Bad JPEG: {0}".format(contents[:3].hex()))
print("Bad JPEG: {0}".format(contents[:3].hex()))
raise ValueError()
return False
@ -682,18 +682,18 @@ class KoboFile(object):
return contents
def decrypt_book(book, lib):
print(u"Converting {0}".format(book.title))
print("Converting {0}".format(book.title))
zin = zipfile.ZipFile(book.filename, "r")
# 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'):
print(u"DRM-free book, conversion is not needed")
print("DRM-free book, conversion is not needed")
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
result = 1
for userkey in lib.userkeys:
print(u"Trying key: {0}".format(userkey.hex()))
print("Trying key: {0}".format(userkey.hex()))
try:
zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
for filename in zin.namelist():
@ -705,12 +705,12 @@ def decrypt_book(book, lib):
file.check(contents)
zout.writestr(filename, contents)
zout.close()
print(u"Decryption succeeded.")
print(u"Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
print("Decryption succeeded.")
print("Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
result = 0
break
except ValueError:
print(u"Decryption failed.")
print("Decryption failed.")
zout.close()
os.remove(outname)
zin.close()
@ -719,7 +719,7 @@ def decrypt_book(book, lib):
def cli_main():
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.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")
@ -735,25 +735,25 @@ def cli_main():
books = lib.books
else:
for i, book in enumerate(lib.books):
print(u"{0}: {1}".format(i + 1, book.title))
print(u"Or 'all'")
print("{0}: {1}".format(i + 1, book.title))
print("Or 'all'")
choice = input(u"Convert book number... ")
if choice == u'all':
choice = input("Convert book number... ")
if choice == "all":
books = list(lib.books)
else:
try:
num = int(choice)
books = [lib.books[num - 1]]
except (ValueError, IndexError):
print(u"Invalid choice. Exiting...")
print("Invalid choice. Exiting...")
exit()
results = [decrypt_book(book, lib) for book in books]
lib.close()
overall_result = all(result != 0 for result in results)
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

@ -1,6 +1,6 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 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'
__docformat__ = 'restructuredtext en'
@ -13,10 +13,7 @@ except ImportError:
from io import StringIO
from traceback import print_exc
try:
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 PyQt5.Qt import (Qt, QDialog, QPixmap, QIcon, QLabel, QHBoxLayout, QFont, QTableWidgetItem)
from calibre.utils.config import config_dir
from calibre.constants import iswindows, DEBUG

@ -153,7 +153,7 @@
from __future__ import print_function
__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 os
@ -173,10 +173,10 @@ import tempfile
can_parse_xml = True
try:
from xml.etree import ElementTree as ET
# print u"using xml.etree for xml parsing"
# print "using xml.etree for xml parsing"
except ImportError:
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
KOBO_HASH_KEYS = ['88b3a2e13', 'XzUhGYdFp', 'NoCanLook','QJhwzAtXL']
@ -309,9 +309,9 @@ class KoboLibrary(object):
# step 1. check whether this looks like a real device
if (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
kobodb = os.path.join(self.kobodir, u"KoboReader.sqlite")
kobodb = os.path.join(self.kobodir, "KoboReader.sqlite")
if (not(os.path.isfile(kobodb))):
# device path seems to be wrong, unset it
device_path = u""
@ -323,22 +323,22 @@ class KoboLibrary(object):
if (len(serials) == 0):
# we got a device path but no saved serial
# 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
if can_parse_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)):
# print u"trying to parse {0}".format(devicexml)
# print "trying to parse {0}".format(devicexml)
xmltree = ET.parse(devicexml)
for node in xmltree.iter():
if "deviceSerial" in node.tag:
serial = node.text
# print u"found serial {0}".format(serial)
# print "found serial {0}".format(serial)
serials.append(serial)
break
else:
# print u"cannot get serials from device."
# print "cannot get serials from device."
device_path = u""
self.kobodir = u""
kobodb = u""
@ -350,19 +350,19 @@ class KoboLibrary(object):
if sys.getwindowsversion().major > 5:
if 'LOCALAPPDATA' in os.environ.keys():
# 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 'USERPROFILE' in os.environ.keys():
# 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(self.kobodir, u"Kobo", u"Kobo Desktop Edition")
self.kobodir = os.path.join(winreg.ExpandEnvironmentStrings("%USERPROFILE%"), "Local Settings", "Application Data")
self.kobodir = os.path.join(self.kobodir, "Kobo", "Kobo Desktop Edition")
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:
# 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
kobodb = os.path.join(self.kobodir, u"Kobo.sqlite")
kobodb = os.path.join(self.kobodir, "Kobo.sqlite")
# check for existence of file
if (not(os.path.isfile(kobodb))):
# give up here, we haven't found anything useful
@ -371,7 +371,7 @@ class KoboLibrary(object):
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
# so we can ensure it's not using WAL logging which sqlite3 can't do.
self.newdb = tempfile.NamedTemporaryFile(mode='wb', delete=False)
@ -431,7 +431,7 @@ class KoboLibrary(object):
def __bookfile (self, volumeid):
"""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):
"""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)
matches = c.findall(output)
for m in matches:
# print u"m:{0}".format(m[0])
# print "m:{0}".format(m[0])
macaddrs.append(m[0].upper())
elif sys.platform.startswith('linux'):
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
textoffset = 0
stride = 1
print(u"Checking text:{0}:".format(contents[:10]))
print("Checking text:{0}:".format(contents[:10]))
# check for byte order mark
if contents[:3]=="\xef\xbb\xbf":
# seems to be utf-8 with BOM
print(u"Could be utf-8 with BOM")
print("Could be utf-8 with BOM")
textoffset = 3
elif contents[:2]=="\xfe\xff":
# seems to be utf-16BE
print(u"Could be utf-16BE")
print("Could be utf-16BE")
textoffset = 3
stride = 2
elif contents[:2]=="\xff\xfe":
# seems to be utf-16LE
print(u"Could be utf-16LE")
print("Could be utf-16LE")
textoffset = 2
stride = 2
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
for i in xrange(textoffset,textoffset+5*stride,stride):
if ord(contents[i])<32 or ord(contents[i])>127:
# 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
print(u"Seems to be good text")
print("Seems to be good text")
return True
if contents[:5]=="<?xml" or contents[:8]=="\xef\xbb\xbf<?xml":
# utf-8
@ -642,13 +642,13 @@ class KoboFile(object):
# utf-16LE of weird <!DOCTYPE start
return True
else:
print(u"Bad XML: {0}".format(contents[:8]))
print("Bad XML: {0}".format(contents[:8]))
raise ValueError
elif self.mimetype == 'image/jpeg':
if contents[:3] == '\xff\xd8\xff':
return True
else:
print(u"Bad JPEG: {0}".format(contents[:3].encode('hex')))
print("Bad JPEG: {0}".format(contents[:3].encode('hex')))
raise ValueError()
return False
@ -671,18 +671,18 @@ class KoboFile(object):
return contents
def decrypt_book(book, lib):
print(u"Converting {0}".format(book.title))
print("Converting {0}".format(book.title))
zin = zipfile.ZipFile(book.filename, "r")
# 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'):
print(u"DRM-free book, conversion is not needed")
print("DRM-free book, conversion is not needed")
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
result = 1
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:
zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
for filename in zin.namelist():
@ -694,12 +694,12 @@ def decrypt_book(book, lib):
file.check(contents)
zout.writestr(filename, contents)
zout.close()
print(u"Decryption succeeded.")
print(u"Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
print("Decryption succeeded.")
print("Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
result = 0
break
except ValueError:
print(u"Decryption failed.")
print("Decryption failed.")
zout.close()
os.remove(outname)
zin.close()
@ -708,7 +708,7 @@ def decrypt_book(book, lib):
def cli_main():
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.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")
@ -724,25 +724,25 @@ def cli_main():
books = lib.books
else:
for i, book in enumerate(lib.books):
print(u"{0}: {1}".format(i + 1, book.title))
print(u"Or 'all'")
print("{0}: {1}".format(i + 1, book.title))
print("Or 'all'")
choice = raw_input(u"Convert book number... ")
if choice == u'all':
choice = raw_input("Convert book number... ")
if choice == "all":
books = list(lib.books)
else:
try:
num = int(choice)
books = [lib.books[num - 1]]
except (ValueError, IndexError):
print(u"Invalid choice. Exiting...")
print("Invalid choice. Exiting...")
exit()
results = [decrypt_book(book, lib) for book in books]
lib.close()
overall_result = all(result != 0 for result in results)
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

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

Loading…
Cancel
Save