summaryrefslogtreecommitdiffstats
path: root/DeDRM_calibre_plugin
diff options
context:
space:
mode:
authorApprentice Harper <[email protected]>2015-03-09 07:38:31 +0000
committerApprentice Alf <[email protected]>2015-03-09 07:41:07 +0000
commit9d9c879413e8ea2bc805399c15f2e94b02f48e1d (patch)
treead223063f4bf8e3de5abdb68c28277ab41cc4c19 /DeDRM_calibre_plugin
parentc4fc10395b1a21ae9b0f76f4bbbf553e52129bba (diff)
tools v6.2.0
Updated for B&N new scheme, added obok plugin, and many minor fixes,
Diffstat (limited to 'DeDRM_calibre_plugin')
-rw-r--r--DeDRM_calibre_plugin/DeDRM_plugin.zipbin345078 -> 341636 bytes
-rw-r--r--DeDRM_calibre_plugin/DeDRM_plugin/DeDRM_Barnes and Noble Key_Help.htm22
-rw-r--r--DeDRM_calibre_plugin/DeDRM_plugin/__init__.py182
-rw-r--r--DeDRM_calibre_plugin/DeDRM_plugin/activitybar.py75
-rw-r--r--DeDRM_calibre_plugin/DeDRM_plugin/android.py54
-rw-r--r--DeDRM_calibre_plugin/DeDRM_plugin/android_readme.txt5
-rw-r--r--DeDRM_calibre_plugin/DeDRM_plugin/argv_utils.py88
-rw-r--r--DeDRM_calibre_plugin/DeDRM_plugin/askfolder_ed.py211
-rw-r--r--DeDRM_calibre_plugin/DeDRM_plugin/dialogs.py719
-rw-r--r--DeDRM_calibre_plugin/DeDRM_plugin/ignoblekey.py335
-rw-r--r--DeDRM_calibre_plugin/DeDRM_plugin/ineptepub.py5
-rw-r--r--DeDRM_calibre_plugin/DeDRM_plugin/scriptinterface.py176
-rw-r--r--DeDRM_calibre_plugin/DeDRM_plugin/scrolltextwidget.py27
-rw-r--r--DeDRM_calibre_plugin/DeDRM_plugin/simpleprefs.py77
-rw-r--r--DeDRM_calibre_plugin/DeDRM_plugin_ReadMe.txt43
15 files changed, 1920 insertions, 99 deletions
diff --git a/DeDRM_calibre_plugin/DeDRM_plugin.zip b/DeDRM_calibre_plugin/DeDRM_plugin.zip
index f79989e..54af1ae 100644
--- a/DeDRM_calibre_plugin/DeDRM_plugin.zip
+++ b/DeDRM_calibre_plugin/DeDRM_plugin.zip
Binary files differ
diff --git a/DeDRM_calibre_plugin/DeDRM_plugin/DeDRM_Barnes and Noble Key_Help.htm b/DeDRM_calibre_plugin/DeDRM_plugin/DeDRM_Barnes and Noble Key_Help.htm
index 5e8dac5..47da891 100644
--- a/DeDRM_calibre_plugin/DeDRM_plugin/DeDRM_Barnes and Noble Key_Help.htm
+++ b/DeDRM_calibre_plugin/DeDRM_plugin/DeDRM_Barnes and Noble Key_Help.htm
@@ -24,14 +24,20 @@ li {margin-top: 0.5em}
<h3>Changes at Barnes & Noble</h3>
-<p>In mid-2014, Barnes & Noble changed the way they generated encryption keys. Instead of deriving the key from the user's name and credit card number, they started generating a random key themselves, sending that key through to devices when they connected to the B&N servers. This means that some users will find that no combination of their name and CC# will work in decrypting their ebooks.</p>
-
-<p>There is a work-around. B&N's desktop apps for Mac and Windows (nook for Mac/PC, nookStudy) generate a log file that contains the encryption key. For Mac the log file can be found somewhere in [user folder]/Library/Application Support/Barnes & Noble/ and for Windows the log file can be found somewhere in C:\Users\admin\AppData\Roaming\Barnes & Noble\</p>
-<p>In both cases, the log file will be called BNClientLog.txt</p>
-<p>You will need to open the application, sign in to your account, and download your books, checking that you can read them in the application, and then quit the application.</p>
-<p>Then you must open the log file in a text editor and search for CCHashResponseV1. Immediately after that text should be some more text, similar to ccHash: "rLYiGD+vcPoXvsj/87kDAb1AkBy="<p>
-<p>Copy the text after ccHash, include the " marks. Save it in a new text file, with file name extension .b64</p>
-<p>Follow the instructions below "Importing Existing Keyfiles:" to import that newly saved file into the preferences.</p>
+<p>In mid-2014, Barnes & Noble changed the way they generated encryption keys. Instead of deriving the key from the user's name and credit card number, they started generating a random key themselves, sending that key through to devices when they connected to the Barnes & Noble servers. This means that some users will find that no combination of their name and CC# will work in decrypting their ebooks.</p>
+
+<p>There is a work-around. Barnes & Noble’s desktop app NOOK Study generates a log file that contains the encryption key. You can download NOOK Study from <a href="https://yuzu.com/nsdownload">https://yuzu.com/nsdownload</a>.</p>
+<p>Once downloaded, install the application, register with your Barnes & Noble or nook account, and download at least one DRMed ebook through NOOK Study. It will be saved somewhere in a folder called "My Barnes & Noble eBooks" in your Documents folder.</p>
+<p>Now import that book into calibre. The log file and the key in the log should be automatically found by the plugin and used to decrypt the book.</p>
+<p>If the automatic process doesn't work for you, you can still find extract it manually and save it as a .b64 file for import into the plugin's preferences as follows:</p>
+<ol><li>In NOOK Study, select Settings/About (Windows) or NOOK Study/About NOOK Study (Mac) and in the dialog that appears click the link at the bottom to copy the log into the clipboard.</li>
+<li>Paste the copied log into a text editor</li>
+<li>Search for the text CCHashResponseV1</li>
+<li>On the line below which starts with ccHash, copy the text between the " marks after ccHash, but don't include the " marks.</li>
+<li>Save that text in a new <b>plain text</b> file, with file name extension .b64 (for example, key.b64)</li>
+<li>Import that file into the preferences through this dialog, using the "Import Existing Key Files" button.</li>
+</ol>
+
<h3>Old instructions: Creating New Keys:</h3>
diff --git a/DeDRM_calibre_plugin/DeDRM_plugin/__init__.py b/DeDRM_calibre_plugin/DeDRM_plugin/__init__.py
index 016c9ef..37c454c 100644
--- a/DeDRM_calibre_plugin/DeDRM_plugin/__init__.py
+++ b/DeDRM_calibre_plugin/DeDRM_plugin/__init__.py
@@ -37,13 +37,15 @@ __docformat__ = 'restructuredtext en'
# 6.0.8 - Fixes a Wine key issue and topaz support
# 6.0.9 - Ported to work with newer versions of Calibre (moved to Qt5). Still supports older Qt4 versions.
# 6.1.0 - Fixed multiple books import problem and PDF import with no key problem
+# 6.2.0 - Support for getting B&N key from nook Study log. Fix for UTF-8 filenames in Adobe ePubs.
+# Fix for not copying needed files. Fix for getting default Adobe key for PDFs
"""
Decrypt DRMed ebooks.
"""
PLUGIN_NAME = u"DeDRM"
-PLUGIN_VERSION_TUPLE = (6, 1, 0)
+PLUGIN_VERSION_TUPLE = (6, 2, 0)
PLUGIN_VERSION = u".".join([unicode(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'
@@ -91,13 +93,8 @@ class DeDRM(FileTypePlugin):
on_import = True
priority = 600
- def load_resources(self, names):
- print u"{0} v{1}: In load_resources".format(PLUGIN_NAME, PLUGIN_VERSION)
- return {}
- def __init__(self, plugin_path):
- print u"{0} v{1}: In __init__".format(PLUGIN_NAME, PLUGIN_VERSION)
- super(DeDRM, self).__init__(plugin_path)
+ def initialize(self):
"""
Dynamic modules can't be imported/loaded from a zipfile.
So this routine will extract the appropriate
@@ -107,15 +104,9 @@ class DeDRM(FileTypePlugin):
so the CDLL stuff will work in the alfcrypto.py script.
The extraction only happens once per version of the plugin
+ Also perform upgrade of preferences once per version
"""
try:
- if iswindows:
- names = [u"alfcrypto.dll",u"alfcrypto64.dll"]
- elif isosx:
- names = [u"libalfcrypto.dylib"]
- else:
- names = [u"libalfcrypto32.so",u"libalfcrypto64.so",u"kindlekey.py",u"adobekey.py",u"subasyncio.py"]
- lib_dict = self.load_resources(names)
self.pluginsdir = os.path.join(config_dir,u"plugins")
if not os.path.exists(self.pluginsdir):
os.mkdir(self.pluginsdir)
@@ -130,28 +121,39 @@ class DeDRM(FileTypePlugin):
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)
- print u"{0} v{1}: verdir {2}".format(PLUGIN_NAME, PLUGIN_VERSION, self.verdir)
if not os.path.exists(self.verdir):
- print u"{0} v{1}: Copying needed libraries from zip".format(PLUGIN_NAME, PLUGIN_VERSION)
- os.mkdir(self.verdir)
+ if iswindows:
+ names = [u"alfcrypto.dll",u"alfcrypto64.dll"]
+ elif isosx:
+ names = [u"libalfcrypto.dylib"]
+ else:
+ names = [u"libalfcrypto32.so",u"libalfcrypto64.so",u"kindlekey.py",u"adobekey.py",u"subasyncio.py"]
+ lib_dict = self.load_resources(names)
+ print u"{0} v{1}: Copying needed library files from plugin's zip".format(PLUGIN_NAME, PLUGIN_VERSION)
+
for entry, data in lib_dict.items():
file_path = os.path.join(self.alfdir, entry)
- os.remove(file_path)
- open(file_path,'wb').write(data)
- except Exception, e:
- traceback.print_exc()
- raise
+ try:
+ os.remove(file_path)
+ except:
+ pass
+ try:
+ open(file_path,'wb').write(data)
+ except:
+ print u"{0} v{1}: Exception when copying needed library files after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
+ traceback.print_exc()
+ pass
+ # convert old preferences, if necessary.
+ from calibre_plugins.dedrm.prefs import convertprefs
+ convertprefs()
- def initialize(self):
- print u"{0} v{1}: In initialize".format(PLUGIN_NAME, PLUGIN_VERSION)
- # convert old preferences, if necessary.
- try:
- from calibre_plugins.dedrm.prefs import convertprefs
- convertprefs()
- except:
+ # mark that this version has been initialized
+ os.mkdir(self.verdir)
+ except Exception, e:
traceback.print_exc()
+ raise
def ePubDecrypt(self,path_to_ebook):
# Create a TemporaryPersistent file to work with.
@@ -186,7 +188,12 @@ class DeDRM(FileTypePlugin):
of = self.temporary_file(u".epub")
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
- result = ignobleepub.decryptBook(userkey, inf.name, of.name)
+ try:
+ result = ignobleepub.decryptBook(userkey, inf.name, of.name)
+ except:
+ print u"{0} v{1}: Exception when trying to decrypt after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
+ traceback.print_exc()
+ result = 1
of.close()
@@ -197,8 +204,69 @@ class DeDRM(FileTypePlugin):
print u"{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)
+ # perhaps we should see if we can get a key from a log file
+ print u"{0} v{1}: Looking for new NOOK Study Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
+
+ # get the default NOOK Study keys
+ defaultkeys = []
+
+ try:
+ if iswindows or isosx:
+ from calibre_plugins.dedrm.ignoblekey import nookkeys
+
+ defaultkeys = nookkeys()
+ else: # linux
+ from wineutils import WineGetKeys
+
+ scriptpath = os.path.join(self.alfdir,u"ignoblekey.py")
+ defaultkeys = WineGetKeys(scriptpath, u".b64",dedrmprefs['adobewineprefix'])
+
+ except:
+ print u"{0} v{1}: Exception when getting default NOOK Study Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
+ traceback.print_exc()
+
+ newkeys = []
+ for keyvalue in defaultkeys:
+ if keyvalue not in dedrmprefs['bandnkeys'].values():
+ newkeys.append(keyvalue)
+
+ if len(newkeys) > 0:
+ try:
+ for i,userkey in enumerate(newkeys):
+ print u"{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
+
+ of = self.temporary_file(u".epub")
+
+ # Give the user key, ebook and TemporaryPersistent file to the decryption function.
+ try:
+ result = ignobleepub.decryptBook(userkey, inf.name, of.name)
+ except:
+ print u"{0} v{1}: Exception when trying to decrypt after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
+ traceback.print_exc()
+ result = 1
+
+ of.close()
+
+ if result == 0:
+ # Decryption was a success
+ # Store the new successful key in the defaults
+ print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
+ try:
+ dedrmprefs.addnamedvaluetoprefs('bandnkeys','default_key',keyvalue)
+ dedrmprefs.writeprefs()
+ print u"{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
+ except:
+ print u"{0} v{1}: Exception saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
+ traceback.print_exc()
+ # 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)
+ except Exception, e:
+ pass
+
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
- raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{2}” after {3:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook),time.time()-self.starttime))
+ raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
# import the Adobe Adept ePub handler
import calibre_plugins.dedrm.ineptepub as ineptepub
@@ -216,6 +284,8 @@ class DeDRM(FileTypePlugin):
try:
result = ineptepub.decryptBook(userkey, inf.name, of.name)
except:
+ print u"{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
+ traceback.print_exc()
result = 1
of.close()
@@ -246,6 +316,7 @@ class DeDRM(FileTypePlugin):
self.default_key = defaultkeys[0]
except:
+ print u"{0} v{1}: Exception when getting default Adobe Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
traceback.print_exc()
self.default_key = u""
@@ -264,6 +335,8 @@ class DeDRM(FileTypePlugin):
try:
result = ineptepub.decryptBook(userkey, inf.name, of.name)
except:
+ print u"{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
+ traceback.print_exc()
result = 1
of.close()
@@ -275,7 +348,9 @@ class DeDRM(FileTypePlugin):
try:
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex'))
dedrmprefs.writeprefs()
+ print u"{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
except:
+ print u"{0} v{1}: Exception when saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
traceback.print_exc()
# Return the modified PersistentTemporary file to calibre.
return of.name
@@ -286,12 +361,12 @@ class DeDRM(FileTypePlugin):
# Something went wrong with decryption.
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
- raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{2}” after {3:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook),time.time()-self.starttime))
+ raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
# Not a Barnes & Noble nor an Adobe Adept
# Import the fixed epub.
print u"{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))
- return inf.name
+ 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))
def PDFDecrypt(self,path_to_ebook):
import calibre_plugins.dedrm.prefs as prefs
@@ -309,6 +384,8 @@ class DeDRM(FileTypePlugin):
try:
result = ineptpdf.decryptBook(userkey, path_to_ebook, of.name)
except:
+ print u"{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
+ traceback.print_exc()
result = 1
of.close()
@@ -318,28 +395,30 @@ class DeDRM(FileTypePlugin):
# Return the modified PersistentTemporary file to calibre.
return of.name
+ print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)
+
# perhaps we need to get a new default ADE key
print u"{0} v{1}: Looking for new default Adobe Digital Editions Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
# get the default Adobe keys
defaultkeys = []
- if iswindows or isosx:
- import calibre_plugins.dedrm.adobekey as adobe
+ try:
+ if iswindows or isosx:
+ from calibre_plugins.dedrm.adobekey import adeptkeys
- try:
- defaultkeys = adobe.adeptkeys()
- except:
- pass
- else:
- # linux
- try:
+ defaultkeys = adeptkeys()
+ else: # linux
from wineutils import WineGetKeys
scriptpath = os.path.join(self.alfdir,u"adobekey.py")
defaultkeys = WineGetKeys(scriptpath, u".der",dedrmprefs['adobewineprefix'])
- except:
- pass
+
+ self.default_key = defaultkeys[0]
+ except:
+ print u"{0} v{1}: Exception when getting default Adobe Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
+ traceback.print_exc()
+ self.default_key = u""
newkeys = []
for keyvalue in defaultkeys:
@@ -354,8 +433,10 @@ class DeDRM(FileTypePlugin):
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
try:
- result = ineptepdf.decryptBook(userkey, inf.name, of.name)
+ result = ineptpdf.decryptBook(userkey, path_to_ebook, of.name)
except:
+ print u"{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
+ traceback.print_exc()
result = 1
of.close()
@@ -367,7 +448,9 @@ class DeDRM(FileTypePlugin):
try:
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex'))
dedrmprefs.writeprefs()
+ print u"{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
except:
+ print u"{0} v{1}: Exception when saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
traceback.print_exc()
# Return the modified PersistentTemporary file to calibre.
return of.name
@@ -378,7 +461,7 @@ class DeDRM(FileTypePlugin):
# Something went wrong with decryption.
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
- raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{2}” after {3:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook),time.time()-self.starttime))
+ raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
def KindleMobiDecrypt(self,path_to_ebook):
@@ -417,6 +500,8 @@ class DeDRM(FileTypePlugin):
scriptpath = os.path.join(self.alfdir,u"kindlekey.py")
defaultkeys = WineGetKeys(scriptpath, u".k4i",dedrmprefs['kindlewineprefix'])
except:
+ print u"{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()
pass
newkeys = {}
@@ -439,8 +524,7 @@ class DeDRM(FileTypePlugin):
if not decoded:
#if you reached here then no luck raise and exception
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
- traceback.print_exc()
- raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{4}” after {3:.1f} seconds with error: {2}\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0],time.time()-self.starttime,os.path.basename(path_to_ebook)))
+ raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
of = self.temporary_file(book.getBookExtension())
book.getFile(of.name)
@@ -474,7 +558,7 @@ class DeDRM(FileTypePlugin):
print u"{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 u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
- raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{2}” after {3:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook),time.time()-self.starttime))
+ raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
def run(self, path_to_ebook):
diff --git a/DeDRM_calibre_plugin/DeDRM_plugin/activitybar.py b/DeDRM_calibre_plugin/DeDRM_plugin/activitybar.py
new file mode 100644
index 0000000..b21c01d
--- /dev/null
+++ b/DeDRM_calibre_plugin/DeDRM_plugin/activitybar.py
@@ -0,0 +1,75 @@
+import sys
+import Tkinter
+import Tkconstants
+
+class ActivityBar(Tkinter.Frame):
+
+ def __init__(self, master, length=300, height=20, barwidth=15, interval=50, bg='white', fillcolor='orchid1',\
+ bd=2, relief=Tkconstants.GROOVE, *args, **kw):
+ Tkinter.Frame.__init__(self, master, bg=bg, width=length, height=height, *args, **kw)
+ self._master = master
+ self._interval = interval
+ self._maximum = length
+ self._startx = 0
+ self._barwidth = barwidth
+ self._bardiv = length / barwidth
+ if self._bardiv < 10:
+ self._bardiv = 10
+ stopx = self._startx + self._barwidth
+ if stopx > self._maximum:
+ stopx = self._maximum
+ # self._canv = Tkinter.Canvas(self, bg=self['bg'], width=self['width'], height=self['height'],\
+ # highlightthickness=0, relief='flat', bd=0)
+ self._canv = Tkinter.Canvas(self, bg=self['bg'], width=self['width'], height=self['height'],\
+ highlightthickness=0, relief=relief, bd=bd)
+ self._canv.pack(fill='both', expand=1)
+ self._rect = self._canv.create_rectangle(0, 0, self._canv.winfo_reqwidth(), self._canv.winfo_reqheight(), fill=fillcolor, width=0)
+
+ self._set()
+ self.bind('<Configure>', self._update_coords)
+ self._running = False
+
+ def _update_coords(self, event):
+ '''Updates the position of the rectangle inside the canvas when the size of
+ the widget gets changed.'''
+ # looks like we have to call update_idletasks() twice to make sure
+ # to get the results we expect
+ self._canv.update_idletasks()
+ self._maximum = self._canv.winfo_width()
+ self._startx = 0
+ self._barwidth = self._maximum / self._bardiv
+ if self._barwidth < 2:
+ self._barwidth = 2
+ stopx = self._startx + self._barwidth
+ if stopx > self._maximum:
+ stopx = self._maximum
+ self._canv.coords(self._rect, 0, 0, stopx, self._canv.winfo_height())
+ self._canv.update_idletasks()
+
+ def _set(self):
+ if self._startx < 0:
+ self._startx = 0
+ if self._startx > self._maximum:
+ self._startx = self._startx % self._maximum
+ stopx = self._startx + self._barwidth
+ if stopx > self._maximum:
+ stopx = self._maximum
+ self._canv.coords(self._rect, self._startx, 0, stopx, self._canv.winfo_height())
+ self._canv.update_idletasks()
+
+ def start(self):
+ self._running = True
+ self.after(self._interval, self._step)
+
+ def stop(self):
+ self._running = False
+ self._set()
+
+ def _step(self):
+ if self._running:
+ stepsize = self._barwidth / 4
+ if stepsize < 2:
+ stepsize = 2
+ self._startx += stepsize
+ self._set()
+ self.after(self._interval, self._step)
diff --git a/DeDRM_calibre_plugin/DeDRM_plugin/android.py b/DeDRM_calibre_plugin/DeDRM_plugin/android.py
index ddb94f5..d8be194 100644
--- a/DeDRM_calibre_plugin/DeDRM_plugin/android.py
+++ b/DeDRM_calibre_plugin/DeDRM_plugin/android.py
@@ -10,6 +10,7 @@ from cStringIO import StringIO
from binascii import a2b_hex, b2a_hex
STORAGE = 'AmazonSecureStorage.xml'
+STORAGE2 = 'map_data_storage.db'
class AndroidObfuscation(object):
'''AndroidObfuscation
@@ -76,13 +77,8 @@ def parse_preference(path):
def get_serials(path=None):
''' get serials from android's shared preference xml '''
- if path is None:
- if not os.path.isfile(STORAGE):
- if os.path.isfile("backup.ab"):
- get_storage()
- else:
- return []
- path = STORAGE
+ if path is None and os.path.isfile("backup.ab"):
+ return get_storage()
if not os.path.isfile(path):
return []
@@ -113,16 +109,27 @@ def get_serials(path=None):
try:
tokens = set(get_value('kindle.account.tokens').split(','))
except:
- return [dsnid]
+ return []
serials = []
for token in tokens:
if token:
serials.append('%s%s' % (dsnid, token))
- serials.append(dsnid)
- for token in tokens:
- if token:
- serials.append(token)
+ return serials
+
+def get_serials2(path=STORAGE2):
+ import sqlite3
+ connection = sqlite3.connect(path)
+ cursor = connection.cursor()
+ cursor.execute('''select userdata_value from userdata where userdata_key like '%/%token.device.deviceserialname%' ''')
+ dsns = [x[0].encode('utf8') for x in cursor.fetchall()]
+
+ cursor.execute('''select userdata_value from userdata where userdata_key like '%/%kindle.account.tokens%' ''')
+ tokens = [x[0].encode('utf8') for x in cursor.fetchall()]
+ serials = []
+ for x in dsns:
+ for y in tokens:
+ serials.append('%s%s' % (x, y))
return serials
def get_storage(path='backup.ab'):
@@ -130,25 +137,38 @@ def get_storage(path='backup.ab'):
backup.ab can be get using adb command:
shell> adb backup com.amazon.kindle
'''
+ if not os.path.isfile(path):
+ serials = []
+ if os.path.isfile(STORAGE2):
+ serials.extend(get_serials2(STORAGE2))
+ if os.path.isfile(STORAGE):
+ serials.extend(get_serials(STORAGE))
+ return serials
output = None
read = open(path, 'rb')
head = read.read(24)
- if head == 'ANDROID BACKUP\n1\n1\nnone\n':
+ if head[:14] == 'ANDROID BACKUP':
output = StringIO(zlib.decompress(read.read()))
read.close()
if not output:
- return False
+ return []
+ serials = []
tar = tarfile.open(fileobj=output)
for member in tar.getmembers():
- if member.name.strip().endswith(STORAGE):
+ if member.name.strip().endswith(STORAGE2):
+ write = open(STORAGE2, 'w')
+ write.write(tar.extractfile(member).read())
+ write.close()
+ serials.extend(get_serials2(STORAGE2))
+ elif member.name.strip().endswith(STORAGE):
write = open(STORAGE, 'w')
write.write(tar.extractfile(member).read())
write.close()
- break
+ serials.extend(get_serials(STORAGE))
- return True
+ return serials
__all__ = [ 'get_storage', 'get_serials', 'parse_preference',
'AndroidObfuscation', 'AndroidObfuscationV2', 'STORAGE']
diff --git a/DeDRM_calibre_plugin/DeDRM_plugin/android_readme.txt b/DeDRM_calibre_plugin/DeDRM_plugin/android_readme.txt
index 9e7d035..cb29df3 100644
--- a/DeDRM_calibre_plugin/DeDRM_plugin/android_readme.txt
+++ b/DeDRM_calibre_plugin/DeDRM_plugin/android_readme.txt
@@ -1,6 +1,7 @@
1.1 get AmazonSecureStorage.xml from /data/data/com.amazon.kindle/shared_prefs/AmazonSecureStorage.xml
+ or map_data_storage.db from /data/data/com.amazon.kindle/databases/map_data_storage.db
1.2 on android 4.0+, run `adb backup com.amazon.kindle` from PC will get backup.ab
- now android.py can convert backup.ab to AmazonSecureStorage.xml
+ now android.py can convert backup.ab to AmazonSecureStorage.xml and map_data_storage.db
-2. run `k4mobidedrm.py -a AmazonSecureStorage.xml <infile> <outdir>'
+2. run `k4mobidedrm.py <infile> <outdir>'
diff --git a/DeDRM_calibre_plugin/DeDRM_plugin/argv_utils.py b/DeDRM_calibre_plugin/DeDRM_plugin/argv_utils.py
new file mode 100644
index 0000000..85ffaa4
--- /dev/null
+++ b/DeDRM_calibre_plugin/DeDRM_plugin/argv_utils.py
@@ -0,0 +1,88 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import sys, os
+import locale
+import codecs
+
+# get sys.argv arguments and encode them into utf-8
+def unicode_argv():
+ if sys.platform.startswith('win'):
+ # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
+ # strings.
+
+ # Versions 2.x of Python don't support Unicode in sys.argv on
+ # Windows, with the underlying Windows API instead replacing multi-byte
+ # characters with '?'.
+
+
+ from ctypes import POINTER, byref, cdll, c_int, windll
+ from ctypes.wintypes import LPCWSTR, LPWSTR
+
+ GetCommandLineW = cdll.kernel32.GetCommandLineW
+ GetCommandLineW.argtypes = []
+ GetCommandLineW.restype = LPCWSTR
+
+ CommandLineToArgvW = windll.shell32.CommandLineToArgvW
+ CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
+ CommandLineToArgvW.restype = POINTER(LPWSTR)
+
+ cmd = GetCommandLineW()
+ argc = c_int(0)
+ argv = CommandLineToArgvW(cmd, byref(argc))
+ if argc.value > 0:
+ # Remove Python executable and commands if present
+ start = argc.value - len(sys.argv)
+ return [argv[i] for i in
+ 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"]
+ else:
+ argvencoding = sys.stdin.encoding
+ if argvencoding == None:
+ argvencoding = "utf-8"
+ return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
+
+
+def add_cp65001_codec():
+ try:
+ codecs.lookup('cp65001')
+ except LookupError:
+ codecs.register(
+ lambda name: name == 'cp65001' and codecs.lookup('utf-8') or None)
+ return
+
+
+def set_utf8_default_encoding():
+ if sys.getdefaultencoding() == 'utf-8':
+ return
+
+ # Regenerate setdefaultencoding.
+ reload(sys)
+ sys.setdefaultencoding('utf-8')
+
+ for attr in dir(locale):
+ if attr[0:3] != 'LC_':
+ continue
+ aref = getattr(locale, attr)
+ try:
+ locale.setlocale(aref, '')
+ except locale.Error:
+ continue
+ try:
+ lang = locale.getlocale(aref)[0]
+ except (TypeError, ValueError):
+ continue
+ if lang:
+ try:
+ locale.setlocale(aref, (lang, 'UTF-8'))
+ except locale.Error:
+ os.environ[attr] = lang + '.UTF-8'
+ try:
+ locale.setlocale(locale.LC_ALL, '')
+ except locale.Error:
+ pass
+ return
+
+
diff --git a/DeDRM_calibre_plugin/DeDRM_plugin/askfolder_ed.py b/DeDRM_calibre_plugin/DeDRM_plugin/askfolder_ed.py
new file mode 100644
index 0000000..a4a2ae0
--- /dev/null
+++ b/DeDRM_calibre_plugin/DeDRM_plugin/askfolder_ed.py
@@ -0,0 +1,211 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
+
+# to work around tk_chooseDirectory not properly returning unicode paths on Windows
+# need to use a dialog that can be hacked up to actually return full unicode paths
+# originally based on AskFolder from EasyDialogs for Windows but modified to fix it
+# to actually use unicode for path
+
+# The original license for EasyDialogs is as follows
+#
+# Copyright (c) 2003-2005 Jimmy Retzlaff
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+"""
+AskFolder(...) -- Ask the user to select a folder Windows specific
+"""
+
+import os
+
+import ctypes
+from ctypes import POINTER, byref, cdll, c_int, windll
+from ctypes.wintypes import LPCWSTR, LPWSTR
+import ctypes.wintypes as wintypes
+
+
+__all__ = ['AskFolder']
+
+# Load required Windows DLLs
+ole32 = ctypes.windll.ole32
+shell32 = ctypes.windll.shell32
+user32 = ctypes.windll.user32
+
+
+# Windows Constants
+BFFM_INITIALIZED = 1
+BFFM_SETOKTEXT = 1129
+BFFM_SETSELECTIONA = 1126
+BFFM_SETSELECTIONW = 1127
+BIF_EDITBOX = 16
+BS_DEFPUSHBUTTON = 1
+CB_ADDSTRING = 323
+CB_GETCURSEL = 327
+CB_SETCURSEL = 334
+CDM_SETCONTROLTEXT = 1128
+EM_GETLINECOUNT = 186
+EM_GETMARGINS = 212
+EM_POSFROMCHAR = 214
+EM_SETSEL = 177
+GWL_STYLE = -16
+IDC_STATIC = -1
+IDCANCEL = 2
+IDNO = 7
+IDOK = 1
+IDYES = 6
+MAX_PATH = 260
+OFN_ALLOWMULTISELECT = 512
+OFN_ENABLEHOOK = 32
+OFN_ENABLESIZING = 8388608
+OFN_ENABLETEMPLATEHANDLE = 128
+OFN_EXPLORER = 524288
+OFN_OVERWRITEPROMPT = 2
+OPENFILENAME_SIZE_VERSION_400 = 76
+PBM_GETPOS = 1032
+PBM_SETMARQUEE = 1034
+PBM_SETPOS = 1026
+PBM_SETRANGE = 1025
+PBM_SETRANGE32 = 1030
+PBS_MARQUEE = 8
+PM_REMOVE = 1
+SW_HIDE = 0
+SW_SHOW = 5
+SW_SHOWNORMAL = 1
+SWP_NOACTIVATE = 16
+SWP_NOMOVE = 2
+SWP_NOSIZE = 1
+SWP_NOZORDER = 4
+VER_PLATFORM_WIN32_NT = 2
+WM_COMMAND = 273
+WM_GETTEXT = 13
+WM_GETTEXTLENGTH = 14
+WM_INITDIALOG = 272
+WM_NOTIFY = 78
+
+# Windows function prototypes
+BrowseCallbackProc = ctypes.WINFUNCTYPE(ctypes.c_int, wintypes.HWND, ctypes.c_uint, wintypes.LPARAM, wintypes.LPARAM)
+
+# Windows types
+LPCTSTR = ctypes.c_char_p
+LPTSTR = ctypes.c_char_p
+LPVOID = ctypes.c_voidp
+TCHAR = ctypes.c_char
+
+class BROWSEINFO(ctypes.Structure):
+ _fields_ = [
+ ("hwndOwner", wintypes.HWND),
+ ("pidlRoot", LPVOID),
+ ("pszDisplayName", LPTSTR),
+ ("lpszTitle", LPCTSTR),
+ ("ulFlags", ctypes.c_uint),
+ ("lpfn", BrowseCallbackProc),
+ ("lParam", wintypes.LPARAM),
+ ("iImage", ctypes.c_int)
+ ]
+
+
+# Utilities
+def CenterWindow(hwnd):
+ desktopRect = GetWindowRect(user32.GetDesktopWindow())
+ myRect = GetWindowRect(hwnd)
+ x = width(desktopRect) // 2 - width(myRect) // 2
+ y = height(desktopRect) // 2 - height(myRect) // 2
+ user32.SetWindowPos(hwnd, 0,
+ desktopRect.left + x,
+ desktopRect.top + y,
+ 0, 0,
+ SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER
+ )
+
+
+def GetWindowRect(hwnd):
+ rect = wintypes.RECT()
+ user32.GetWindowRect(hwnd, ctypes.byref(rect))
+ return rect
+
+def width(rect):
+ return rect.right-rect.left
+
+def height(rect):
+ return rect.bottom-rect.top
+
+
+def AskFolder(
+ message=None,
+ version=None,
+ defaultLocation=None,
+ location=None,
+ windowTitle=None,
+ actionButtonLabel=None,
+ cancelButtonLabel=None,
+ multiple=None):
+ """Display a dialog asking the user for select a folder.
+ modified to use unicode strings as much as possible
+ returns unicode path
+ """
+
+ def BrowseCallback(hwnd, uMsg, lParam, lpData):
+ if uMsg == BFFM_INITIALIZED:
+ if actionButtonLabel:
+ label = unicode(actionButtonLabel, errors='replace')
+ user32.SendMessageW(hwnd, BFFM_SETOKTEXT, 0, label)
+ if cancelButtonLabel:
+ label = unicode(cancelButtonLabel, errors='replace')
+ cancelButton = user32.GetDlgItem(hwnd, IDCANCEL)
+ if cancelButton:
+ user32.SetWindowTextW(cancelButton, label)
+ if windowTitle:
+ title = unicode(windowTitle, erros='replace')
+ user32.SetWindowTextW(hwnd, title)
+ if defaultLocation:
+ user32.SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, defaultLocation.replace('/', '\\'))
+ if location:
+ x, y = location
+ desktopRect = wintypes.RECT()
+ user32.GetWindowRect(0, ctypes.byref(desktopRect))
+ user32.SetWindowPos(hwnd, 0,
+ desktopRect.left + x,
+ desktopRect.top + y, 0, 0,
+ SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER)
+ else:
+ CenterWindow(hwnd)
+ return 0
+
+ # This next line is needed to prevent gc of the callback
+ callback = BrowseCallbackProc(BrowseCallback)
+
+ browseInfo = BROWSEINFO()
+ browseInfo.pszDisplayName = ctypes.c_char_p('\0' * (MAX_PATH+1))
+ browseInfo.lpszTitle = message
+ browseInfo.lpfn = callback
+
+ pidl = shell32.SHBrowseForFolder(ctypes.byref(browseInfo))
+ if not pidl:
+ result = None
+ else:
+ path = LPCWSTR(u" " * (MAX_PATH+1))
+ shell32.SHGetPathFromIDListW(pidl, path)
+ ole32.CoTaskMemFree(pidl)
+ result = path.value
+ return result
+
+
+
+
diff --git a/DeDRM_calibre_plugin/DeDRM_plugin/dialogs.py b/DeDRM_calibre_plugin/DeDRM_plugin/dialogs.py
new file mode 100644
index 0000000..21c1dad
--- /dev/null
+++ b/DeDRM_calibre_plugin/DeDRM_plugin/dialogs.py
@@ -0,0 +1,719 @@
+#!/usr/bin/env python
+# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+
+from __future__ import with_statement
+__license__ = 'GPL v3'
+
+# Standard Python modules.
+import os, sys, re, hashlib
+import json
+
+from PyQt4.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QListWidget, QListWidgetItem, QAbstractItemView, QLineEdit, QPushButton, QIcon, QGroupBox, QDialog, QDialogButtonBox, QUrl, QString)
+from PyQt4 import QtGui
+
+# calibre modules and constants.
+from calibre.gui2 import (error_dialog, question_dialog, info_dialog, open_url,
+ choose_dir, choose_files)
+from calibre.utils.config import dynamic, config_dir, JSONConfig
+
+from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
+from calibre_plugins.dedrm.utilities import (uStrCmp, DETAILED_MESSAGE, parseCustString)
+from calibre_plugins.dedrm.ignoblekeygen import generate_key as generate_bandn_key
+from calibre_plugins.dedrm.erdr2pml import getuser_key as generate_ereader_key
+from calibre_plugins.dedrm.adobekey import adeptkeys as retrieve_adept_keys
+from calibre_plugins.dedrm.kindlekey import kindlekeys as retrieve_kindle_keys
+
+class ManageKeysDialog(QDialog):
+ def __init__(self, parent, key_type_name, plugin_keys, create_key, keyfile_ext = u""):
+ QDialog.__init__(self,parent)
+ self.parent = parent
+ self.key_type_name = key_type_name
+ self.plugin_keys = plugin_keys
+ self.create_key = create_key
+ self.keyfile_ext = keyfile_ext
+ self.import_key = (keyfile_ext != u"")
+ self.binary_file = (key_type_name == u"Adobe Digital Editions Key")
+ self.json_file = (key_type_name == u"Kindle for Mac and PC Key")
+
+ self.setWindowTitle("{0} {1}: Manage {2}s".format(PLUGIN_NAME, PLUGIN_VERSION, self.key_type_name))
+
+ # Start Qt Gui dialog layout
+ layout = QVBoxLayout(self)
+ self.setLayout(layout)
+
+ help_layout = QHBoxLayout()
+ layout.addLayout(help_layout)
+ # Add hyperlink to a help file at the right. We will replace the correct name when it is clicked.
+ help_label = QLabel('<a href="http://www.foo.com/">Help</a>', self)
+ help_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard)
+ help_label.setAlignment(Qt.AlignRight)
+ 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)
+ 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.setSelectionMode(QAbstractItemView.SingleSelection)
+ self.populate_list()
+ keys_group_box_layout.addWidget(self.listy)
+
+ button_layout = QVBoxLayout()
+ keys_group_box_layout.addLayout(button_layout)
+ self._add_key_button = QtGui.QToolButton(self)
+ self._add_key_button.setToolTip(u"Create new {0}".format(self.key_type_name))
+ self._add_key_button.setIcon(QIcon(I('plus.png')))
+ 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.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:
+ self._rename_key_button = QtGui.QToolButton(self)
+ self._rename_key_button.setToolTip(_(u"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.setIcon(QIcon(I('save.png')))
+ self.export_key_button.clicked.connect(self.export_key)
+ button_layout.addWidget(self.export_key_button)
+ spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
+ button_layout.addItem(spacerItem)
+
+ layout.addSpacing(5)
+ migrate_layout = QHBoxLayout()
+ 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.clicked.connect(self.migrate_wrapper)
+ migrate_layout.addWidget(self.migrate_btn)
+ migrate_layout.addStretch()
+ self.button_box = QDialogButtonBox(QDialogButtonBox.Close)
+ self.button_box.rejected.connect(self.close)
+ migrate_layout.addWidget(self.button_box)
+
+ self.resize(self.sizeHint())
+
+ def populate_list(self):
+ if type(self.plugin_keys) == dict:
+ for key in self.plugin_keys.keys():
+ self.listy.addItem(QListWidgetItem(key))
+ else:
+ for key in self.plugin_keys:
+ self.listy.addItem(QListWidgetItem(key))
+
+ def add_key(self):
+ d = self.create_key(self)
+ d.exec_()
+
+ if d.result() != d.Accepted:
+ # New key generation cancelled.
+ return
+ new_key_value = d.key_value
+ if type(self.plugin_keys) == dict:
+ 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)
+ 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)
+ return
+
+ self.plugin_keys.append(d.key_value)
+ self.listy.clear()
+ self.populate_list()
+
+ 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)
+ r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
+ _(errmsg), show=True, show_copy_button=False)
+ return
+
+ d = RenameKeyDialog(self)
+ d.exec_()
+
+ if d.result() != d.Accepted:
+ # rename cancelled or moot.
+ return
+ keyname = unicode(self.listy.currentItem().text().toUtf8(),'utf8')
+ 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):
+ return
+ self.plugin_keys[d.key_name] = self.plugin_keys[keyname]
+ del self.plugin_keys[keyname]
+
+ self.listy.clear()
+ self.populate_list()
+
+ def delete_key(self):
+ if not self.listy.currentItem():
+ return
+ keyname = unicode(self.listy.currentItem().text().toUtf8(), 'utf8')
+ 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):
+ return
+ if type(self.plugin_keys) == dict:
+ del self.plugin_keys[keyname]
+ else:
+ self.plugin_keys.remove(keyname)
+
+ self.listy.clear()
+ self.populate_list()
+
+ 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.
+ 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)
+ with open(file_path,'w') as f:
+ f.write(self.parent.load_resource(help_file_name))
+ return file_path
+ url = 'file:///' + get_help_file_resource()
+ open_url(QUrl(url))
+
+ def migrate_files(self):
+ dynamic[PLUGIN_NAME + u"config_dir"] = config_dir
+ files = choose_files(self, PLUGIN_NAME + u"config_dir",
+ u"Select {0} files to import".format(self.key_type_name), [(u"{0} files".format(self.key_type_name), [self.keyfile_ext])], False)
+ counter = 0
+ skipped = 0
+ if files:
+ for filename in files:
+ fpath = os.path.join(config_dir, filename)
+ filename = os.path.basename(filename)
+ new_key_name = os.path.splitext(os.path.basename(filename))[0]
+ with open(fpath,'rb') as keyfile:
+ new_key_value = keyfile.read()
+ if self.binary_file:
+ new_key_value = new_key_value.encode('hex')
+ elif self.json_file:
+ new_key_value = json.loads(new_key_value)
+ match = False
+ 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)
+ inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
+ _(msg), show_copy_button=False, show=True)
+ match = True
+ break
+ if not match:
+ 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]
+ 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)
+ else:
+ counter += 1
+ self.plugin_keys[new_key_name] = new_key_value
+
+ 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")
+ if skipped > 0:
+ msg += u"Skipped <strong>{0:d}</strong> key {1}.".format(skipped, u"file" if counter == 1 else u"files")
+ inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
+ _(msg), show_copy_button=False, show=True)
+ return counter > 0
+
+ def migrate_wrapper(self):
+ if self.migrate_files():
+ self.listy.clear()
+ self.populate_list()
+
+ def export_key(self):
+ if not self.listy.currentItem():
+ errmsg = u"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
+ filter = QString(u"{0} Files (*.{1})".format(self.key_type_name, self.keyfile_ext))
+ keyname = unicode(self.listy.currentItem().text().toUtf8(), 'utf8')
+ if dynamic.get(PLUGIN_NAME + 'save_dir'):
+ defaultname = os.path.join(dynamic.get(PLUGIN_NAME + 'save_dir'), u"{0}.{1}".format(keyname , self.keyfile_ext))
+ else:
+ defaultname = os.path.join(os.path.expanduser('~'), u"{0}.{1}".format(keyname , self.keyfile_ext))
+ filename = unicode(QtGui.QFileDialog.getSaveFileName(self, u"Save {0} File as...".format(self.key_type_name), defaultname,
+ u"{0} Files (*.{1})".format(self.key_type_name,self.keyfile_ext), filter))
+ if filename:
+ dynamic[PLUGIN_NAME + 'save_dir'] = os.path.split(filename)[0]
+ with file(filename, 'w') as fname:
+ if self.binary_file:
+ fname.write(self.plugin_keys[keyname].decode('hex'))
+ elif self.json_file:
+ fname.write(json.dumps(self.plugin_keys[keyname]))
+ else:
+ fname.write(self.plugin_keys[keyname])
+
+
+
+
+class RenameKeyDialog(QDialog):
+ def __init__(self, parent=None,):
+ print repr(self), repr(parent)
+ QDialog.__init__(self, parent)
+ self.parent = parent
+ self.setWindowTitle("{0} {1}: Rename {0}".format(PLUGIN_NAME, PLUGIN_VERSION, parent.key_type_name))
+ layout = QVBoxLayout(self)
+ self.setLayout(layout)
+
+ data_group_box = QGroupBox('', self)
+ layout.addWidget(data_group_box)
+ data_group_box_layout = QVBoxLayout()
+ data_group_box.setLayout(data_group_box_layout)
+
+ 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))
+ data_group_box_layout.addWidget(self.key_ledit)
+
+ layout.addSpacing(20)
+
+ self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+ self.button_box.accepted.connect(self.accept)
+ self.button_box.rejected.connect(self.reject)
+ layout.addWidget(self.button_box)
+
+ self.resize(self.sizeHint())
+
+ def accept(self):
+ if self.key_ledit.text().isEmpty() or unicode(self.key_ledit.text()).isspace():
+ errmsg = u"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!"
+ 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()):
+ # Same exact name ... do nothing.
+ return QDialog.reject(self)
+ 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())
+ return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
+ _(errmsg), show=True, show_copy_button=False)
+ QDialog.accept(self)
+
+ @property
+ def key_name(self):
+ return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+
+
+
+
+
+
+
+
+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))
+ layout = QVBoxLayout(self)
+ self.setLayout(layout)
+
+ data_group_box = QGroupBox(u"", self)
+ layout.addWidget(data_group_box)
+ data_group_box_layout = QVBoxLayout()
+ data_group_box.setLayout(data_group_box_layout)
+
+ key_group = QHBoxLayout()
+ data_group_box_layout.addLayout(key_group)
+ key_group.addWidget(QLabel(u"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."))
+ key_group.addWidget(self.key_ledit)
+ key_label = QLabel(_(''), self)
+ key_label.setAlignment(Qt.AlignHCenter)
+ data_group_box_layout.addWidget(key_label)
+
+ name_group = QHBoxLayout()
+ data_group_box_layout.addLayout(name_group)
+ name_group.addWidget(QLabel(u"Your Name:", self))
+ self.name_ledit = QLineEdit(u"", self)
+ self.name_ledit.setToolTip(_(u"<p>Enter your name as it appears in your B&N " +
+ u"account or on your credit card.</p>" +
+ u"<p>It will only be used to generate this " +
+ u"one-time key and won\'t be stored anywhere " +
+ u"in calibre or on your computer.</p>" +
+ u"<p>(ex: Jonathan Smith)"))
+ name_group.addWidget(self.name_ledit)
+ name_disclaimer_label = QLabel(_(u"(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))
+ self.cc_ledit = QLineEdit(u"", self)
+ self.cc_ledit.setToolTip(_(u"<p>Enter the full credit card number on record " +
+ u"in your B&N account.</p>" +
+ u"<p>No spaces or dashes... just the numbers. " +
+ u"This number will only be used to generate this " +
+ u"one-time key and won\'t be stored anywhere in " +
+ u"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)
+ data_group_box_layout.addWidget(ccn_disclaimer_label)
+ layout.addSpacing(10)
+
+ self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+ self.button_box.accepted.connect(self.accept)
+ self.button_box.rejected.connect(self.reject)
+ layout.addWidget(self.button_box)
+
+ self.resize(self.sizeHint())
+
+ @property
+ def key_name(self):
+ return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+
+ @property
+ def key_value(self):
+ return generate_bandn_key(self.user_name,self.cc_number)
+
+ @property
+ def user_name(self):
+ return unicode(self.name_ledit.text().toUtf8(), 'utf8').strip().lower().replace(' ','')
+
+ @property
+ def cc_number(self):
+ return unicode(self.cc_ledit.text().toUtf8(), 'utf8').strip().replace(' ', '').replace('-','')
+
+
+ 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!"
+ 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!"
+ 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!"
+ return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
+ QDialog.accept(self)
+
+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))
+ layout = QVBoxLayout(self)
+ self.setLayout(layout)
+
+ data_group_box = QGroupBox(u"", self)
+ layout.addWidget(data_group_box)
+ data_group_box_layout = QVBoxLayout()
+ data_group_box.setLayout(data_group_box_layout)
+
+ key_group = QHBoxLayout()
+ data_group_box_layout.addLayout(key_group)
+ key_group.addWidget(QLabel(u"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.")
+ key_group.addWidget(self.key_ledit)
+ key_label = QLabel(_(''), self)
+ key_label.setAlignment(Qt.AlignHCenter)
+ data_group_box_layout.addWidget(key_label)
+
+ name_group = QHBoxLayout()
+ data_group_box_layout.addLayout(name_group)
+ name_group.addWidget(QLabel(u"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)")
+ name_group.addWidget(self.name_ledit)
+ name_disclaimer_label = QLabel(_(u"(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))
+ 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.")
+ ccn_group.addWidget(self.cc_ledit)
+ ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self)
+ ccn_disclaimer_label.setAlignment(Qt.AlignHCenter)
+ data_group_box_layout.addWidget(ccn_disclaimer_label)
+ layout.addSpacing(10)
+
+ self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+ self.button_box.accepted.connect(self.accept)
+ self.button_box.rejected.connect(self.reject)
+ layout.addWidget(self.button_box)
+
+ self.resize(self.sizeHint())
+
+ @property
+ def key_name(self):
+ return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+
+ @property
+ def key_value(self):
+ return generate_ereader_key(self.user_name,self.cc_number).encode('hex')
+
+ @property
+ def user_name(self):
+ return unicode(self.name_ledit.text().toUtf8(), 'utf8').strip().lower().replace(' ','')
+
+ @property
+ def cc_number(self):
+ return unicode(self.cc_ledit.text().toUtf8(), 'utf8').strip().replace(' ', '').replace('-','')
+
+
+ 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!"
+ 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!"
+ 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!"
+ return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
+ QDialog.accept(self)
+
+
+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))
+ layout = QVBoxLayout(self)
+ self.setLayout(layout)
+
+ try:
+ self.default_key = retrieve_adept_keys()[0]
+ except:
+ self.default_key = u""
+
+ self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+
+ if len(self.default_key)>0:
+ data_group_box = QGroupBox(u"", self)
+ layout.addWidget(data_group_box)
+ data_group_box_layout = QVBoxLayout()
+ data_group_box.setLayout(data_group_box_layout)
+
+ key_group = QHBoxLayout()
+ data_group_box_layout.addLayout(key_group)
+ key_group.addWidget(QLabel(u"Unique Key Name:", self))
+ self.key_ledit = QLineEdit("", self)
+ self.key_ledit.setToolTip(u"<p>Enter an identifying name for the current default Adobe Digital Editions key.")
+ key_group.addWidget(self.key_ledit)
+ key_label = QLabel(_(''), self)
+ key_label.setAlignment(Qt.AlignHCenter)
+ data_group_box_layout.addWidget(key_label)
+ 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.setAlignment(Qt.AlignHCenter)
+ layout.addWidget(default_key_error)
+ # if no default, bot buttons do the same
+ self.button_box.accepted.connect(self.reject)
+
+ self.button_box.rejected.connect(self.reject)
+ layout.addWidget(self.button_box)
+
+ self.resize(self.sizeHint())
+
+ @property
+ def key_name(self):
+ return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+
+ @property
+ def key_value(self):
+ return self.default_key.encode('hex')
+
+
+ def accept(self):
+ if len(self.key_name) == 0 or self.key_name.isspace():
+ errmsg = u"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!"
+ return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
+ QDialog.accept(self)
+
+
+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))
+ layout = QVBoxLayout(self)
+ self.setLayout(layout)
+
+ try:
+ self.default_key = retrieve_kindle_keys()[0]
+ except:
+ self.default_key = u""
+
+ self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+
+ if len(self.default_key)>0:
+ data_group_box = QGroupBox(u"", self)
+ layout.addWidget(data_group_box)
+ data_group_box_layout = QVBoxLayout()
+ data_group_box.setLayout(data_group_box_layout)
+
+ key_group = QHBoxLayout()
+ data_group_box_layout.addLayout(key_group)
+ key_group.addWidget(QLabel(u"Unique Key Name:", self))
+ self.key_ledit = QLineEdit("", self)
+ self.key_ledit.setToolTip(u"<p>Enter an identifying name for the current default Kindle for Mac/PC key.")
+ key_group.addWidget(self.key_ledit)
+ key_label = QLabel(_(''), self)
+ key_label.setAlignment(Qt.AlignHCenter)
+ data_group_box_layout.addWidget(key_label)
+ 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.setAlignment(Qt.AlignHCenter)
+ layout.addWidget(default_key_error)
+ # if no default, bot buttons do the same
+ self.button_box.accepted.connect(self.reject)
+
+ self.button_box.rejected.connect(self.reject)
+ layout.addWidget(self.button_box)
+
+ self.resize(self.sizeHint())
+
+ @property
+ def key_name(self):
+ return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+
+ @property
+ def key_value(self):
+ return self.default_key
+
+
+ def accept(self):
+ if len(self.key_name) == 0 or self.key_name.isspace():
+ errmsg = u"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!"
+ return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
+ QDialog.accept(self)
+
+
+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))
+ layout = QVBoxLayout(self)
+ self.setLayout(layout)
+
+ data_group_box = QGroupBox(u"", self)
+ layout.addWidget(data_group_box)
+ data_group_box_layout = QVBoxLayout()
+ data_group_box.setLayout(data_group_box_layout)
+
+ key_group = QHBoxLayout()
+ data_group_box_layout.addLayout(key_group)
+ key_group.addWidget(QLabel(u"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.")
+ key_group.addWidget(self.key_ledit)
+ key_label = QLabel(_(''), self)
+ key_label.setAlignment(Qt.AlignHCenter)
+ data_group_box_layout.addWidget(key_label)
+
+ self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+ self.button_box.accepted.connect(self.accept)
+ self.button_box.rejected.connect(self.reject)
+ layout.addWidget(self.button_box)
+
+ self.resize(self.sizeHint())
+
+ @property
+ def key_name(self):
+ return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+
+ @property
+ def key_value(self):
+ return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+
+ 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."
+ 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))
+ return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
+ QDialog.accept(self)
+
+
+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))
+ layout = QVBoxLayout(self)
+ self.setLayout(layout)
+
+ data_group_box = QGroupBox(u"", self)
+ layout.addWidget(data_group_box)
+ data_group_box_layout = QVBoxLayout()
+ data_group_box.setLayout(data_group_box_layout)
+
+ key_group = QHBoxLayout()
+ data_group_box_layout.addLayout(key_group)
+ key_group.addWidget(QLabel(u"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.")
+ key_group.addWidget(self.key_ledit)
+ key_label = QLabel(_(''), self)
+ key_label.setAlignment(Qt.AlignHCenter)
+ data_group_box_layout.addWidget(key_label)
+
+ self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+ self.button_box.accepted.connect(self.accept)
+ self.button_box.rejected.connect(self.reject)
+ layout.addWidget(self.button_box)
+
+ self.resize(self.sizeHint())
+
+ @property
+ def key_name(self):
+ return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+
+ @property
+ def key_value(self):
+ return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+
+ 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."
+ 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))
+ return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
+ QDialog.accept(self)
+
+
diff --git a/DeDRM_calibre_plugin/DeDRM_plugin/ignoblekey.py b/DeDRM_calibre_plugin/DeDRM_plugin/ignoblekey.py
new file mode 100644
index 0000000..4e9eead
--- /dev/null
+++ b/DeDRM_calibre_plugin/DeDRM_plugin/ignoblekey.py
@@ -0,0 +1,335 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+# ignoblekey.py
+# Copyright © 2015 Apprentice Alf
+
+# Based on kindlekey.py, Copyright © 2010-2013 by some_updates and Apprentice Alf
+
+# Released under the terms of the GNU General Public Licence, version 3
+# <http://www.gnu.org/licenses/>
+
+# Revision history:
+# 1.0 - Initial release
+
+"""
+Get Barnes & Noble EPUB user key from nook Studio log file
+"""
+
+__license__ = 'GPL v3'
+__version__ = "1.0"
+
+import sys
+import os
+import hashlib
+import getopt
+import re
+
+# Wrap a stream so that output gets flushed immediately
+# and also make sure that any unicode strings get
+# encoded using "replace" before writing them.
+class SafeUnbuffered:
+ def __init__(self, stream):
+ self.stream = stream
+ self.encoding = stream.encoding
+ if self.encoding == None:
+ self.encoding = "utf-8"
+ def write(self, data):
+ if isinstance(data,unicode):
+ data = data.encode(self.encoding,"replace")
+ self.stream.write(data)
+ self.stream.flush()
+ def __getattr__(self, attr):
+ return getattr(self.stream, attr)
+
+try:
+ from calibre.constants import iswindows, isosx
+except:
+ iswindows = sys.platform.startswith('win')
+ isosx = sys.platform.startswith('darwin')
+
+def unicode_argv():
+ if iswindows:
+ # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
+ # strings.
+
+ # Versions 2.x of Python don't support Unicode in sys.argv on
+ # Windows, with the underlying Windows API instead replacing multi-byte
+ # characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv
+ # as a list of Unicode strings and encode them as utf-8
+
+ from ctypes import POINTER, byref, cdll, c_int, windll
+ from ctypes.wintypes import LPCWSTR, LPWSTR
+
+ GetCommandLineW = cdll.kernel32.GetCommandLineW
+ GetCommandLineW.argtypes = []
+ GetCommandLineW.restype = LPCWSTR
+
+ CommandLineToArgvW = windll.shell32.CommandLineToArgvW
+ CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
+ CommandLineToArgvW.restype = POINTER(LPWSTR)
+
+ cmd = GetCommandLineW()
+ argc = c_int(0)
+ argv = CommandLineToArgvW(cmd, byref(argc))
+ if argc.value > 0:
+ # Remove Python executable and commands if present
+ start = argc.value - len(sys.argv)
+ return [argv[i] for i in
+ xrange(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"]
+ else:
+ argvencoding = sys.stdin.encoding
+ if argvencoding == None:
+ argvencoding = "utf-8"
+ return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
+
+class DrmException(Exception):
+ pass
+
+# Locate all of the nookStudy/nook for PC/Mac log file and return as list
+def getNookLogFiles():
+ logFiles = []
+ found = False
+ if iswindows:
+ import _winreg as winreg
+
+ # some 64 bit machines do not have the proper registry key for some reason
+ # or the python interface to the 32 vs 64 bit registry is broken
+ 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%")
+ 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"
+ if os.path.isdir(path):
+ paths.add(path)
+ path = winreg.ExpandEnvironmentStrings(u"%USERPROFILE%")+u"\\AppData\\Roaming"
+ if os.path.isdir(path):
+ paths.add(path)
+ # User Shell Folders show take precedent over Shell Folders if present
+ try:
+ regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\")
+ path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
+ if os.path.isdir(path):
+ paths.add(path)
+ except WindowsError:
+ pass
+ try:
+ regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\")
+ path = winreg.QueryValueEx(regkey, 'AppData')[0]
+ if os.path.isdir(path):
+ paths.add(path)
+ except WindowsError:
+ pass
+ try:
+ regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
+ path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
+ if os.path.isdir(path):
+ paths.add(path)
+ except WindowsError:
+ pass
+ try:
+ regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
+ path = winreg.QueryValueEx(regkey, 'AppData')[0]
+ if os.path.isdir(path):
+ paths.add(path)
+ except WindowsError:
+ pass
+
+ for path in paths:
+ # look for nookStudy log file
+ logpath = path +'\\Barnes & Noble\\NOOKstudy\\logs\\BNClientLog.txt'
+ if os.path.isfile(logpath):
+ found = True
+ print('Found nookStudy log file: ' + logpath.encode('ascii','ignore'))
+ logFiles.append(logpath)
+ else:
+ home = os.getenv('HOME')
+ # check for BNClientLog.txt in various locations
+ testpath = home + '/Library/Application Support/Barnes & Noble/DesktopReader/logs/BNClientLog.txt'
+ if os.path.isfile(testpath):
+ logFiles.append(testpath)
+ print('Found nookStudy log file: ' + testpath)
+ found = True
+ testpath = home + '/Library/Application Support/Barnes & Noble/DesktopReader/indices/BNClientLog.txt'
+ if os.path.isfile(testpath):
+ logFiles.append(testpath)
+ print('Found nookStudy log file: ' + testpath)
+ found = True
+ testpath = home + '/Library/Application Support/Barnes & Noble/BNDesktopReader/logs/BNClientLog.txt'
+ if os.path.isfile(testpath):
+ logFiles.append(testpath)
+ print('Found nookStudy log file: ' + testpath)
+ found = True
+ testpath = home + '/Library/Application Support/Barnes & Noble/BNDesktopReader/indices/BNClientLog.txt'
+ if os.path.isfile(testpath):
+ logFiles.append(testpath)
+ print('Found nookStudy log file: ' + testpath)
+ found = True
+
+ if not found:
+ print('No nook Study log files have been found.')
+ return logFiles
+
+
+# Extract CCHash key(s) from log file
+def getKeysFromLog(kLogFile):
+ keys = []
+ regex = re.compile("ccHash: \"(.{28})\"");
+ for line in open(kLogFile):
+ for m in regex.findall(line):
+ keys.append(m)
+ return keys
+
+# interface for calibre plugin
+def nookkeys(files = []):
+ keys = []
+ if files == []:
+ files = getNookLogFiles()
+ for file in files:
+ fileKeys = getKeysFromLog(file)
+ if fileKeys:
+ print u"Found {0} keys in the Nook Study log files".format(len(fileKeys))
+ keys.extend(fileKeys)
+ return keys
+
+# interface for Python DeDRM
+# returns single key or multiple keys, depending on path or file passed in
+def getkey(outpath, files=[]):
+ keys = nookkeys(files)
+ if len(keys) > 0:
+ if not os.path.isdir(outpath):
+ outfile = outpath
+ with file(outfile, 'w') as keyfileout:
+ keyfileout.write(keys[0])
+ print u"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))
+ if not os.path.exists(outfile):
+ break
+ with file(outfile, 'w') as keyfileout:
+ keyfileout.write(key)
+ print u"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)
+
+
+def cli_main():
+ sys.stdout=SafeUnbuffered(sys.stdout)
+ 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__)
+
+ try:
+ opts, args = getopt.getopt(argv[1:], "hk:")
+ except getopt.GetoptError, err:
+ print u"Error in options or arguments: {0}".format(err.args[0])
+ usage(progname)
+ sys.exit(2)
+
+ files = []
+ for o, a in opts:
+ if o == "-h":
+ usage(progname)
+ sys.exit(0)
+ if o == "-k":
+ files = [a]
+
+ if len(args) > 1:
+ usage(progname)
+ sys.exit(2)
+
+ if len(args) == 1:
+ # save to the specified file or directory
+ outpath = args[0]
+ if not os.path.isabs(outpath):
+ outpath = os.path.abspath(outpath)
+ else:
+ # save to the same directory as the script
+ outpath = os.path.dirname(argv[0])
+
+ # make sure the outpath is the
+ outpath = os.path.realpath(os.path.normpath(outpath))
+
+ if not getkey(outpath, files):
+ print u"Could not retrieve nook Study key."
+ return 0
+
+
+def gui_main():
+ try:
+ import Tkinter
+ import Tkconstants
+ import tkMessageBox
+ import traceback
+ except:
+ return cli_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:",
+ anchor=Tkconstants.W, justify=Tkconstants.LEFT)
+ label.pack(fill=Tkconstants.X, expand=0)
+ self.text = Tkinter.Text(self)
+ self.text.pack(fill=Tkconstants.BOTH, expand=1)
+
+ self.text.insert(Tkconstants.END, text)
+
+
+ argv=unicode_argv()
+ root = Tkinter.Tk()
+ root.withdraw()
+ progpath, progname = os.path.split(argv[0])
+ success = False
+ try:
+ keys = nookkeys()
+ keycount = 0
+ for key in keys:
+ print key
+ while True:
+ keycount += 1
+ outfile = os.path.join(progpath,u"nookkey{0:d}.b64".format(keycount))
+ if not os.path.exists(outfile):
+ break
+
+ with file(outfile, 'w') as keyfileout:
+ keyfileout.write(key)
+ success = True
+ tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
+ except DrmException, e:
+ tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
+ except Exception:
+ root.wm_state('normal')
+ root.title(progname)
+ text = traceback.format_exc()
+ ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1)
+ root.mainloop()
+ if not success:
+ return 1
+ return 0
+
+if __name__ == '__main__':
+ if len(sys.argv) > 1:
+ sys.exit(cli_main())
+ sys.exit(gui_main())
diff --git a/DeDRM_calibre_plugin/DeDRM_plugin/ineptepub.py b/DeDRM_calibre_plugin/DeDRM_plugin/ineptepub.py
index f8181cb..e116a6e 100644
--- a/DeDRM_calibre_plugin/DeDRM_plugin/ineptepub.py
+++ b/DeDRM_calibre_plugin/DeDRM_plugin/ineptepub.py
@@ -37,13 +37,14 @@ from __future__ import with_statement
# 5.9 - Fixed to retain zip file metadata (e.g. file modification date)
# 6.0 - moved unicode_argv call inside main for Windows DeDRM compatibility
# 6.1 - Work if TkInter is missing
+# 6.2 - Handle UTF-8 file names inside an ePub, fix by Jose Luis
"""
Decrypt Adobe Digital Editions encrypted ePub books.
"""
__license__ = 'GPL v3'
-__version__ = "6.1"
+__version__ = "6.2"
import sys
import os
@@ -366,7 +367,7 @@ class Decryptor(object):
return bytes
def decrypt(self, path, data):
- if path in self._encrypted:
+ if path.encode('utf-8') in self._encrypted:
data = self._aes.decrypt(data)[16:]
data = data[:-ord(data[-1])]
data = self.decompress(data)
diff --git a/DeDRM_calibre_plugin/DeDRM_plugin/scriptinterface.py b/DeDRM_calibre_plugin/DeDRM_plugin/scriptinterface.py
new file mode 100644
index 0000000..3be643f
--- /dev/null
+++ b/DeDRM_calibre_plugin/DeDRM_plugin/scriptinterface.py
@@ -0,0 +1,176 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
+
+import sys
+import os
+import re
+import ineptepub
+import ignobleepub
+import epubtest
+import zipfix
+import ineptpdf
+import erdr2pml
+import k4mobidedrm
+import traceback
+
+def decryptepub(infile, outdir, rscpath):
+ errlog = ''
+
+ # first fix the epub to make sure we do not get errors
+ name, ext = os.path.splitext(os.path.basename(infile))
+ bpath = os.path.dirname(infile)
+ zippath = os.path.join(bpath,name + '_temp.zip')
+ rv = zipfix.repairBook(infile, zippath)
+ if rv != 0:
+ print "Error while trying to fix epub"
+ return rv
+
+ # determine a good name for the output file
+ outfile = os.path.join(outdir, name + '_nodrm.epub')
+
+ rv = 1
+ # first try with the Adobe adept epub
+ if ineptepub.adeptBook(zippath):
+ # try with any keyfiles (*.der) in the rscpath
+ files = os.listdir(rscpath)
+ filefilter = re.compile("\.der$", re.IGNORECASE)
+ files = filter(filefilter.search, files)
+ if files:
+ for filename in files:
+ keypath = os.path.join(rscpath, filename)
+ userkey = open(keypath,'rb').read()
+ try:
+ rv = ineptepub.decryptBook(userkey, zippath, outfile)
+ if rv == 0:
+ print "Decrypted Adobe ePub with key file {0}".format(filename)
+ break
+ except Exception, e:
+ errlog += traceback.format_exc()
+ errlog += str(e)
+ rv = 1
+ # now try with ignoble epub
+ elif ignobleepub.ignobleBook(zippath):
+ # try with any keyfiles (*.b64) in the rscpath
+ files = os.listdir(rscpath)
+ filefilter = re.compile("\.b64$", re.IGNORECASE)
+ files = filter(filefilter.search, files)
+ if files:
+ for filename in files:
+ keypath = os.path.join(rscpath, filename)
+ userkey = open(keypath,'r').read()
+ #print userkey
+ try:
+ rv = ignobleepub.decryptBook(userkey, zippath, outfile)
+ if rv == 0:
+ print "Decrypted B&N ePub with key file {0}".format(filename)
+ break
+ except Exception, e:
+ errlog += traceback.format_exc()
+ errlog += str(e)
+ rv = 1
+ else:
+ encryption = epubtest.encryption(zippath)
+ if encryption == "Unencrypted":
+ print "{0} is not DRMed.".format(name)
+ rv = 0
+ else:
+ print "{0} has an unknown encryption.".format(name)
+
+ os.remove(zippath)
+ if rv != 0:
+ print errlog
+ return rv
+
+
+def decryptpdf(infile, outdir, rscpath):
+ errlog = ''
+ rv = 1
+
+ # determine a good name for the output file
+ name, ext = os.path.splitext(os.path.basename(infile))
+ outfile = os.path.join(outdir, name + '_nodrm.pdf')
+
+ # try with any keyfiles (*.der) in the rscpath
+ files = os.listdir(rscpath)
+ filefilter = re.compile("\.der$", re.IGNORECASE)
+ files = filter(filefilter.search, files)
+ if files:
+ for filename in files:
+ keypath = os.path.join(rscpath, filename)
+ userkey = open(keypath,'rb').read()
+ try:
+ rv = ineptpdf.decryptBook(userkey, infile, outfile)
+ if rv == 0:
+ break
+ except Exception, e:
+ errlog += traceback.format_exc()
+ errlog += str(e)
+ rv = 1
+
+ if rv != 0:
+ print errlog
+ return rv
+
+
+def decryptpdb(infile, outdir, rscpath):
+ outname = os.path.splitext(os.path.basename(infile))[0] + ".pmlz"
+ outpath = os.path.join(outdir, outname)
+ rv = 1
+ socialpath = os.path.join(rscpath,'sdrmlist.txt')
+ if os.path.exists(socialpath):
+ keydata = file(socialpath,'r').read()
+ keydata = keydata.rstrip(os.linesep)
+ ar = keydata.split(',')
+ for i in ar:
+ try:
+ name, cc8 = i.split(':')
+ except ValueError:
+ print ' Error parsing user supplied social drm data.'
+ return 1
+ try:
+ rv = erdr2pml.decryptBook(infile, outpath, True, erdr2pml.getuser_key(name, cc8))
+ except Exception, e:
+ errlog += traceback.format_exc()
+ errlog += str(e)
+ rv = 1
+
+ if rv == 0:
+ break
+ return rv
+
+
+def decryptk4mobi(infile, outdir, rscpath):
+ rv = 1
+ pidnums = []
+ pidspath = os.path.join(rscpath,'pidlist.txt')
+ if os.path.exists(pidspath):
+ pidstr = file(pidspath,'r').read()
+ pidstr = pidstr.rstrip(os.linesep)
+ pidstr = pidstr.strip()
+ if pidstr != '':
+ pidnums = pidstr.split(',')
+ serialnums = []
+ serialnumspath = os.path.join(rscpath,'seriallist.txt')
+ if os.path.exists(serialnumspath):
+ serialstr = file(serialnumspath,'r').read()
+ serialstr = serialstr.rstrip(os.linesep)
+ serialstr = serialstr.strip()
+ if serialstr != '':
+ serialnums = serialstr.split(',')
+ kDatabaseFiles = []
+ files = os.listdir(rscpath)
+ filefilter = re.compile("\.k4i$", re.IGNORECASE)
+ files = filter(filefilter.search, files)
+ if files:
+ for filename in files:
+ dpath = os.path.join(rscpath,filename)
+ kDatabaseFiles.append(dpath)
+ try:
+ rv = k4mobidedrm.decryptBook(infile, outdir, kDatabaseFiles, serialnums, pidnums)
+ except Exception, e:
+ errlog += traceback.format_exc()
+ errlog += str(e)
+ rv = 1
+
+ return rv
diff --git a/DeDRM_calibre_plugin/DeDRM_plugin/scrolltextwidget.py b/DeDRM_calibre_plugin/DeDRM_plugin/scrolltextwidget.py
new file mode 100644
index 0000000..98b4147
--- /dev/null
+++ b/DeDRM_calibre_plugin/DeDRM_plugin/scrolltextwidget.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
+
+import Tkinter
+import Tkconstants
+
+# basic scrolled text widget
+class ScrolledText(Tkinter.Text):
+ def __init__(self, master=None, **kw):
+ self.frame = Tkinter.Frame(master)
+ self.vbar = Tkinter.Scrollbar(self.frame)
+ self.vbar.pack(side=Tkconstants.RIGHT, fill=Tkconstants.Y)
+ kw.update({'yscrollcommand': self.vbar.set})
+ Tkinter.Text.__init__(self, self.frame, **kw)
+ self.pack(side=Tkconstants.LEFT, fill=Tkconstants.BOTH, expand=True)
+ self.vbar['command'] = self.yview
+ # Copy geometry methods of self.frame without overriding Text
+ # methods = hack!
+ text_meths = vars(Tkinter.Text).keys()
+ methods = vars(Tkinter.Pack).keys() + vars(Tkinter.Grid).keys() + vars(Tkinter.Place).keys()
+ methods = set(methods).difference(text_meths)
+ for m in methods:
+ if m[0] != '_' and m != 'config' and m != 'configure':
+ setattr(self, m, getattr(self.frame, m))
+
+ def __str__(self):
+ return str(self.frame)
diff --git a/DeDRM_calibre_plugin/DeDRM_plugin/simpleprefs.py b/DeDRM_calibre_plugin/DeDRM_plugin/simpleprefs.py
new file mode 100644
index 0000000..0809944
--- /dev/null
+++ b/DeDRM_calibre_plugin/DeDRM_plugin/simpleprefs.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
+
+import sys
+import os, os.path
+import shutil
+
+class SimplePrefsError(Exception):
+ pass
+
+class SimplePrefs(object):
+ def __init__(self, target, description):
+ self.prefs = {}
+ self.key2file={}
+ self.file2key={}
+ for keyfilemap in description:
+ [key, filename] = keyfilemap
+ self.key2file[key] = filename
+ self.file2key[filename] = key
+ self.target = target + 'Prefs'
+ if sys.platform.startswith('win'):
+ import _winreg as winreg
+ regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
+ path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
+ prefdir = path + os.sep + self.target
+ elif sys.platform.startswith('darwin'):
+ home = os.getenv('HOME')
+ prefdir = os.path.join(home,'Library','Preferences','org.' + self.target)
+ else:
+ # linux and various flavors of unix
+ home = os.getenv('HOME')
+ prefdir = os.path.join(home,'.' + self.target)
+ if not os.path.exists(prefdir):
+ os.makedirs(prefdir)
+ self.prefdir = prefdir
+ self.prefs['dir'] = self.prefdir
+ self._loadPreferences()
+
+ def _loadPreferences(self):
+ filenames = os.listdir(self.prefdir)
+ for filename in filenames:
+ if filename in self.file2key:
+ key = self.file2key[filename]
+ filepath = os.path.join(self.prefdir,filename)
+ if os.path.isfile(filepath):
+ try :
+ data = file(filepath,'rb').read()
+ self.prefs[key] = data
+ except Exception, e:
+ pass
+
+ def getPreferences(self):
+ return self.prefs
+
+ def setPreferences(self, newprefs={}):
+ if 'dir' not in newprefs:
+ raise SimplePrefsError('Error: Attempt to Set Preferences in unspecified directory')
+ if newprefs['dir'] != self.prefs['dir']:
+ raise SimplePrefsError('Error: Attempt to Set Preferences in unspecified directory')
+ for key in newprefs:
+ if key != 'dir':
+ if key in self.key2file:
+ filename = self.key2file[key]
+ filepath = os.path.join(self.prefdir,filename)
+ data = newprefs[key]
+ if data != None:
+ data = str(data)
+ if data == None or data == '':
+ if os.path.exists(filepath):
+ os.remove(filepath)
+ else:
+ try:
+ file(filepath,'wb').write(data)
+ except Exception, e:
+ pass
+ self.prefs = newprefs
+ return
diff --git a/DeDRM_calibre_plugin/DeDRM_plugin_ReadMe.txt b/DeDRM_calibre_plugin/DeDRM_plugin_ReadMe.txt
index a58eec8..6e77c16 100644
--- a/DeDRM_calibre_plugin/DeDRM_plugin_ReadMe.txt
+++ b/DeDRM_calibre_plugin/DeDRM_plugin_ReadMe.txt
@@ -1,19 +1,19 @@
-DeDRM_plugin.zip
+DeDRM_plugin.zip
================
-This calibre plugin replaces all previous DRM removal plugins. When you install this plugin, the older separate plugins should be removed.
+This calibre plugin replaces all previous DRM removal plugins. Before you install this plugin, you should uninstall any older individual DRM removal plugins, e.g. K4MobiDeDRM.
-This plugin will remove the DRM from Amazon Kindle ebooks (Mobi, KF8, Topaz and Print Replica), Mobipocket, Adobe Digital Edition ePubs (including Sony and Kobo ePubs), Barnes and Noble ePubs, Adobe Digital Edition PDFs, and Fictionwise eReader ebooks.
+This plugin will remove the DRM from Amazon Kindle ebooks (Mobi, KF8, Topaz and Print Replica), Mobipocket, Adobe Digital Edition ePubs (including Sony and Kobo ePubs), Barnes and Noble (nook) ePubs, Adobe Digital Edition PDFs, and Fictionwise eReader ebooks.
Installation
------------
-Do NOT select "Get plugins to enhance calibre" as this is reserved for 'official' calibre plugins, instead select "Change calibre behavior" to go to Calibre's Preferences page. Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (DeDRM_plugin.zip) and click the "Add" button. Click "Yes" in the the "Are you sure?" dialog. Click OK in the "Success" dialog.
+From the Preferences menu, do NOT select "Get plugins to enhance calibre" as this is reserved for 'official' calibre plugins. Instead select "Change calibre behavior" to go to Calibre's Preferences page. Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (DeDRM_plugin.zip) and click the "Add" button. Click "Yes" in the the "Are you sure?" dialog. Click OK in the "Success" dialog.
Customization
-------------
-The keys for ebooks downloaded using Kindle for Mac/PC and Adobe Digital Editions are automatically generated and saved when needed. If all your DRMed ebooks can be opened and read in Kindle for Mac/PC or Adobe Digital Editions on the same computer on which you are running calibre, you do not need to do any configuration of this plugin. (On Linux, Kindle for PC and Adobe Digital Editions along with Python and PyCrypto need to be installed under Wine for this to work, see the Linux section at the end.)
+The keys for ebooks downloaded using Kindle for Mac/PC, Adobe Digital Editions and NOOK Study are automatically generated and saved when needed. If all your DRMed ebooks can be downloaded and read in Kindle for Mac/PC, Adobe Digital Editions or NOOK Study on the same computer and user account on which you are running calibre, you do not need to do add any customisation data to this plugin. (Linux users should see the Linux section at the end of this ReadMe.)
If you have books from other sources (e.g. from an eInk Kindle), highlight the plugin (DeDRM under the "File type plugins" category) and click the "Customize Plugin" button.
@@ -26,21 +26,19 @@ When you have finished entering your configuration information, you must click t
Troubleshooting
---------------
-If you find that it's not working for you (imported ebooks still have DRM), you can save a lot of time and trouble by deleting the DRMed ebook from calibre and then trying to add the ebook to calibre in debug mode with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests.
+If you find that it's not working for you (imported ebooks still have DRM - that is, they won't convert or open in the calibre ebook viewer), you should make a log of import process by deleting the DRMed ebook from calibre and then adding the ebook to calibre when it's running in debug mode. This will generate a lot of helpful debugging info that can be copied into any online help requests. Here's how to do it:
-On Macintosh only you must first run calibre, open Preferences, open Miscellaneous, and click on the “Install command line tools” button. (On Windows and Linux the command line tools are installed automatically.)
-
-On Windows, open a terminal/command window. (Start/Run… and then type 'cmd' (without the 's) as the program to run).
+On Windows, open a terminal/command window. (Start/Run… and then type 'cmd.exe' (without the 's) as the program to run).
On Macintosh, open the Terminal application (in your Utilities folder).
On Linux open a command window. Hopefully all Linux users know how to do this.
You should now have a text-based command-line window open.
-Type in "calibre-debug -g" (without the ") and press the return/enter key. Calibre will launch and run as normal, but with debugging information output to the terminal window.
+Type in "calibre-debug -g" (without the "s but with the space before the -g) and press the return/enter key. Calibre will launch and run as normal, but with debugging information output to the terminal window.
-Import the drmed eBook into calibre in any of the the normal ways. (I usually drag&drop onto the calibre window.)
+Import the DRMed eBook into calibre in any of the the normal ways. (I usually drag&drop onto the calibre window.)
-More debug information will be written to the terminal window.
+Debug information will be written to the terminal window.
Copy the output from the terminal window.
On Windows, you must use the window menu (little icon at left of window bar) to select all the text and then to copy it.
@@ -51,15 +49,18 @@ Paste the information into a comment at my blog, http://apprenticealf.wordpress.
Credits
-------
-The mobidedrm and erdr2pml scripts were created by The Dark Reverser
-The ignobleepub, ignoblekeygen, ineptepub and adobe key scripts were created by i♥cabbages
-The k4mobidedrm script and supporting scripts were written by some_updates with help from DiapDealer and Apprentice Alf, based on code by Bart Simpson (aka Skindle), CMBDTC and clarknova
-The alfcrypto library was created by some_updates
-The ePub encryption detection script was adapted by Apprentice Alf from a script by Paul Durrant
-The DeDRM all-in-one AppleScript was created by Apprentice Alf
-The DeDRM all-in-one python script was created by some_updates and Apprentice Alf
-
-
+The original inept and ignoble scripts were by i♥cabbages
+The original mobidedrm and erdr2pml scripts were by The Dark Reverser
+The original topaz DRM removal script was by CMBDTC
+The original topaz format conversion scripts were by some_updates, clarknova and Bart Simpson
+The original obok script was by Physisticated
+
+The alfcrypto library is by some_updates
+The ePub encryption detection script is by Apprentice Alf, adapted from a script by Paul Durrant
+The ignoblekey script is by Apprentice Harper
+The DeDRM plugin was based on plugins by DiapDealer and is maintained by Apprentice Alf and Apprentice Harper
+
+Many fixes, updates and enhancements to the scripts and applicatons have been made by many other people. For more details, see the commments in the individual scripts.
Linux Systems Only