]> xmof Git - DeDRM.git/commitdiff
Android backup handling approach improved and implemented in Windows and plugin....
authorapprenticeharper <apprenticeharper@gmail.com>
Wed, 29 Jul 2015 17:11:19 +0000 (18:11 +0100)
committerapprenticeharper <apprenticeharper@gmail.com>
Wed, 29 Jul 2015 17:11:19 +0000 (18:11 +0100)
23 files changed:
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/__init__.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/androidkindlekey.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/config.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/dialogs.py [deleted file]
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/prefs.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/scriptinterface.py
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/DeDRM_app.pyw
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/__init__.py
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/androidkindlekey.py
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/config.py
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignoblekeyfetch.py
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/k4mobidedrm.py
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/prefs.py
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/scriptinterface.py
DeDRM_calibre_plugin/DeDRM_plugin.zip
DeDRM_calibre_plugin/DeDRM_plugin/DeDRM_Kindle for Android_Help.htm [moved from DeDRM_calibre_plugin/DeDRM_plugin/DeDRM_Kindle for Andoid_Help.htm with 100% similarity]
DeDRM_calibre_plugin/DeDRM_plugin/__init__.py
DeDRM_calibre_plugin/DeDRM_plugin/androidkindlekey.py
DeDRM_calibre_plugin/DeDRM_plugin/config.py
DeDRM_calibre_plugin/DeDRM_plugin/k4mobidedrm.py
DeDRM_calibre_plugin/DeDRM_plugin/prefs.py
DeDRM_calibre_plugin/DeDRM_plugin/scriptinterface.py

index 82329fe5615151ba3f0acc68ccc083044238afb6..5919f5b0c88496dca2f2686832f54eec409d5b30 100644 (file)
@@ -90,7 +90,7 @@ class DeDRM(FileTypePlugin):
     name                    = PLUGIN_NAME
     description             = u"Removes DRM from Amazon Kindle, Adobe Adept (including Kobo), Barnes & Noble, Mobipocket and eReader ebooks. Credit given to i♥cabbages and The Dark Reverser for the original stand-alone scripts."
     supported_platforms     = ['linux', 'osx', 'windows']
-    author                  = u"DiapDealer, Apprentice Alf, The Dark Reverser and i♥cabbages"
+    author                  = u"Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
     version                 = PLUGIN_VERSION_TUPLE
     minimum_calibre_version = (0, 7, 55)  # Compiled python libraries cannot be imported in earlier versions.
     file_types              = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','tpz'])
@@ -482,11 +482,15 @@ class DeDRM(FileTypePlugin):
         dedrmprefs = prefs.DeDRM_Prefs()
         pids = dedrmprefs['pids']
         serials = dedrmprefs['serials']
-        serials.extend(dedrmprefs['androidserials'])
+        for android_serials_list in dedrmprefs['androidkeys'].values():
+            #print android_serials_list
+            serials.extend(android_serials_list)
+        #print serials
+        androidFiles = []
         kindleDatabases = dedrmprefs['kindlekeys'].items()
 
         try:
-            book = k4mobidedrm.GetDecryptedBook(path_to_ebook,kindleDatabases,serials,pids,self.starttime)
+            book = k4mobidedrm.GetDecryptedBook(path_to_ebook,kindleDatabases,androidFiles,serials,pids,self.starttime)
         except Exception, e:
             decoded = False
             # perhaps we need to get a new default Kindle for Mac/PC key
@@ -558,6 +562,7 @@ class DeDRM(FileTypePlugin):
             # Decryption was successful return the modified PersistentTemporary
             # file to Calibre's import process.
             if  result == 0:
+                print u"{0} v{1}: Successfully decrypted with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime)
                 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_masked,time.time()-self.starttime)
@@ -576,7 +581,7 @@ class DeDRM(FileTypePlugin):
         self.starttime = time.time()
 
         booktype = os.path.splitext(path_to_ebook)[1].lower()[1:]
-        if booktype in ['prc','mobi','azw','azw1','azw3','azw4','tpz']:
+        if booktype in ['prc','mobi','pobi','azw','azw1','azw3','azw4','tpz']:
             # Kindle/Mobipocket
             decrypted_ebook = self.KindleMobiDecrypt(path_to_ebook)
         elif booktype == 'pdb':
@@ -593,7 +598,7 @@ class DeDRM(FileTypePlugin):
         else:
             print u"Unknown booktype {0}. Passing back to calibre unchanged".format(booktype)
             return path_to_ebook
-        print u"{0} v{1}: Successfully decrypted book after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
+        print u"{0} v{1}: Finished after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
         return decrypted_ebook
 
     def is_customizable(self):
index 7a2571070e99fd37274f5b9461bf54bd1109633d..2c539eea40c4159d6b9aeac2cea4186c037824c1 100644 (file)
@@ -14,14 +14,15 @@ from __future__ import with_statement
 #  1.2   - Changed to be callable from AppleScript by returning only serial number
 #        - and changed name to androidkindlekey.py
 #        - and added in unicode command line support
-#  1.3   - added in TkInter interface, output to a file and attempt to get backup from a connected android device.
+#  1.3   - added in TkInter interface, output to a file
+#  1.4   - Fix some problems identified by Aldo Bleeker
 
 """
 Retrieve Kindle for Android Serial Number.
 """
 
 __license__ = 'GPL v3'
-__version__ = '1.3'
+__version__ = '1.4'
 
 import os
 import sys
@@ -199,13 +200,16 @@ def get_serials1(path=STORAGE1):
         return []
 
     serials = []
+    if dsnid:
+        serials.append(dsnid)
     for token in tokens:
         if token:
             serials.append('%s%s' % (dsnid, token))
+            serials.append(token)
     return serials
 
 def get_serials2(path=STORAGE2):
-    ''' get serials from android's shared preference xml '''
+    ''' get serials from android's sql database '''
     if not os.path.isfile(path):
         return []
 
@@ -213,14 +217,32 @@ def get_serials2(path=STORAGE2):
     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()]
+    userdata_keys = cursor.fetchall()
+    dsns = []
+    for userdata_row in userdata_keys:
+        if userdata_row:
+            userdata_utf8 = userdata_row[0].encode('utf8')
+            if len(userdata_utf8) > 0:
+                dsns.append(userdata_utf8)
+    dsns = list(set(dsns))
 
     cursor.execute('''select userdata_value from userdata where userdata_key like '%/%kindle.account.tokens%' ''')
-    tokens = [x[0].encode('utf8') for x in cursor.fetchall()]
+    userdata_keys = cursor.fetchall()
+    tokens = []
+    for userdata_row in userdata_keys:
+        if userdata_row:
+            userdata_utf8 = userdata_row[0].encode('utf8')
+            if len(userdata_utf8) > 0:
+                tokens.append(userdata_utf8)
+    tokens = list(set(tokens))
     serials = []
     for x in dsns:
+        serials.append(x)
         for y in tokens:
             serials.append('%s%s' % (x, y))
+    for y in tokens:
+        serials.append(y)
     return serials
 
 def get_serials(path=STORAGE):
@@ -269,46 +291,31 @@ def get_serials(path=STORAGE):
             write_path = os.path.abspath(write.name)
             serials.extend(get_serials2(write_path))
             os.remove(write_path)
-
-    return serials
+    return list(set(serials))
 
 __all__ = [ 'get_serials', 'getkey']
 
-# interface for Python DeDRM
-# returns single key or multiple keys, depending on path or file passed in
-def getkey(outpath, inpath):
+# procedure for CLI and GUI interfaces
+# returns single or multiple keys (one per line) in the specified file
+def getkey(outfile, inpath):
     keys = get_serials(inpath)
     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
+        with file(outfile, 'w') as keyfileout:
             for key in keys:
-                while True:
-                    keycount += 1
-                    outfile = os.path.join(outpath,u"kindlekey{0:d}.k4a".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)
+                keyfileout.write(key)
+                keyfileout.write("\n")
         return True
     return False
 
 
 def usage(progname):
-    print u"{0} v{1}\nCopyright © 2013-2015 Thom and Apprentice Harper".format(progname,__version__)
-    print u"Decrypts the serial number of Kindle For Android from Android backup or file"
+    print u"Decrypts the serial number(s) of Kindle For Android from Android backup or file"
     print u"Get backup.ab file using adb backup com.amazon.kindle for Android 4.0+."
     print u"Otherwise extract AmazonSecureStorage.xml from /data/data/com.amazon.kindle/shared_prefs/AmazonSecureStorage.xml"
     print u"Or map_data_storage.db from /data/data/com.amazon.kindle/databases/map_data_storage.db"
     print u""
-    print u"Serial number is written to standard output."
     print u"Usage:"
-    print u"    {0:s} [-h] [-b <backup.ab>] [<outpath>]".format(progname)
+    print u"    {0:s} [-h] [-b <backup.ab>] [<outfile.k4a>]".format(progname)
 
 
 def cli_main():
@@ -339,24 +346,28 @@ def cli_main():
 
     if len(args) == 1:
         # save to the specified file or directory
-        outpath = args[0]
-        if not os.path.isabs(outpath):
-           outpath = os.path.join(os.path.dirname(argv[0]),outpath)
-           outpath = os.path.abspath(outpath)
+        outfile = args[0]
+        if not os.path.isabs(outfile):
+           outfile = os.path.join(os.path.dirname(argv[0]),outfile)
+           outfile = os.path.abspath(outfile)
+        if os.path.isdir(outfile):
+           outfile = os.path.join(os.path.dirname(argv[0]),"androidkindlekey.k4a")
     else:
         # save to the same directory as the script
-        outpath = os.path.dirname(argv[0])
+        outfile = os.path.join(os.path.dirname(argv[0]),"androidkindlekey.k4a")
 
     # make sure the outpath is OK
-    outpath = os.path.realpath(os.path.normpath(outpath))
+    outfile = os.path.realpath(os.path.normpath(outfile))
 
     if not os.path.isfile(inpath):
         usage(progname)
         print u"\n{0:s} file not found".format(inpath)
         return 2
 
-    if not getkey(outpath, inpath):
-        print u"Could not retrieve Kindle for Android key."
+    if getkey(outfile, inpath):
+        print u"\nSaved Kindle for Android key to {0}".format(outfile)
+    else:
+        print u"\nCould not retrieve Kindle for Android key."
     return 0
 
 
index dcba6eb7233896d9304f78ded51dd1231e979b33..b3a21a1a44914b8d500d4a731914e3a216daad59 100644 (file)
@@ -34,9 +34,9 @@ from calibre.constants import iswindows, isosx
 from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
 from calibre_plugins.dedrm.__init__ import RESOURCE_NAME as help_file_name
 from calibre_plugins.dedrm.utilities import uStrCmp
-from calibre_plugins.dedrm.androidkindlekey import get_serials
 
 import calibre_plugins.dedrm.prefs as prefs
+import calibre_plugins.dedrm.androidkindlekey as androidkindlekey
 
 class ConfigWidget(QWidget):
     def __init__(self, plugin_path, alfdir):
@@ -54,9 +54,9 @@ class ConfigWidget(QWidget):
         self.tempdedrmprefs['adeptkeys'] = self.dedrmprefs['adeptkeys'].copy()
         self.tempdedrmprefs['ereaderkeys'] = self.dedrmprefs['ereaderkeys'].copy()
         self.tempdedrmprefs['kindlekeys'] = self.dedrmprefs['kindlekeys'].copy()
+        self.tempdedrmprefs['androidkeys'] = self.dedrmprefs['androidkeys'].copy()
         self.tempdedrmprefs['pids'] = list(self.dedrmprefs['pids'])
         self.tempdedrmprefs['serials'] = list(self.dedrmprefs['serials'])
-        self.tempdedrmprefs['androidserials'] = list(self.dedrmprefs['androidserials'])
         self.tempdedrmprefs['adobewineprefix'] = self.dedrmprefs['adobewineprefix']
         self.tempdedrmprefs['kindlewineprefix'] = self.dedrmprefs['kindlewineprefix']
 
@@ -86,9 +86,9 @@ class ConfigWidget(QWidget):
         self.bandn_button.setText(u"Barnes and Noble ebooks")
         self.bandn_button.clicked.connect(self.bandn_keys)
         self.kindle_android_button = QtGui.QPushButton(self)
-        self.kindle_android_button.setToolTip(_(u"Click to manage Kindle for Android serial numbers for Kindle ebooks"))
+        self.kindle_android_button.setToolTip(_(u"Click to manage keys for Kindle for Android ebooks"))
         self.kindle_android_button.setText(u"Kindle for Android ebooks")
-        self.kindle_android_button.clicked.connect(self.kindle_android_serials)
+        self.kindle_android_button.clicked.connect(self.kindle_android)
         self.kindle_serial_button = QtGui.QPushButton(self)
         self.kindle_serial_button.setToolTip(_(u"Click to manage eInk Kindle serial numbers for Kindle ebooks"))
         self.kindle_serial_button.setText(u"eInk Kindle ebooks")
@@ -123,8 +123,8 @@ class ConfigWidget(QWidget):
         d = ManageKeysDialog(self,u"EInk Kindle Serial Number",self.tempdedrmprefs['serials'], AddSerialDialog)
         d.exec_()
         
-    def kindle_android_serials(self):
-        d = ManageKeysDialog(self,u"Kindle for Andoid Serial Number",self.tempdedrmprefs['androidserials'], AddAndroidSerialDialog, 'ab')
+    def kindle_android(self):
+        d = ManageKeysDialog(self,u"Kindle for Android Keys File",self.tempdedrmprefs['androidkeys'], AddAndroidDialog, 'k4a')
         d.exec_()
 
     def kindle_keys(self):
@@ -173,9 +173,9 @@ class ConfigWidget(QWidget):
         self.dedrmprefs.set('adeptkeys', self.tempdedrmprefs['adeptkeys'])
         self.dedrmprefs.set('ereaderkeys', self.tempdedrmprefs['ereaderkeys'])
         self.dedrmprefs.set('kindlekeys', self.tempdedrmprefs['kindlekeys'])
+        self.dedrmprefs.set('androidkeys', self.tempdedrmprefs['androidkeys'])
         self.dedrmprefs.set('pids', self.tempdedrmprefs['pids'])
         self.dedrmprefs.set('serials', self.tempdedrmprefs['serials'])
-        self.dedrmprefs.set('androidserials', self.tempdedrmprefs['androidserials'])
         self.dedrmprefs.set('adobewineprefix', self.tempdedrmprefs['adobewineprefix'])
         self.dedrmprefs.set('kindlewineprefix', self.tempdedrmprefs['kindlewineprefix'])
         self.dedrmprefs.set('configured', True)
@@ -200,7 +200,7 @@ class ManageKeysDialog(QDialog):
         self.import_key = (keyfile_ext != u"")
         self.binary_file = (keyfile_ext == u"der")
         self.json_file = (keyfile_ext == u"k4i")
-        self.android_file = (keyfile_ext == u"ab")
+        self.android_file = (keyfile_ext == u"k4a")
         self.wineprefix = wineprefix
 
         self.setWindowTitle("{0} {1}: Manage {2}s".format(PLUGIN_NAME, PLUGIN_VERSION, self.key_type_name))
@@ -232,8 +232,8 @@ class ManageKeysDialog(QDialog):
         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.setToolTip(u"Create new {0}".format(self.key_type_name))
         self._add_key_button.clicked.connect(self.add_key)
         button_layout.addWidget(self._add_key_button)
 
@@ -383,42 +383,34 @@ class ManageKeysDialog(QDialog):
             for filename in files:
                 fpath = os.path.join(config_dir, filename)
                 filename = os.path.basename(filename)
-                if type(self.plugin_keys) != dict:
-                    # must be the new Kindle for Android section
-                    print u"Getting keys from "+fpath
-                    new_keys = get_serials(fpath)
-                    for key in new_keys:
-                        if key in self.plugin_keys:
-                            skipped += 1
-                        else:
-                            counter += 1
-                            self.plugin_keys.append(key)
-                else:
-                    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
+                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)
+                elif self.android_file:
+                    # convert to list of the keys in the string
+                    new_key_value = new_key_value.splitlines()
+                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:
@@ -453,6 +445,10 @@ class ManageKeysDialog(QDialog):
                     fname.write(self.plugin_keys[keyname].decode('hex'))
                 elif self.json_file:
                     fname.write(json.dumps(self.plugin_keys[keyname]))
+                elif self.android_file:
+                    for key in self.plugin_keys[keyname]:
+                        fname.write(key)
+                        fname.write("\n")
                 else:
                     fname.write(self.plugin_keys[keyname])
 
@@ -539,9 +535,6 @@ class AddBandNKeyDialog(QDialog):
                                 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)
@@ -626,9 +619,6 @@ class AddEReaderDialog(QDialog):
         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)
@@ -727,9 +717,7 @@ class AddAdeptDialog(QDialog):
             self.key_ledit = QLineEdit(u"default_key", self)
             self.key_ledit.setToolTip(u"<p>Enter an identifying name for the current default Adobe Digital Editions key.")
             key_group.addWidget(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)
@@ -800,15 +788,14 @@ class AddKindleDialog(QDialog):
             self.key_ledit = QLineEdit(u"default_key", self)
             self.key_ledit.setToolTip(u"<p>Enter an identifying name for the current default Kindle for Mac/PC key.")
             key_group.addWidget(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
+            
+            # if no default, both buttons do the same
             self.button_box.accepted.connect(self.reject)
 
         self.button_box.rejected.connect(self.reject)
@@ -854,9 +841,6 @@ class AddSerialDialog(QDialog):
         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)
@@ -883,51 +867,89 @@ class AddSerialDialog(QDialog):
         QDialog.accept(self)
 
 
-class AddAndroidSerialDialog(QDialog):
+class AddAndroidDialog(QDialog):
     def __init__(self, parent=None,):
+
         QDialog.__init__(self, parent)
         self.parent = parent
-        self.setWindowTitle(u"{0} {1}: Add New Kindle for Android Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION))
+        self.setWindowTitle(u"{0} {1}: Add new Kindle for Android Key".format(PLUGIN_NAME, PLUGIN_VERSION))
         layout = QVBoxLayout(self)
         self.setLayout(layout)
+        self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
 
         data_group_box = QGroupBox(u"", self)
         layout.addWidget(data_group_box)
         data_group_box_layout = QVBoxLayout()
         data_group_box.setLayout(data_group_box_layout)
 
+        file_group = QHBoxLayout()
+        data_group_box_layout.addLayout(file_group)
+        add_btn = QPushButton(u"Choose Backup File", self)
+        add_btn.setToolTip(u"Import Kindle for Android backup file.")
+        add_btn.clicked.connect(self.get_android_file)
+        file_group.addWidget(add_btn)
+        self.selected_file_name = QLabel(u"",self)
+        self.selected_file_name.setAlignment(Qt.AlignHCenter)
+        file_group.addWidget(self.selected_file_name)
+        
         key_group = QHBoxLayout()
         data_group_box_layout.addLayout(key_group)
-        key_group.addWidget(QLabel(u"Kindle for Android Serial Number:", self))
-        self.key_ledit = QLineEdit("", self)
-        self.key_ledit.setToolTip(u"Enter a Kindle for ANdroid serial number. These can be found using the androidkindlekey.py script.")
+        key_group.addWidget(QLabel(u"Unique Key Name:", self))
+        self.key_ledit = QLineEdit(u"", self)
+        self.key_ledit.setToolTip(u"<p>Enter an identifying name for the Android for Kindle key.")
         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)
+        #key_label = QLabel(_(''), self)
+        #key_label.setAlignment(Qt.AlignHCenter)
+        #data_group_box_layout.addWidget(key_label)
+        
         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()).strip()
 
+    @property
+    def file_name(self):
+        return unicode(self.selected_file_name.text()).strip()
+
     @property
     def key_value(self):
-        return unicode(self.key_ledit.text()).strip()
+        return self.serials_from_file
+        
+    def get_android_file(self):
+        unique_dlg_name = PLUGIN_NAME + u"Import Kindle for Android backup file" #takes care of automatically remembering last directory
+        caption = u"Select Kindle for Android backup file to add"
+        filters = [(u"Kindle for Android backup files", ['db','ab','xml'])]
+        files = choose_files(self, unique_dlg_name, caption, filters, all_files=False)
+        self.serials_from_file = []
+        file_name = u""
+        if files:
+            # find the first selected file that yields some serial numbers
+            for filename in files:
+                fpath = os.path.join(config_dir, filename)
+                self.filename = os.path.basename(filename)
+                file_serials = androidkindlekey.get_serials(fpath)
+                if len(file_serials)>0:
+                    file_name = os.path.basename(self.filename)
+                    self.serials_from_file.extend(file_serials)
+        self.selected_file_name.setText(file_name)
+    
 
     def accept(self):
+        if len(self.file_name) == 0 or len(self.key_value) == 0:
+            errmsg = u"Please choose a Kindle for Android backup file."
+            return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
         if len(self.key_name) == 0 or self.key_name.isspace():
-            errmsg = u"Please enter a Kindle for Android Serial Number or click Cancel in the dialog."
+            errmsg = u"Please enter a key name."
+            return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
+        if len(self.key_name) < 4:
+            errmsg = u"Key name must be at <i>least</i> 4 characters long!"
             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)
@@ -947,9 +969,6 @@ class AddPIDDialog(QDialog):
         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)
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/dialogs.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/dialogs.py
deleted file mode 100644 (file)
index 9921e4e..0000000
+++ /dev/null
@@ -1,719 +0,0 @@
-#!/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.ignoblekeyfetch import fetch_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)
-
-
index f4dd3fba802b2656d0d60cef7ffc9aabaa3536e2..4ce5aab7b550a11aec305fc3a08658696558c8f6 100644 (file)
@@ -3,15 +3,15 @@
 
 from __future__ import with_statement
 
-# ignobleepub.pyw, version 3.6
-# Copyright © 2009-2012 by DiapDealer et al.
+# k4mobidedrm.py, version 5.3
+# Copyright © 2009-2015 by ApprenticeHarper et al.
 
-# engine to remove drm from Kindle for Mac and Kindle for PC books
+# engine to remove drm from Kindle and Mobipocket ebooks
 # for personal use for archiving and converting your ebooks
 
 # PLEASE DO NOT PIRATE EBOOKS!
 
-# We want all authors and publishers, and eBook stores to live
+# We want all authors and publishers, and ebook stores to live
 # long and prosperous lives but at the same time  we just want to
 # be able to read OUR books on whatever device we want and to keep
 # readable for a long, long time
@@ -55,8 +55,9 @@ from __future__ import with_statement
 #      - tweaked GetDecryptedBook interface to leave passed parameters unchanged
 #  5.1 - moved unicode_argv call inside main for Windows DeDRM compatibility
 #  5.2 - Fixed error in command line processing of unicode arguments
+#  5.3 - Changed Android support to allow passing of backup .ab files
 
-__version__ = '5.2'
+__version__ = '5.3'
 
 
 import sys, os, re
@@ -187,7 +188,7 @@ def unescape(text):
         return text # leave as is
     return re.sub(u"&#?\w+;", fixup, text)
 
-def GetDecryptedBook(infile, kDatabases, serials, pids, starttime = time.time()):
+def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime = time.time()):
     # handle the obvious cases at the beginning
     if not os.path.isfile(infile):
         raise DrmException(u"Input file does not exist.")
@@ -207,9 +208,14 @@ def GetDecryptedBook(infile, kDatabases, serials, pids, starttime = time.time())
 
     # copy list of pids
     totalpids = list(pids)
-    # extend PID list with book-specific PIDs
+    # extend list of serials with serials from android databases
+    for aFile in androidFiles:
+        serials.extend(androidkindlekey.get_serials(aFile))
+    # extend PID list with book-specific PIDs from seriala and kDatabases
     md1, md2 = mb.getPIDMetaInfo()
     totalpids.extend(kgenpids.getPidList(md1, md2, serials, kDatabases))
+    # remove any duplicates
+    totalpid = list(set(totalpids))
     print u"Found {1:d} keys to try after {0:.1f} seconds".format(time.time()-starttime, len(totalpids))
 
     try:
@@ -223,7 +229,7 @@ def GetDecryptedBook(infile, kDatabases, serials, pids, starttime = time.time())
 
 
 # kDatabaseFiles is a list of files created by kindlekey
-def decryptBook(infile, outdir, kDatabaseFiles, serials, pids):
+def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
     starttime = time.time()
     kDatabases = []
     for dbfile in kDatabaseFiles:
@@ -239,7 +245,7 @@ def decryptBook(infile, outdir, kDatabaseFiles, serials, pids):
 
 
     try:
-        book = GetDecryptedBook(infile, kDatabases, serials, pids, starttime)
+        book = GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime)
     except Exception, e:
         print u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime)
         traceback.print_exc()
@@ -254,7 +260,7 @@ def decryptBook(infile, outdir, kDatabaseFiles, serials, pids):
 
     # avoid excessively long file names
     if len(outfilename)>150:
-        outfilename = outfilename[:150]
+        outfilename = outfilename[:99]+"--"+outfilename[-49:]
 
     outfilename = outfilename+u"_nodrm"
     outfile = os.path.join(outdir, outfilename + book.getBookExtension())
@@ -275,7 +281,7 @@ def decryptBook(infile, outdir, kDatabaseFiles, serials, pids):
 def usage(progname):
     print u"Removes DRM protection from Mobipocket, Amazon KF8, Amazon Print Replica and Amazon Topaz ebooks"
     print u"Usage:"
-    print u"    {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] [ -a <AmazonSecureStorage.xml> ] <infile> <outdir>".format(progname)
+    print u"    {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] [ -a <AmazonSecureStorage.xml|backup.ab> ] <infile> <outdir>".format(progname)
 
 #
 # Main
@@ -298,6 +304,7 @@ def cli_main():
     infile = args[0]
     outdir = args[1]
     kDatabaseFiles = []
+    androidFiles = []
     serials = []
     pids = []
 
@@ -317,12 +324,12 @@ def cli_main():
         if o == '-a':
             if a == None:
                 continue
-            serials.extend(androidkindlekey.get_serials(a))
+            androidFiles.apprend(a)
 
     # try with built in Kindle Info files if not on Linux
     k4 = not sys.platform.startswith('linux')
 
-    return decryptBook(infile, outdir, kDatabaseFiles, serials, pids)
+    return decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids)
 
 
 if __name__ == '__main__':
index f0f494cfa8db189f9e267b90777247aba837c60d..c1bfcb9a2936bc17add82eded8621df859a5161f 100644 (file)
@@ -23,9 +23,9 @@ class DeDRM_Prefs():
         self.dedrmprefs.defaults['adeptkeys'] = {}
         self.dedrmprefs.defaults['ereaderkeys'] = {}
         self.dedrmprefs.defaults['kindlekeys'] = {}
+        self.dedrmprefs.defaults['androidkeys'] = {}
         self.dedrmprefs.defaults['pids'] = []
         self.dedrmprefs.defaults['serials'] = []
-        self.dedrmprefs.defaults['androidserials'] = []
         self.dedrmprefs.defaults['adobewineprefix'] = ""
         self.dedrmprefs.defaults['kindlewineprefix'] = ""
 
@@ -41,12 +41,12 @@ class DeDRM_Prefs():
             self.dedrmprefs['ereaderkeys'] = {}
         if self.dedrmprefs['kindlekeys'] == {}:
             self.dedrmprefs['kindlekeys'] = {}
+        if self.dedrmprefs['androidkeys'] == {}:
+            self.dedrmprefs['androidkeys'] = {}
         if self.dedrmprefs['pids'] == []:
             self.dedrmprefs['pids'] = []
         if self.dedrmprefs['serials'] == []:
             self.dedrmprefs['serials'] = []
-        if self.dedrmprefs['androidserials'] == []:
-            self.dedrmprefs['androidserials'] = []
 
     def __getitem__(self,kind = None):
         if kind is not None:
index 3be643f5447bff1896e2816ff75265873b663b84..ec86b130c898b190b59527eed73378c72c50494b 100644 (file)
@@ -166,8 +166,30 @@ def decryptk4mobi(infile, outdir, rscpath):
         for filename in files:
             dpath = os.path.join(rscpath,filename)
             kDatabaseFiles.append(dpath)
+    androidFiles = []
+    files = os.listdir(rscpath)
+    filefilter = re.compile("\.ab$", re.IGNORECASE)
+    files = filter(filefilter.search, files)
+    if files:
+        for filename in files:
+            dpath = os.path.join(rscpath,filename)
+            androidFiles.append(dpath)
+    files = os.listdir(rscpath)
+    filefilter = re.compile("\.db$", re.IGNORECASE)
+    files = filter(filefilter.search, files)
+    if files:
+        for filename in files:
+            dpath = os.path.join(rscpath,filename)
+            androidFiles.append(dpath)
+    files = os.listdir(rscpath)
+    filefilter = re.compile("\.xml$", re.IGNORECASE)
+    files = filter(filefilter.search, files)
+    if files:
+        for filename in files:
+            dpath = os.path.join(rscpath,filename)
+            androidFiles.append(dpath)
     try:
-        rv = k4mobidedrm.decryptBook(infile, outdir, kDatabaseFiles, serialnums, pidnums)
+        rv = k4mobidedrm.decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serialnums, pidnums)
     except Exception, e:
         errlog += traceback.format_exc()
         errlog += str(e)
index 5f9623c7b9f3b086a17874191ac6ad8b2b53a87c..34cfa7073703fb9b94bce16e3347e0c6657602ba 100644 (file)
@@ -132,8 +132,14 @@ class MainApp(Tk):
             nfile = os.path.join(prefdir,fname)
             if os.path.isfile(dfile):
                 shutil.copyfile(dfile,nfile)
-        if 'kinfofile' in newprefs:
-            dfile = newprefs['kinfofile']
+        if 'kindlefile' in newprefs:
+            dfile = newprefs['kindlefile']
+            fname = os.path.basename(dfile)
+            nfile = os.path.join(prefdir,fname)
+            if os.path.isfile(dfile):
+                shutil.copyfile(dfile,nfile)
+        if 'androidfile' in newprefs:
+            dfile = newprefs['androidfile']
             fname = os.path.basename(dfile)
             nfile = os.path.join(prefdir,fname)
             if os.path.isfile(dfile):
@@ -187,11 +193,11 @@ class PrefsDialog(Toplevel):
         button.grid(row=cur_row, column=2)
 
         cur_row = cur_row + 1
-        Tkinter.Label(body, text='Android Kindle Key file (kindlekey.k4a)').grid(row=cur_row, sticky=Tkconstants.E)
+        Tkinter.Label(body, text='Android Kindle backup file (backup.ab)').grid(row=cur_row, sticky=Tkconstants.E)
         self.akkpath = Tkinter.Entry(body, width=50)
         self.akkpath.grid(row=cur_row, column=1, sticky=sticky)
         prefdir = self.prefs_array['dir']
-        keyfile = os.path.join(prefdir,'kindlekey.k4a')
+        keyfile = os.path.join(prefdir,'backup.ab')
         if os.path.isfile(keyfile):
             path = keyfile
             self.akkpath.insert(0, path)
@@ -332,21 +338,13 @@ class PrefsDialog(Toplevel):
         return
 
     def get_akkpath(self):
-        akkbpath = tkFileDialog.askopenfilename(parent=None, title='Select Android for Kindle backup file',
+        cpath = self.akkpath.get()
+        akkpath = tkFileDialog.askopenfilename(initialdir = cpath, parent=None, title='Select Android for Kindle backup file',
             defaultextension='.ab', filetypes=[('Kindle for Android backup file', '.ab'), ('All Files', '.*')])
-        if akkbpath:
-            # call androidkindlekey here
-            prefdir = self.prefs_array['dir']
-            androidkindlekeyfile = os.path.join(prefdir,'kindlekey.k4a')
-            import androidkindlekey
-            try:
-                androidkindlekey.getkey(androidkindlekeyfile, akkbpath)
-            except:
-                traceback.print_exc()
-                pass
-            if os.path.isfile(androidkindlekeyfile):
-                self.akkpath.delete(0, Tkconstants.END)
-                self.akkpath.insert(0, androidkindlekeyfile)
+        if akkpath:
+            akkpath = os.path.normpath(akkpath)
+            self.akkpath.delete(0, Tkconstants.END)
+            self.akkpath.insert(0, akkpath)
         return
 
     def get_bnkpath(self):
@@ -401,6 +399,9 @@ class PrefsDialog(Toplevel):
         kkpath = self.kkpath.get()
         if os.path.dirname(kkpath) != prefdir:
             new_prefs['kindlefile'] = kkpath
+        akkpath = self.akkpath.get()
+        if os.path.dirname(akkpath) != prefdir:
+            new_prefs['androidfile'] = akkpath
         self.master.setPreferences(new_prefs)
         # and update internal copies
         self.prefs_array['pids'] = new_prefs['pids']
index 82329fe5615151ba3f0acc68ccc083044238afb6..5919f5b0c88496dca2f2686832f54eec409d5b30 100644 (file)
@@ -90,7 +90,7 @@ class DeDRM(FileTypePlugin):
     name                    = PLUGIN_NAME
     description             = u"Removes DRM from Amazon Kindle, Adobe Adept (including Kobo), Barnes & Noble, Mobipocket and eReader ebooks. Credit given to i♥cabbages and The Dark Reverser for the original stand-alone scripts."
     supported_platforms     = ['linux', 'osx', 'windows']
-    author                  = u"DiapDealer, Apprentice Alf, The Dark Reverser and i♥cabbages"
+    author                  = u"Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
     version                 = PLUGIN_VERSION_TUPLE
     minimum_calibre_version = (0, 7, 55)  # Compiled python libraries cannot be imported in earlier versions.
     file_types              = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','tpz'])
@@ -482,11 +482,15 @@ class DeDRM(FileTypePlugin):
         dedrmprefs = prefs.DeDRM_Prefs()
         pids = dedrmprefs['pids']
         serials = dedrmprefs['serials']
-        serials.extend(dedrmprefs['androidserials'])
+        for android_serials_list in dedrmprefs['androidkeys'].values():
+            #print android_serials_list
+            serials.extend(android_serials_list)
+        #print serials
+        androidFiles = []
         kindleDatabases = dedrmprefs['kindlekeys'].items()
 
         try:
-            book = k4mobidedrm.GetDecryptedBook(path_to_ebook,kindleDatabases,serials,pids,self.starttime)
+            book = k4mobidedrm.GetDecryptedBook(path_to_ebook,kindleDatabases,androidFiles,serials,pids,self.starttime)
         except Exception, e:
             decoded = False
             # perhaps we need to get a new default Kindle for Mac/PC key
@@ -558,6 +562,7 @@ class DeDRM(FileTypePlugin):
             # Decryption was successful return the modified PersistentTemporary
             # file to Calibre's import process.
             if  result == 0:
+                print u"{0} v{1}: Successfully decrypted with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime)
                 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_masked,time.time()-self.starttime)
@@ -576,7 +581,7 @@ class DeDRM(FileTypePlugin):
         self.starttime = time.time()
 
         booktype = os.path.splitext(path_to_ebook)[1].lower()[1:]
-        if booktype in ['prc','mobi','azw','azw1','azw3','azw4','tpz']:
+        if booktype in ['prc','mobi','pobi','azw','azw1','azw3','azw4','tpz']:
             # Kindle/Mobipocket
             decrypted_ebook = self.KindleMobiDecrypt(path_to_ebook)
         elif booktype == 'pdb':
@@ -593,7 +598,7 @@ class DeDRM(FileTypePlugin):
         else:
             print u"Unknown booktype {0}. Passing back to calibre unchanged".format(booktype)
             return path_to_ebook
-        print u"{0} v{1}: Successfully decrypted book after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
+        print u"{0} v{1}: Finished after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
         return decrypted_ebook
 
     def is_customizable(self):
index 7a2571070e99fd37274f5b9461bf54bd1109633d..2c539eea40c4159d6b9aeac2cea4186c037824c1 100644 (file)
@@ -14,14 +14,15 @@ from __future__ import with_statement
 #  1.2   - Changed to be callable from AppleScript by returning only serial number
 #        - and changed name to androidkindlekey.py
 #        - and added in unicode command line support
-#  1.3   - added in TkInter interface, output to a file and attempt to get backup from a connected android device.
+#  1.3   - added in TkInter interface, output to a file
+#  1.4   - Fix some problems identified by Aldo Bleeker
 
 """
 Retrieve Kindle for Android Serial Number.
 """
 
 __license__ = 'GPL v3'
-__version__ = '1.3'
+__version__ = '1.4'
 
 import os
 import sys
@@ -199,13 +200,16 @@ def get_serials1(path=STORAGE1):
         return []
 
     serials = []
+    if dsnid:
+        serials.append(dsnid)
     for token in tokens:
         if token:
             serials.append('%s%s' % (dsnid, token))
+            serials.append(token)
     return serials
 
 def get_serials2(path=STORAGE2):
-    ''' get serials from android's shared preference xml '''
+    ''' get serials from android's sql database '''
     if not os.path.isfile(path):
         return []
 
@@ -213,14 +217,32 @@ def get_serials2(path=STORAGE2):
     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()]
+    userdata_keys = cursor.fetchall()
+    dsns = []
+    for userdata_row in userdata_keys:
+        if userdata_row:
+            userdata_utf8 = userdata_row[0].encode('utf8')
+            if len(userdata_utf8) > 0:
+                dsns.append(userdata_utf8)
+    dsns = list(set(dsns))
 
     cursor.execute('''select userdata_value from userdata where userdata_key like '%/%kindle.account.tokens%' ''')
-    tokens = [x[0].encode('utf8') for x in cursor.fetchall()]
+    userdata_keys = cursor.fetchall()
+    tokens = []
+    for userdata_row in userdata_keys:
+        if userdata_row:
+            userdata_utf8 = userdata_row[0].encode('utf8')
+            if len(userdata_utf8) > 0:
+                tokens.append(userdata_utf8)
+    tokens = list(set(tokens))
     serials = []
     for x in dsns:
+        serials.append(x)
         for y in tokens:
             serials.append('%s%s' % (x, y))
+    for y in tokens:
+        serials.append(y)
     return serials
 
 def get_serials(path=STORAGE):
@@ -269,46 +291,31 @@ def get_serials(path=STORAGE):
             write_path = os.path.abspath(write.name)
             serials.extend(get_serials2(write_path))
             os.remove(write_path)
-
-    return serials
+    return list(set(serials))
 
 __all__ = [ 'get_serials', 'getkey']
 
-# interface for Python DeDRM
-# returns single key or multiple keys, depending on path or file passed in
-def getkey(outpath, inpath):
+# procedure for CLI and GUI interfaces
+# returns single or multiple keys (one per line) in the specified file
+def getkey(outfile, inpath):
     keys = get_serials(inpath)
     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
+        with file(outfile, 'w') as keyfileout:
             for key in keys:
-                while True:
-                    keycount += 1
-                    outfile = os.path.join(outpath,u"kindlekey{0:d}.k4a".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)
+                keyfileout.write(key)
+                keyfileout.write("\n")
         return True
     return False
 
 
 def usage(progname):
-    print u"{0} v{1}\nCopyright © 2013-2015 Thom and Apprentice Harper".format(progname,__version__)
-    print u"Decrypts the serial number of Kindle For Android from Android backup or file"
+    print u"Decrypts the serial number(s) of Kindle For Android from Android backup or file"
     print u"Get backup.ab file using adb backup com.amazon.kindle for Android 4.0+."
     print u"Otherwise extract AmazonSecureStorage.xml from /data/data/com.amazon.kindle/shared_prefs/AmazonSecureStorage.xml"
     print u"Or map_data_storage.db from /data/data/com.amazon.kindle/databases/map_data_storage.db"
     print u""
-    print u"Serial number is written to standard output."
     print u"Usage:"
-    print u"    {0:s} [-h] [-b <backup.ab>] [<outpath>]".format(progname)
+    print u"    {0:s} [-h] [-b <backup.ab>] [<outfile.k4a>]".format(progname)
 
 
 def cli_main():
@@ -339,24 +346,28 @@ def cli_main():
 
     if len(args) == 1:
         # save to the specified file or directory
-        outpath = args[0]
-        if not os.path.isabs(outpath):
-           outpath = os.path.join(os.path.dirname(argv[0]),outpath)
-           outpath = os.path.abspath(outpath)
+        outfile = args[0]
+        if not os.path.isabs(outfile):
+           outfile = os.path.join(os.path.dirname(argv[0]),outfile)
+           outfile = os.path.abspath(outfile)
+        if os.path.isdir(outfile):
+           outfile = os.path.join(os.path.dirname(argv[0]),"androidkindlekey.k4a")
     else:
         # save to the same directory as the script
-        outpath = os.path.dirname(argv[0])
+        outfile = os.path.join(os.path.dirname(argv[0]),"androidkindlekey.k4a")
 
     # make sure the outpath is OK
-    outpath = os.path.realpath(os.path.normpath(outpath))
+    outfile = os.path.realpath(os.path.normpath(outfile))
 
     if not os.path.isfile(inpath):
         usage(progname)
         print u"\n{0:s} file not found".format(inpath)
         return 2
 
-    if not getkey(outpath, inpath):
-        print u"Could not retrieve Kindle for Android key."
+    if getkey(outfile, inpath):
+        print u"\nSaved Kindle for Android key to {0}".format(outfile)
+    else:
+        print u"\nCould not retrieve Kindle for Android key."
     return 0
 
 
index dcba6eb7233896d9304f78ded51dd1231e979b33..b3a21a1a44914b8d500d4a731914e3a216daad59 100644 (file)
@@ -34,9 +34,9 @@ from calibre.constants import iswindows, isosx
 from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
 from calibre_plugins.dedrm.__init__ import RESOURCE_NAME as help_file_name
 from calibre_plugins.dedrm.utilities import uStrCmp
-from calibre_plugins.dedrm.androidkindlekey import get_serials
 
 import calibre_plugins.dedrm.prefs as prefs
+import calibre_plugins.dedrm.androidkindlekey as androidkindlekey
 
 class ConfigWidget(QWidget):
     def __init__(self, plugin_path, alfdir):
@@ -54,9 +54,9 @@ class ConfigWidget(QWidget):
         self.tempdedrmprefs['adeptkeys'] = self.dedrmprefs['adeptkeys'].copy()
         self.tempdedrmprefs['ereaderkeys'] = self.dedrmprefs['ereaderkeys'].copy()
         self.tempdedrmprefs['kindlekeys'] = self.dedrmprefs['kindlekeys'].copy()
+        self.tempdedrmprefs['androidkeys'] = self.dedrmprefs['androidkeys'].copy()
         self.tempdedrmprefs['pids'] = list(self.dedrmprefs['pids'])
         self.tempdedrmprefs['serials'] = list(self.dedrmprefs['serials'])
-        self.tempdedrmprefs['androidserials'] = list(self.dedrmprefs['androidserials'])
         self.tempdedrmprefs['adobewineprefix'] = self.dedrmprefs['adobewineprefix']
         self.tempdedrmprefs['kindlewineprefix'] = self.dedrmprefs['kindlewineprefix']
 
@@ -86,9 +86,9 @@ class ConfigWidget(QWidget):
         self.bandn_button.setText(u"Barnes and Noble ebooks")
         self.bandn_button.clicked.connect(self.bandn_keys)
         self.kindle_android_button = QtGui.QPushButton(self)
-        self.kindle_android_button.setToolTip(_(u"Click to manage Kindle for Android serial numbers for Kindle ebooks"))
+        self.kindle_android_button.setToolTip(_(u"Click to manage keys for Kindle for Android ebooks"))
         self.kindle_android_button.setText(u"Kindle for Android ebooks")
-        self.kindle_android_button.clicked.connect(self.kindle_android_serials)
+        self.kindle_android_button.clicked.connect(self.kindle_android)
         self.kindle_serial_button = QtGui.QPushButton(self)
         self.kindle_serial_button.setToolTip(_(u"Click to manage eInk Kindle serial numbers for Kindle ebooks"))
         self.kindle_serial_button.setText(u"eInk Kindle ebooks")
@@ -123,8 +123,8 @@ class ConfigWidget(QWidget):
         d = ManageKeysDialog(self,u"EInk Kindle Serial Number",self.tempdedrmprefs['serials'], AddSerialDialog)
         d.exec_()
         
-    def kindle_android_serials(self):
-        d = ManageKeysDialog(self,u"Kindle for Andoid Serial Number",self.tempdedrmprefs['androidserials'], AddAndroidSerialDialog, 'ab')
+    def kindle_android(self):
+        d = ManageKeysDialog(self,u"Kindle for Android Keys File",self.tempdedrmprefs['androidkeys'], AddAndroidDialog, 'k4a')
         d.exec_()
 
     def kindle_keys(self):
@@ -173,9 +173,9 @@ class ConfigWidget(QWidget):
         self.dedrmprefs.set('adeptkeys', self.tempdedrmprefs['adeptkeys'])
         self.dedrmprefs.set('ereaderkeys', self.tempdedrmprefs['ereaderkeys'])
         self.dedrmprefs.set('kindlekeys', self.tempdedrmprefs['kindlekeys'])
+        self.dedrmprefs.set('androidkeys', self.tempdedrmprefs['androidkeys'])
         self.dedrmprefs.set('pids', self.tempdedrmprefs['pids'])
         self.dedrmprefs.set('serials', self.tempdedrmprefs['serials'])
-        self.dedrmprefs.set('androidserials', self.tempdedrmprefs['androidserials'])
         self.dedrmprefs.set('adobewineprefix', self.tempdedrmprefs['adobewineprefix'])
         self.dedrmprefs.set('kindlewineprefix', self.tempdedrmprefs['kindlewineprefix'])
         self.dedrmprefs.set('configured', True)
@@ -200,7 +200,7 @@ class ManageKeysDialog(QDialog):
         self.import_key = (keyfile_ext != u"")
         self.binary_file = (keyfile_ext == u"der")
         self.json_file = (keyfile_ext == u"k4i")
-        self.android_file = (keyfile_ext == u"ab")
+        self.android_file = (keyfile_ext == u"k4a")
         self.wineprefix = wineprefix
 
         self.setWindowTitle("{0} {1}: Manage {2}s".format(PLUGIN_NAME, PLUGIN_VERSION, self.key_type_name))
@@ -232,8 +232,8 @@ class ManageKeysDialog(QDialog):
         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.setToolTip(u"Create new {0}".format(self.key_type_name))
         self._add_key_button.clicked.connect(self.add_key)
         button_layout.addWidget(self._add_key_button)
 
@@ -383,42 +383,34 @@ class ManageKeysDialog(QDialog):
             for filename in files:
                 fpath = os.path.join(config_dir, filename)
                 filename = os.path.basename(filename)
-                if type(self.plugin_keys) != dict:
-                    # must be the new Kindle for Android section
-                    print u"Getting keys from "+fpath
-                    new_keys = get_serials(fpath)
-                    for key in new_keys:
-                        if key in self.plugin_keys:
-                            skipped += 1
-                        else:
-                            counter += 1
-                            self.plugin_keys.append(key)
-                else:
-                    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
+                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)
+                elif self.android_file:
+                    # convert to list of the keys in the string
+                    new_key_value = new_key_value.splitlines()
+                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:
@@ -453,6 +445,10 @@ class ManageKeysDialog(QDialog):
                     fname.write(self.plugin_keys[keyname].decode('hex'))
                 elif self.json_file:
                     fname.write(json.dumps(self.plugin_keys[keyname]))
+                elif self.android_file:
+                    for key in self.plugin_keys[keyname]:
+                        fname.write(key)
+                        fname.write("\n")
                 else:
                     fname.write(self.plugin_keys[keyname])
 
@@ -539,9 +535,6 @@ class AddBandNKeyDialog(QDialog):
                                 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)
@@ -626,9 +619,6 @@ class AddEReaderDialog(QDialog):
         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)
@@ -727,9 +717,7 @@ class AddAdeptDialog(QDialog):
             self.key_ledit = QLineEdit(u"default_key", self)
             self.key_ledit.setToolTip(u"<p>Enter an identifying name for the current default Adobe Digital Editions key.")
             key_group.addWidget(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)
@@ -800,15 +788,14 @@ class AddKindleDialog(QDialog):
             self.key_ledit = QLineEdit(u"default_key", self)
             self.key_ledit.setToolTip(u"<p>Enter an identifying name for the current default Kindle for Mac/PC key.")
             key_group.addWidget(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
+            
+            # if no default, both buttons do the same
             self.button_box.accepted.connect(self.reject)
 
         self.button_box.rejected.connect(self.reject)
@@ -854,9 +841,6 @@ class AddSerialDialog(QDialog):
         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)
@@ -883,51 +867,89 @@ class AddSerialDialog(QDialog):
         QDialog.accept(self)
 
 
-class AddAndroidSerialDialog(QDialog):
+class AddAndroidDialog(QDialog):
     def __init__(self, parent=None,):
+
         QDialog.__init__(self, parent)
         self.parent = parent
-        self.setWindowTitle(u"{0} {1}: Add New Kindle for Android Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION))
+        self.setWindowTitle(u"{0} {1}: Add new Kindle for Android Key".format(PLUGIN_NAME, PLUGIN_VERSION))
         layout = QVBoxLayout(self)
         self.setLayout(layout)
+        self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
 
         data_group_box = QGroupBox(u"", self)
         layout.addWidget(data_group_box)
         data_group_box_layout = QVBoxLayout()
         data_group_box.setLayout(data_group_box_layout)
 
+        file_group = QHBoxLayout()
+        data_group_box_layout.addLayout(file_group)
+        add_btn = QPushButton(u"Choose Backup File", self)
+        add_btn.setToolTip(u"Import Kindle for Android backup file.")
+        add_btn.clicked.connect(self.get_android_file)
+        file_group.addWidget(add_btn)
+        self.selected_file_name = QLabel(u"",self)
+        self.selected_file_name.setAlignment(Qt.AlignHCenter)
+        file_group.addWidget(self.selected_file_name)
+        
         key_group = QHBoxLayout()
         data_group_box_layout.addLayout(key_group)
-        key_group.addWidget(QLabel(u"Kindle for Android Serial Number:", self))
-        self.key_ledit = QLineEdit("", self)
-        self.key_ledit.setToolTip(u"Enter a Kindle for ANdroid serial number. These can be found using the androidkindlekey.py script.")
+        key_group.addWidget(QLabel(u"Unique Key Name:", self))
+        self.key_ledit = QLineEdit(u"", self)
+        self.key_ledit.setToolTip(u"<p>Enter an identifying name for the Android for Kindle key.")
         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)
+        #key_label = QLabel(_(''), self)
+        #key_label.setAlignment(Qt.AlignHCenter)
+        #data_group_box_layout.addWidget(key_label)
+        
         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()).strip()
 
+    @property
+    def file_name(self):
+        return unicode(self.selected_file_name.text()).strip()
+
     @property
     def key_value(self):
-        return unicode(self.key_ledit.text()).strip()
+        return self.serials_from_file
+        
+    def get_android_file(self):
+        unique_dlg_name = PLUGIN_NAME + u"Import Kindle for Android backup file" #takes care of automatically remembering last directory
+        caption = u"Select Kindle for Android backup file to add"
+        filters = [(u"Kindle for Android backup files", ['db','ab','xml'])]
+        files = choose_files(self, unique_dlg_name, caption, filters, all_files=False)
+        self.serials_from_file = []
+        file_name = u""
+        if files:
+            # find the first selected file that yields some serial numbers
+            for filename in files:
+                fpath = os.path.join(config_dir, filename)
+                self.filename = os.path.basename(filename)
+                file_serials = androidkindlekey.get_serials(fpath)
+                if len(file_serials)>0:
+                    file_name = os.path.basename(self.filename)
+                    self.serials_from_file.extend(file_serials)
+        self.selected_file_name.setText(file_name)
+    
 
     def accept(self):
+        if len(self.file_name) == 0 or len(self.key_value) == 0:
+            errmsg = u"Please choose a Kindle for Android backup file."
+            return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
         if len(self.key_name) == 0 or self.key_name.isspace():
-            errmsg = u"Please enter a Kindle for Android Serial Number or click Cancel in the dialog."
+            errmsg = u"Please enter a key name."
+            return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
+        if len(self.key_name) < 4:
+            errmsg = u"Key name must be at <i>least</i> 4 characters long!"
             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)
@@ -947,9 +969,6 @@ class AddPIDDialog(QDialog):
         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)
index 02495e716a42dbfff9f521b66ba0907963395041..c91e6f3522ec00c2d64ebf4269082f6d31e72e0d 100644 (file)
@@ -140,9 +140,9 @@ def fetch_key(email, password):
             response = urllib2.urlopen(req)
             the_page = response.read()
             #print the_page            
-        found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
-    except:
-        found = ''
+            found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
+        except:
+            found = ''
 
     return found
 
index f4dd3fba802b2656d0d60cef7ffc9aabaa3536e2..4ce5aab7b550a11aec305fc3a08658696558c8f6 100644 (file)
@@ -3,15 +3,15 @@
 
 from __future__ import with_statement
 
-# ignobleepub.pyw, version 3.6
-# Copyright © 2009-2012 by DiapDealer et al.
+# k4mobidedrm.py, version 5.3
+# Copyright © 2009-2015 by ApprenticeHarper et al.
 
-# engine to remove drm from Kindle for Mac and Kindle for PC books
+# engine to remove drm from Kindle and Mobipocket ebooks
 # for personal use for archiving and converting your ebooks
 
 # PLEASE DO NOT PIRATE EBOOKS!
 
-# We want all authors and publishers, and eBook stores to live
+# We want all authors and publishers, and ebook stores to live
 # long and prosperous lives but at the same time  we just want to
 # be able to read OUR books on whatever device we want and to keep
 # readable for a long, long time
@@ -55,8 +55,9 @@ from __future__ import with_statement
 #      - tweaked GetDecryptedBook interface to leave passed parameters unchanged
 #  5.1 - moved unicode_argv call inside main for Windows DeDRM compatibility
 #  5.2 - Fixed error in command line processing of unicode arguments
+#  5.3 - Changed Android support to allow passing of backup .ab files
 
-__version__ = '5.2'
+__version__ = '5.3'
 
 
 import sys, os, re
@@ -187,7 +188,7 @@ def unescape(text):
         return text # leave as is
     return re.sub(u"&#?\w+;", fixup, text)
 
-def GetDecryptedBook(infile, kDatabases, serials, pids, starttime = time.time()):
+def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime = time.time()):
     # handle the obvious cases at the beginning
     if not os.path.isfile(infile):
         raise DrmException(u"Input file does not exist.")
@@ -207,9 +208,14 @@ def GetDecryptedBook(infile, kDatabases, serials, pids, starttime = time.time())
 
     # copy list of pids
     totalpids = list(pids)
-    # extend PID list with book-specific PIDs
+    # extend list of serials with serials from android databases
+    for aFile in androidFiles:
+        serials.extend(androidkindlekey.get_serials(aFile))
+    # extend PID list with book-specific PIDs from seriala and kDatabases
     md1, md2 = mb.getPIDMetaInfo()
     totalpids.extend(kgenpids.getPidList(md1, md2, serials, kDatabases))
+    # remove any duplicates
+    totalpid = list(set(totalpids))
     print u"Found {1:d} keys to try after {0:.1f} seconds".format(time.time()-starttime, len(totalpids))
 
     try:
@@ -223,7 +229,7 @@ def GetDecryptedBook(infile, kDatabases, serials, pids, starttime = time.time())
 
 
 # kDatabaseFiles is a list of files created by kindlekey
-def decryptBook(infile, outdir, kDatabaseFiles, serials, pids):
+def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
     starttime = time.time()
     kDatabases = []
     for dbfile in kDatabaseFiles:
@@ -239,7 +245,7 @@ def decryptBook(infile, outdir, kDatabaseFiles, serials, pids):
 
 
     try:
-        book = GetDecryptedBook(infile, kDatabases, serials, pids, starttime)
+        book = GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime)
     except Exception, e:
         print u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime)
         traceback.print_exc()
@@ -254,7 +260,7 @@ def decryptBook(infile, outdir, kDatabaseFiles, serials, pids):
 
     # avoid excessively long file names
     if len(outfilename)>150:
-        outfilename = outfilename[:150]
+        outfilename = outfilename[:99]+"--"+outfilename[-49:]
 
     outfilename = outfilename+u"_nodrm"
     outfile = os.path.join(outdir, outfilename + book.getBookExtension())
@@ -275,7 +281,7 @@ def decryptBook(infile, outdir, kDatabaseFiles, serials, pids):
 def usage(progname):
     print u"Removes DRM protection from Mobipocket, Amazon KF8, Amazon Print Replica and Amazon Topaz ebooks"
     print u"Usage:"
-    print u"    {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] [ -a <AmazonSecureStorage.xml> ] <infile> <outdir>".format(progname)
+    print u"    {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] [ -a <AmazonSecureStorage.xml|backup.ab> ] <infile> <outdir>".format(progname)
 
 #
 # Main
@@ -298,6 +304,7 @@ def cli_main():
     infile = args[0]
     outdir = args[1]
     kDatabaseFiles = []
+    androidFiles = []
     serials = []
     pids = []
 
@@ -317,12 +324,12 @@ def cli_main():
         if o == '-a':
             if a == None:
                 continue
-            serials.extend(androidkindlekey.get_serials(a))
+            androidFiles.apprend(a)
 
     # try with built in Kindle Info files if not on Linux
     k4 = not sys.platform.startswith('linux')
 
-    return decryptBook(infile, outdir, kDatabaseFiles, serials, pids)
+    return decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids)
 
 
 if __name__ == '__main__':
index f0f494cfa8db189f9e267b90777247aba837c60d..c1bfcb9a2936bc17add82eded8621df859a5161f 100644 (file)
@@ -23,9 +23,9 @@ class DeDRM_Prefs():
         self.dedrmprefs.defaults['adeptkeys'] = {}
         self.dedrmprefs.defaults['ereaderkeys'] = {}
         self.dedrmprefs.defaults['kindlekeys'] = {}
+        self.dedrmprefs.defaults['androidkeys'] = {}
         self.dedrmprefs.defaults['pids'] = []
         self.dedrmprefs.defaults['serials'] = []
-        self.dedrmprefs.defaults['androidserials'] = []
         self.dedrmprefs.defaults['adobewineprefix'] = ""
         self.dedrmprefs.defaults['kindlewineprefix'] = ""
 
@@ -41,12 +41,12 @@ class DeDRM_Prefs():
             self.dedrmprefs['ereaderkeys'] = {}
         if self.dedrmprefs['kindlekeys'] == {}:
             self.dedrmprefs['kindlekeys'] = {}
+        if self.dedrmprefs['androidkeys'] == {}:
+            self.dedrmprefs['androidkeys'] = {}
         if self.dedrmprefs['pids'] == []:
             self.dedrmprefs['pids'] = []
         if self.dedrmprefs['serials'] == []:
             self.dedrmprefs['serials'] = []
-        if self.dedrmprefs['androidserials'] == []:
-            self.dedrmprefs['androidserials'] = []
 
     def __getitem__(self,kind = None):
         if kind is not None:
index 1212f51f9447d8346cd8409a5a7dbe0c2384fdbf..ec86b130c898b190b59527eed73378c72c50494b 100644 (file)
@@ -158,24 +158,38 @@ def decryptk4mobi(infile, outdir, rscpath):
         serialstr = serialstr.strip()
         if serialstr != '':
             serialnums = serialstr.split(',')
+    kDatabaseFiles = []
     files = os.listdir(rscpath)
-    filefilter = re.compile("\.k4a$", re.IGNORECASE)
+    filefilter = re.compile("\.k4i$", re.IGNORECASE)
     files = filter(filefilter.search, files)
     if files:
         for filename in files:
             dpath = os.path.join(rscpath,filename)
-            androidserial = open(keypath,'r').read()
-            serialnums.append(androidserial)
-    kDatabaseFiles = []
+            kDatabaseFiles.append(dpath)
+    androidFiles = []
     files = os.listdir(rscpath)
-    filefilter = re.compile("\.k4i$", re.IGNORECASE)
+    filefilter = re.compile("\.ab$", re.IGNORECASE)
     files = filter(filefilter.search, files)
     if files:
         for filename in files:
             dpath = os.path.join(rscpath,filename)
-            kDatabaseFiles.append(dpath)
+            androidFiles.append(dpath)
+    files = os.listdir(rscpath)
+    filefilter = re.compile("\.db$", re.IGNORECASE)
+    files = filter(filefilter.search, files)
+    if files:
+        for filename in files:
+            dpath = os.path.join(rscpath,filename)
+            androidFiles.append(dpath)
+    files = os.listdir(rscpath)
+    filefilter = re.compile("\.xml$", re.IGNORECASE)
+    files = filter(filefilter.search, files)
+    if files:
+        for filename in files:
+            dpath = os.path.join(rscpath,filename)
+            androidFiles.append(dpath)
     try:
-        rv = k4mobidedrm.decryptBook(infile, outdir, kDatabaseFiles, serialnums, pidnums)
+        rv = k4mobidedrm.decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serialnums, pidnums)
     except Exception, e:
         errlog += traceback.format_exc()
         errlog += str(e)
index d268a339febe2fdd1e28dbf3a5ea55ce326f40f8..ce87f3bf5761ab7d7b7eb076befe71b881214edf 100644 (file)
Binary files a/DeDRM_calibre_plugin/DeDRM_plugin.zip and b/DeDRM_calibre_plugin/DeDRM_plugin.zip differ
index 82329fe5615151ba3f0acc68ccc083044238afb6..5919f5b0c88496dca2f2686832f54eec409d5b30 100644 (file)
@@ -90,7 +90,7 @@ class DeDRM(FileTypePlugin):
     name                    = PLUGIN_NAME
     description             = u"Removes DRM from Amazon Kindle, Adobe Adept (including Kobo), Barnes & Noble, Mobipocket and eReader ebooks. Credit given to i♥cabbages and The Dark Reverser for the original stand-alone scripts."
     supported_platforms     = ['linux', 'osx', 'windows']
-    author                  = u"DiapDealer, Apprentice Alf, The Dark Reverser and i♥cabbages"
+    author                  = u"Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
     version                 = PLUGIN_VERSION_TUPLE
     minimum_calibre_version = (0, 7, 55)  # Compiled python libraries cannot be imported in earlier versions.
     file_types              = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','tpz'])
@@ -482,11 +482,15 @@ class DeDRM(FileTypePlugin):
         dedrmprefs = prefs.DeDRM_Prefs()
         pids = dedrmprefs['pids']
         serials = dedrmprefs['serials']
-        serials.extend(dedrmprefs['androidserials'])
+        for android_serials_list in dedrmprefs['androidkeys'].values():
+            #print android_serials_list
+            serials.extend(android_serials_list)
+        #print serials
+        androidFiles = []
         kindleDatabases = dedrmprefs['kindlekeys'].items()
 
         try:
-            book = k4mobidedrm.GetDecryptedBook(path_to_ebook,kindleDatabases,serials,pids,self.starttime)
+            book = k4mobidedrm.GetDecryptedBook(path_to_ebook,kindleDatabases,androidFiles,serials,pids,self.starttime)
         except Exception, e:
             decoded = False
             # perhaps we need to get a new default Kindle for Mac/PC key
@@ -558,6 +562,7 @@ class DeDRM(FileTypePlugin):
             # Decryption was successful return the modified PersistentTemporary
             # file to Calibre's import process.
             if  result == 0:
+                print u"{0} v{1}: Successfully decrypted with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime)
                 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_masked,time.time()-self.starttime)
@@ -576,7 +581,7 @@ class DeDRM(FileTypePlugin):
         self.starttime = time.time()
 
         booktype = os.path.splitext(path_to_ebook)[1].lower()[1:]
-        if booktype in ['prc','mobi','azw','azw1','azw3','azw4','tpz']:
+        if booktype in ['prc','mobi','pobi','azw','azw1','azw3','azw4','tpz']:
             # Kindle/Mobipocket
             decrypted_ebook = self.KindleMobiDecrypt(path_to_ebook)
         elif booktype == 'pdb':
@@ -593,7 +598,7 @@ class DeDRM(FileTypePlugin):
         else:
             print u"Unknown booktype {0}. Passing back to calibre unchanged".format(booktype)
             return path_to_ebook
-        print u"{0} v{1}: Successfully decrypted book after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
+        print u"{0} v{1}: Finished after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
         return decrypted_ebook
 
     def is_customizable(self):
index 7a2571070e99fd37274f5b9461bf54bd1109633d..2c539eea40c4159d6b9aeac2cea4186c037824c1 100644 (file)
@@ -14,14 +14,15 @@ from __future__ import with_statement
 #  1.2   - Changed to be callable from AppleScript by returning only serial number
 #        - and changed name to androidkindlekey.py
 #        - and added in unicode command line support
-#  1.3   - added in TkInter interface, output to a file and attempt to get backup from a connected android device.
+#  1.3   - added in TkInter interface, output to a file
+#  1.4   - Fix some problems identified by Aldo Bleeker
 
 """
 Retrieve Kindle for Android Serial Number.
 """
 
 __license__ = 'GPL v3'
-__version__ = '1.3'
+__version__ = '1.4'
 
 import os
 import sys
@@ -199,13 +200,16 @@ def get_serials1(path=STORAGE1):
         return []
 
     serials = []
+    if dsnid:
+        serials.append(dsnid)
     for token in tokens:
         if token:
             serials.append('%s%s' % (dsnid, token))
+            serials.append(token)
     return serials
 
 def get_serials2(path=STORAGE2):
-    ''' get serials from android's shared preference xml '''
+    ''' get serials from android's sql database '''
     if not os.path.isfile(path):
         return []
 
@@ -213,14 +217,32 @@ def get_serials2(path=STORAGE2):
     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()]
+    userdata_keys = cursor.fetchall()
+    dsns = []
+    for userdata_row in userdata_keys:
+        if userdata_row:
+            userdata_utf8 = userdata_row[0].encode('utf8')
+            if len(userdata_utf8) > 0:
+                dsns.append(userdata_utf8)
+    dsns = list(set(dsns))
 
     cursor.execute('''select userdata_value from userdata where userdata_key like '%/%kindle.account.tokens%' ''')
-    tokens = [x[0].encode('utf8') for x in cursor.fetchall()]
+    userdata_keys = cursor.fetchall()
+    tokens = []
+    for userdata_row in userdata_keys:
+        if userdata_row:
+            userdata_utf8 = userdata_row[0].encode('utf8')
+            if len(userdata_utf8) > 0:
+                tokens.append(userdata_utf8)
+    tokens = list(set(tokens))
     serials = []
     for x in dsns:
+        serials.append(x)
         for y in tokens:
             serials.append('%s%s' % (x, y))
+    for y in tokens:
+        serials.append(y)
     return serials
 
 def get_serials(path=STORAGE):
@@ -269,46 +291,31 @@ def get_serials(path=STORAGE):
             write_path = os.path.abspath(write.name)
             serials.extend(get_serials2(write_path))
             os.remove(write_path)
-
-    return serials
+    return list(set(serials))
 
 __all__ = [ 'get_serials', 'getkey']
 
-# interface for Python DeDRM
-# returns single key or multiple keys, depending on path or file passed in
-def getkey(outpath, inpath):
+# procedure for CLI and GUI interfaces
+# returns single or multiple keys (one per line) in the specified file
+def getkey(outfile, inpath):
     keys = get_serials(inpath)
     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
+        with file(outfile, 'w') as keyfileout:
             for key in keys:
-                while True:
-                    keycount += 1
-                    outfile = os.path.join(outpath,u"kindlekey{0:d}.k4a".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)
+                keyfileout.write(key)
+                keyfileout.write("\n")
         return True
     return False
 
 
 def usage(progname):
-    print u"{0} v{1}\nCopyright © 2013-2015 Thom and Apprentice Harper".format(progname,__version__)
-    print u"Decrypts the serial number of Kindle For Android from Android backup or file"
+    print u"Decrypts the serial number(s) of Kindle For Android from Android backup or file"
     print u"Get backup.ab file using adb backup com.amazon.kindle for Android 4.0+."
     print u"Otherwise extract AmazonSecureStorage.xml from /data/data/com.amazon.kindle/shared_prefs/AmazonSecureStorage.xml"
     print u"Or map_data_storage.db from /data/data/com.amazon.kindle/databases/map_data_storage.db"
     print u""
-    print u"Serial number is written to standard output."
     print u"Usage:"
-    print u"    {0:s} [-h] [-b <backup.ab>] [<outpath>]".format(progname)
+    print u"    {0:s} [-h] [-b <backup.ab>] [<outfile.k4a>]".format(progname)
 
 
 def cli_main():
@@ -339,24 +346,28 @@ def cli_main():
 
     if len(args) == 1:
         # save to the specified file or directory
-        outpath = args[0]
-        if not os.path.isabs(outpath):
-           outpath = os.path.join(os.path.dirname(argv[0]),outpath)
-           outpath = os.path.abspath(outpath)
+        outfile = args[0]
+        if not os.path.isabs(outfile):
+           outfile = os.path.join(os.path.dirname(argv[0]),outfile)
+           outfile = os.path.abspath(outfile)
+        if os.path.isdir(outfile):
+           outfile = os.path.join(os.path.dirname(argv[0]),"androidkindlekey.k4a")
     else:
         # save to the same directory as the script
-        outpath = os.path.dirname(argv[0])
+        outfile = os.path.join(os.path.dirname(argv[0]),"androidkindlekey.k4a")
 
     # make sure the outpath is OK
-    outpath = os.path.realpath(os.path.normpath(outpath))
+    outfile = os.path.realpath(os.path.normpath(outfile))
 
     if not os.path.isfile(inpath):
         usage(progname)
         print u"\n{0:s} file not found".format(inpath)
         return 2
 
-    if not getkey(outpath, inpath):
-        print u"Could not retrieve Kindle for Android key."
+    if getkey(outfile, inpath):
+        print u"\nSaved Kindle for Android key to {0}".format(outfile)
+    else:
+        print u"\nCould not retrieve Kindle for Android key."
     return 0
 
 
index dcba6eb7233896d9304f78ded51dd1231e979b33..b3a21a1a44914b8d500d4a731914e3a216daad59 100644 (file)
@@ -34,9 +34,9 @@ from calibre.constants import iswindows, isosx
 from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
 from calibre_plugins.dedrm.__init__ import RESOURCE_NAME as help_file_name
 from calibre_plugins.dedrm.utilities import uStrCmp
-from calibre_plugins.dedrm.androidkindlekey import get_serials
 
 import calibre_plugins.dedrm.prefs as prefs
+import calibre_plugins.dedrm.androidkindlekey as androidkindlekey
 
 class ConfigWidget(QWidget):
     def __init__(self, plugin_path, alfdir):
@@ -54,9 +54,9 @@ class ConfigWidget(QWidget):
         self.tempdedrmprefs['adeptkeys'] = self.dedrmprefs['adeptkeys'].copy()
         self.tempdedrmprefs['ereaderkeys'] = self.dedrmprefs['ereaderkeys'].copy()
         self.tempdedrmprefs['kindlekeys'] = self.dedrmprefs['kindlekeys'].copy()
+        self.tempdedrmprefs['androidkeys'] = self.dedrmprefs['androidkeys'].copy()
         self.tempdedrmprefs['pids'] = list(self.dedrmprefs['pids'])
         self.tempdedrmprefs['serials'] = list(self.dedrmprefs['serials'])
-        self.tempdedrmprefs['androidserials'] = list(self.dedrmprefs['androidserials'])
         self.tempdedrmprefs['adobewineprefix'] = self.dedrmprefs['adobewineprefix']
         self.tempdedrmprefs['kindlewineprefix'] = self.dedrmprefs['kindlewineprefix']
 
@@ -86,9 +86,9 @@ class ConfigWidget(QWidget):
         self.bandn_button.setText(u"Barnes and Noble ebooks")
         self.bandn_button.clicked.connect(self.bandn_keys)
         self.kindle_android_button = QtGui.QPushButton(self)
-        self.kindle_android_button.setToolTip(_(u"Click to manage Kindle for Android serial numbers for Kindle ebooks"))
+        self.kindle_android_button.setToolTip(_(u"Click to manage keys for Kindle for Android ebooks"))
         self.kindle_android_button.setText(u"Kindle for Android ebooks")
-        self.kindle_android_button.clicked.connect(self.kindle_android_serials)
+        self.kindle_android_button.clicked.connect(self.kindle_android)
         self.kindle_serial_button = QtGui.QPushButton(self)
         self.kindle_serial_button.setToolTip(_(u"Click to manage eInk Kindle serial numbers for Kindle ebooks"))
         self.kindle_serial_button.setText(u"eInk Kindle ebooks")
@@ -123,8 +123,8 @@ class ConfigWidget(QWidget):
         d = ManageKeysDialog(self,u"EInk Kindle Serial Number",self.tempdedrmprefs['serials'], AddSerialDialog)
         d.exec_()
         
-    def kindle_android_serials(self):
-        d = ManageKeysDialog(self,u"Kindle for Andoid Serial Number",self.tempdedrmprefs['androidserials'], AddAndroidSerialDialog, 'ab')
+    def kindle_android(self):
+        d = ManageKeysDialog(self,u"Kindle for Android Keys File",self.tempdedrmprefs['androidkeys'], AddAndroidDialog, 'k4a')
         d.exec_()
 
     def kindle_keys(self):
@@ -173,9 +173,9 @@ class ConfigWidget(QWidget):
         self.dedrmprefs.set('adeptkeys', self.tempdedrmprefs['adeptkeys'])
         self.dedrmprefs.set('ereaderkeys', self.tempdedrmprefs['ereaderkeys'])
         self.dedrmprefs.set('kindlekeys', self.tempdedrmprefs['kindlekeys'])
+        self.dedrmprefs.set('androidkeys', self.tempdedrmprefs['androidkeys'])
         self.dedrmprefs.set('pids', self.tempdedrmprefs['pids'])
         self.dedrmprefs.set('serials', self.tempdedrmprefs['serials'])
-        self.dedrmprefs.set('androidserials', self.tempdedrmprefs['androidserials'])
         self.dedrmprefs.set('adobewineprefix', self.tempdedrmprefs['adobewineprefix'])
         self.dedrmprefs.set('kindlewineprefix', self.tempdedrmprefs['kindlewineprefix'])
         self.dedrmprefs.set('configured', True)
@@ -200,7 +200,7 @@ class ManageKeysDialog(QDialog):
         self.import_key = (keyfile_ext != u"")
         self.binary_file = (keyfile_ext == u"der")
         self.json_file = (keyfile_ext == u"k4i")
-        self.android_file = (keyfile_ext == u"ab")
+        self.android_file = (keyfile_ext == u"k4a")
         self.wineprefix = wineprefix
 
         self.setWindowTitle("{0} {1}: Manage {2}s".format(PLUGIN_NAME, PLUGIN_VERSION, self.key_type_name))
@@ -232,8 +232,8 @@ class ManageKeysDialog(QDialog):
         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.setToolTip(u"Create new {0}".format(self.key_type_name))
         self._add_key_button.clicked.connect(self.add_key)
         button_layout.addWidget(self._add_key_button)
 
@@ -383,42 +383,34 @@ class ManageKeysDialog(QDialog):
             for filename in files:
                 fpath = os.path.join(config_dir, filename)
                 filename = os.path.basename(filename)
-                if type(self.plugin_keys) != dict:
-                    # must be the new Kindle for Android section
-                    print u"Getting keys from "+fpath
-                    new_keys = get_serials(fpath)
-                    for key in new_keys:
-                        if key in self.plugin_keys:
-                            skipped += 1
-                        else:
-                            counter += 1
-                            self.plugin_keys.append(key)
-                else:
-                    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
+                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)
+                elif self.android_file:
+                    # convert to list of the keys in the string
+                    new_key_value = new_key_value.splitlines()
+                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:
@@ -453,6 +445,10 @@ class ManageKeysDialog(QDialog):
                     fname.write(self.plugin_keys[keyname].decode('hex'))
                 elif self.json_file:
                     fname.write(json.dumps(self.plugin_keys[keyname]))
+                elif self.android_file:
+                    for key in self.plugin_keys[keyname]:
+                        fname.write(key)
+                        fname.write("\n")
                 else:
                     fname.write(self.plugin_keys[keyname])
 
@@ -539,9 +535,6 @@ class AddBandNKeyDialog(QDialog):
                                 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)
@@ -626,9 +619,6 @@ class AddEReaderDialog(QDialog):
         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)
@@ -727,9 +717,7 @@ class AddAdeptDialog(QDialog):
             self.key_ledit = QLineEdit(u"default_key", self)
             self.key_ledit.setToolTip(u"<p>Enter an identifying name for the current default Adobe Digital Editions key.")
             key_group.addWidget(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)
@@ -800,15 +788,14 @@ class AddKindleDialog(QDialog):
             self.key_ledit = QLineEdit(u"default_key", self)
             self.key_ledit.setToolTip(u"<p>Enter an identifying name for the current default Kindle for Mac/PC key.")
             key_group.addWidget(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
+            
+            # if no default, both buttons do the same
             self.button_box.accepted.connect(self.reject)
 
         self.button_box.rejected.connect(self.reject)
@@ -854,9 +841,6 @@ class AddSerialDialog(QDialog):
         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)
@@ -883,51 +867,89 @@ class AddSerialDialog(QDialog):
         QDialog.accept(self)
 
 
-class AddAndroidSerialDialog(QDialog):
+class AddAndroidDialog(QDialog):
     def __init__(self, parent=None,):
+
         QDialog.__init__(self, parent)
         self.parent = parent
-        self.setWindowTitle(u"{0} {1}: Add New Kindle for Android Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION))
+        self.setWindowTitle(u"{0} {1}: Add new Kindle for Android Key".format(PLUGIN_NAME, PLUGIN_VERSION))
         layout = QVBoxLayout(self)
         self.setLayout(layout)
+        self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
 
         data_group_box = QGroupBox(u"", self)
         layout.addWidget(data_group_box)
         data_group_box_layout = QVBoxLayout()
         data_group_box.setLayout(data_group_box_layout)
 
+        file_group = QHBoxLayout()
+        data_group_box_layout.addLayout(file_group)
+        add_btn = QPushButton(u"Choose Backup File", self)
+        add_btn.setToolTip(u"Import Kindle for Android backup file.")
+        add_btn.clicked.connect(self.get_android_file)
+        file_group.addWidget(add_btn)
+        self.selected_file_name = QLabel(u"",self)
+        self.selected_file_name.setAlignment(Qt.AlignHCenter)
+        file_group.addWidget(self.selected_file_name)
+        
         key_group = QHBoxLayout()
         data_group_box_layout.addLayout(key_group)
-        key_group.addWidget(QLabel(u"Kindle for Android Serial Number:", self))
-        self.key_ledit = QLineEdit("", self)
-        self.key_ledit.setToolTip(u"Enter a Kindle for ANdroid serial number. These can be found using the androidkindlekey.py script.")
+        key_group.addWidget(QLabel(u"Unique Key Name:", self))
+        self.key_ledit = QLineEdit(u"", self)
+        self.key_ledit.setToolTip(u"<p>Enter an identifying name for the Android for Kindle key.")
         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)
+        #key_label = QLabel(_(''), self)
+        #key_label.setAlignment(Qt.AlignHCenter)
+        #data_group_box_layout.addWidget(key_label)
+        
         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()).strip()
 
+    @property
+    def file_name(self):
+        return unicode(self.selected_file_name.text()).strip()
+
     @property
     def key_value(self):
-        return unicode(self.key_ledit.text()).strip()
+        return self.serials_from_file
+        
+    def get_android_file(self):
+        unique_dlg_name = PLUGIN_NAME + u"Import Kindle for Android backup file" #takes care of automatically remembering last directory
+        caption = u"Select Kindle for Android backup file to add"
+        filters = [(u"Kindle for Android backup files", ['db','ab','xml'])]
+        files = choose_files(self, unique_dlg_name, caption, filters, all_files=False)
+        self.serials_from_file = []
+        file_name = u""
+        if files:
+            # find the first selected file that yields some serial numbers
+            for filename in files:
+                fpath = os.path.join(config_dir, filename)
+                self.filename = os.path.basename(filename)
+                file_serials = androidkindlekey.get_serials(fpath)
+                if len(file_serials)>0:
+                    file_name = os.path.basename(self.filename)
+                    self.serials_from_file.extend(file_serials)
+        self.selected_file_name.setText(file_name)
+    
 
     def accept(self):
+        if len(self.file_name) == 0 or len(self.key_value) == 0:
+            errmsg = u"Please choose a Kindle for Android backup file."
+            return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
         if len(self.key_name) == 0 or self.key_name.isspace():
-            errmsg = u"Please enter a Kindle for Android Serial Number or click Cancel in the dialog."
+            errmsg = u"Please enter a key name."
+            return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
+        if len(self.key_name) < 4:
+            errmsg = u"Key name must be at <i>least</i> 4 characters long!"
             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)
@@ -947,9 +969,6 @@ class AddPIDDialog(QDialog):
         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)
index f4dd3fba802b2656d0d60cef7ffc9aabaa3536e2..4ce5aab7b550a11aec305fc3a08658696558c8f6 100644 (file)
@@ -3,15 +3,15 @@
 
 from __future__ import with_statement
 
-# ignobleepub.pyw, version 3.6
-# Copyright © 2009-2012 by DiapDealer et al.
+# k4mobidedrm.py, version 5.3
+# Copyright © 2009-2015 by ApprenticeHarper et al.
 
-# engine to remove drm from Kindle for Mac and Kindle for PC books
+# engine to remove drm from Kindle and Mobipocket ebooks
 # for personal use for archiving and converting your ebooks
 
 # PLEASE DO NOT PIRATE EBOOKS!
 
-# We want all authors and publishers, and eBook stores to live
+# We want all authors and publishers, and ebook stores to live
 # long and prosperous lives but at the same time  we just want to
 # be able to read OUR books on whatever device we want and to keep
 # readable for a long, long time
@@ -55,8 +55,9 @@ from __future__ import with_statement
 #      - tweaked GetDecryptedBook interface to leave passed parameters unchanged
 #  5.1 - moved unicode_argv call inside main for Windows DeDRM compatibility
 #  5.2 - Fixed error in command line processing of unicode arguments
+#  5.3 - Changed Android support to allow passing of backup .ab files
 
-__version__ = '5.2'
+__version__ = '5.3'
 
 
 import sys, os, re
@@ -187,7 +188,7 @@ def unescape(text):
         return text # leave as is
     return re.sub(u"&#?\w+;", fixup, text)
 
-def GetDecryptedBook(infile, kDatabases, serials, pids, starttime = time.time()):
+def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime = time.time()):
     # handle the obvious cases at the beginning
     if not os.path.isfile(infile):
         raise DrmException(u"Input file does not exist.")
@@ -207,9 +208,14 @@ def GetDecryptedBook(infile, kDatabases, serials, pids, starttime = time.time())
 
     # copy list of pids
     totalpids = list(pids)
-    # extend PID list with book-specific PIDs
+    # extend list of serials with serials from android databases
+    for aFile in androidFiles:
+        serials.extend(androidkindlekey.get_serials(aFile))
+    # extend PID list with book-specific PIDs from seriala and kDatabases
     md1, md2 = mb.getPIDMetaInfo()
     totalpids.extend(kgenpids.getPidList(md1, md2, serials, kDatabases))
+    # remove any duplicates
+    totalpid = list(set(totalpids))
     print u"Found {1:d} keys to try after {0:.1f} seconds".format(time.time()-starttime, len(totalpids))
 
     try:
@@ -223,7 +229,7 @@ def GetDecryptedBook(infile, kDatabases, serials, pids, starttime = time.time())
 
 
 # kDatabaseFiles is a list of files created by kindlekey
-def decryptBook(infile, outdir, kDatabaseFiles, serials, pids):
+def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
     starttime = time.time()
     kDatabases = []
     for dbfile in kDatabaseFiles:
@@ -239,7 +245,7 @@ def decryptBook(infile, outdir, kDatabaseFiles, serials, pids):
 
 
     try:
-        book = GetDecryptedBook(infile, kDatabases, serials, pids, starttime)
+        book = GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime)
     except Exception, e:
         print u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime)
         traceback.print_exc()
@@ -254,7 +260,7 @@ def decryptBook(infile, outdir, kDatabaseFiles, serials, pids):
 
     # avoid excessively long file names
     if len(outfilename)>150:
-        outfilename = outfilename[:150]
+        outfilename = outfilename[:99]+"--"+outfilename[-49:]
 
     outfilename = outfilename+u"_nodrm"
     outfile = os.path.join(outdir, outfilename + book.getBookExtension())
@@ -275,7 +281,7 @@ def decryptBook(infile, outdir, kDatabaseFiles, serials, pids):
 def usage(progname):
     print u"Removes DRM protection from Mobipocket, Amazon KF8, Amazon Print Replica and Amazon Topaz ebooks"
     print u"Usage:"
-    print u"    {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] [ -a <AmazonSecureStorage.xml> ] <infile> <outdir>".format(progname)
+    print u"    {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] [ -a <AmazonSecureStorage.xml|backup.ab> ] <infile> <outdir>".format(progname)
 
 #
 # Main
@@ -298,6 +304,7 @@ def cli_main():
     infile = args[0]
     outdir = args[1]
     kDatabaseFiles = []
+    androidFiles = []
     serials = []
     pids = []
 
@@ -317,12 +324,12 @@ def cli_main():
         if o == '-a':
             if a == None:
                 continue
-            serials.extend(androidkindlekey.get_serials(a))
+            androidFiles.apprend(a)
 
     # try with built in Kindle Info files if not on Linux
     k4 = not sys.platform.startswith('linux')
 
-    return decryptBook(infile, outdir, kDatabaseFiles, serials, pids)
+    return decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids)
 
 
 if __name__ == '__main__':
index f0f494cfa8db189f9e267b90777247aba837c60d..c1bfcb9a2936bc17add82eded8621df859a5161f 100644 (file)
@@ -23,9 +23,9 @@ class DeDRM_Prefs():
         self.dedrmprefs.defaults['adeptkeys'] = {}
         self.dedrmprefs.defaults['ereaderkeys'] = {}
         self.dedrmprefs.defaults['kindlekeys'] = {}
+        self.dedrmprefs.defaults['androidkeys'] = {}
         self.dedrmprefs.defaults['pids'] = []
         self.dedrmprefs.defaults['serials'] = []
-        self.dedrmprefs.defaults['androidserials'] = []
         self.dedrmprefs.defaults['adobewineprefix'] = ""
         self.dedrmprefs.defaults['kindlewineprefix'] = ""
 
@@ -41,12 +41,12 @@ class DeDRM_Prefs():
             self.dedrmprefs['ereaderkeys'] = {}
         if self.dedrmprefs['kindlekeys'] == {}:
             self.dedrmprefs['kindlekeys'] = {}
+        if self.dedrmprefs['androidkeys'] == {}:
+            self.dedrmprefs['androidkeys'] = {}
         if self.dedrmprefs['pids'] == []:
             self.dedrmprefs['pids'] = []
         if self.dedrmprefs['serials'] == []:
             self.dedrmprefs['serials'] = []
-        if self.dedrmprefs['androidserials'] == []:
-            self.dedrmprefs['androidserials'] = []
 
     def __getitem__(self,kind = None):
         if kind is not None:
index 3be643f5447bff1896e2816ff75265873b663b84..ec86b130c898b190b59527eed73378c72c50494b 100644 (file)
@@ -166,8 +166,30 @@ def decryptk4mobi(infile, outdir, rscpath):
         for filename in files:
             dpath = os.path.join(rscpath,filename)
             kDatabaseFiles.append(dpath)
+    androidFiles = []
+    files = os.listdir(rscpath)
+    filefilter = re.compile("\.ab$", re.IGNORECASE)
+    files = filter(filefilter.search, files)
+    if files:
+        for filename in files:
+            dpath = os.path.join(rscpath,filename)
+            androidFiles.append(dpath)
+    files = os.listdir(rscpath)
+    filefilter = re.compile("\.db$", re.IGNORECASE)
+    files = filter(filefilter.search, files)
+    if files:
+        for filename in files:
+            dpath = os.path.join(rscpath,filename)
+            androidFiles.append(dpath)
+    files = os.listdir(rscpath)
+    filefilter = re.compile("\.xml$", re.IGNORECASE)
+    files = filter(filefilter.search, files)
+    if files:
+        for filename in files:
+            dpath = os.path.join(rscpath,filename)
+            androidFiles.append(dpath)
     try:
-        rv = k4mobidedrm.decryptBook(infile, outdir, kDatabaseFiles, serialnums, pidnums)
+        rv = k4mobidedrm.decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serialnums, pidnums)
     except Exception, e:
         errlog += traceback.format_exc()
         errlog += str(e)