diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/__init__.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/__init__.py index 82329fe..5919f5b 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/__init__.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/__init__.py @@ -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): diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/androidkindlekey.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/androidkindlekey.py index 7a25710..2c539ee 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/androidkindlekey.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/androidkindlekey.py @@ -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 ] []".format(progname) + print u" {0:s} [-h] [-b ] []".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 diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/config.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/config.py index dcba6eb..b3a21a1 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/config.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/config.py @@ -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 {0} already exists!\nSkipping key file {1}.\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 {1} 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 {0} already exists!\nSkipping key file {1}.\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 {1} 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"

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"

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"

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"

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"

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 least 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 index 9921e4e..0000000 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/dialogs.py +++ /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('Help', 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 {0} 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 {0} to {1}?".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} {0}?".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 {0} already exists!\nSkipping key file {1}.\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 {1} 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 {0:d} key {1}. ".format(counter, u"file" if counter == 1 else u"files") - if skipped > 0: - msg += u"Skipped {0:d} 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 least 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 {0} 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"

Enter an identifying name for this new key.

" + - u"

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"

Enter your name as it appears in your B&N " + - u"account or on your credit card.

" + - u"

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.

" + - u"

(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"

Enter the full credit card number on record " + - u"in your B&N account.

" + - u"

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 least 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"

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"

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 least 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"

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 least 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"

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 least 4 characters long!" - return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) - QDialog.accept(self) - - -class AddSerialDialog(QDialog): - def __init__(self, parent=None,): - QDialog.__init__(self, parent) - self.parent = parent - self.setWindowTitle(u"{0} {1}: Add New EInk Kindle Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION)) - layout = QVBoxLayout(self) - self.setLayout(layout) - - data_group_box = QGroupBox(u"", self) - layout.addWidget(data_group_box) - data_group_box_layout = QVBoxLayout() - data_group_box.setLayout(data_group_box_layout) - - key_group = QHBoxLayout() - data_group_box_layout.addLayout(key_group) - key_group.addWidget(QLabel(u"EInk Kindle Serial Number:", self)) - self.key_ledit = QLineEdit("", self) - self.key_ledit.setToolTip(u"Enter an eInk Kindle serial number. EInk Kindle serial numbers are 16 characters long and usually start with a 'B' or a '9'. Kindle Serial Numbers are case-sensitive, so be sure to enter the upper and lower case letters unchanged.") - key_group.addWidget(self.key_ledit) - key_label = QLabel(_(''), self) - key_label.setAlignment(Qt.AlignHCenter) - data_group_box_layout.addWidget(key_label) - - self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) - self.button_box.accepted.connect(self.accept) - self.button_box.rejected.connect(self.reject) - layout.addWidget(self.button_box) - - self.resize(self.sizeHint()) - - @property - def key_name(self): - return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() - - @property - def key_value(self): - return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() - - def accept(self): - if len(self.key_name) == 0 or self.key_name.isspace(): - errmsg = u"Please enter an eInk Kindle Serial Number or click Cancel in the dialog." - return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) - if len(self.key_name) != 16: - errmsg = u"EInk Kindle Serial Numbers must be 16 characters long. This is {0:d} characters long.".format(len(self.key_name)) - return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) - QDialog.accept(self) - - -class AddPIDDialog(QDialog): - def __init__(self, parent=None,): - QDialog.__init__(self, parent) - self.parent = parent - self.setWindowTitle(u"{0} {1}: Add New Mobipocket PID".format(PLUGIN_NAME, PLUGIN_VERSION)) - layout = QVBoxLayout(self) - self.setLayout(layout) - - data_group_box = QGroupBox(u"", self) - layout.addWidget(data_group_box) - data_group_box_layout = QVBoxLayout() - data_group_box.setLayout(data_group_box_layout) - - key_group = QHBoxLayout() - data_group_box_layout.addLayout(key_group) - key_group.addWidget(QLabel(u"PID:", self)) - self.key_ledit = QLineEdit("", self) - self.key_ledit.setToolTip(u"Enter a Mobipocket PID. Mobipocket PIDs are 8 or 10 characters long. Mobipocket PIDs are case-sensitive, so be sure to enter the upper and lower case letters unchanged.") - key_group.addWidget(self.key_ledit) - key_label = QLabel(_(''), self) - key_label.setAlignment(Qt.AlignHCenter) - data_group_box_layout.addWidget(key_label) - - self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) - self.button_box.accepted.connect(self.accept) - self.button_box.rejected.connect(self.reject) - layout.addWidget(self.button_box) - - self.resize(self.sizeHint()) - - @property - def key_name(self): - return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() - - @property - def key_value(self): - return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip() - - def accept(self): - if len(self.key_name) == 0 or self.key_name.isspace(): - errmsg = u"Please enter a Mobipocket PID or click Cancel in the dialog." - return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) - if len(self.key_name) != 8 and len(self.key_name) != 10: - errmsg = u"Mobipocket PIDs must be 8 or 10 characters long. This is {0:d} characters long.".format(len(self.key_name)) - return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) - QDialog.accept(self) - - diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py index f4dd3fb..4ce5aab 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py @@ -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 ] [-p ] [-s ] [ -a ] ".format(progname) + print u" {0} [-k ] [-p ] [-s ] [ -a ] ".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__': diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/prefs.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/prefs.py index f0f494c..c1bfcb9 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/prefs.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/prefs.py @@ -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: diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/scriptinterface.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/scriptinterface.py index 3be643f..ec86b13 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/scriptinterface.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/scriptinterface.py @@ -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) diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/DeDRM_app.pyw b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/DeDRM_app.pyw index 5f9623c..34cfa70 100644 --- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/DeDRM_app.pyw +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/DeDRM_app.pyw @@ -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'] diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/__init__.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/__init__.py index 82329fe..5919f5b 100644 --- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/__init__.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/__init__.py @@ -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): diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/androidkindlekey.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/androidkindlekey.py index 7a25710..2c539ee 100644 --- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/androidkindlekey.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/androidkindlekey.py @@ -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 ] []".format(progname) + print u" {0:s} [-h] [-b ] []".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 diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/config.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/config.py index dcba6eb..b3a21a1 100644 --- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/config.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/config.py @@ -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 {0} already exists!\nSkipping key file {1}.\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 {1} 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 {0} already exists!\nSkipping key file {1}.\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 {1} 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"

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"

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"

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"

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"

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 least 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_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignoblekeyfetch.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignoblekeyfetch.py index 02495e7..c91e6f3 100644 --- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignoblekeyfetch.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignoblekeyfetch.py @@ -140,9 +140,9 @@ def fetch_key(email, password): response = urllib2.urlopen(req) the_page = response.read() #print the_page - found = re.search('ccHash>(.+?)(.+?)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 ] [-p ] [-s ] [ -a ] ".format(progname) + print u" {0} [-k ] [-p ] [-s ] [ -a ] ".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__': diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/prefs.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/prefs.py index f0f494c..c1bfcb9 100644 --- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/prefs.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/prefs.py @@ -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: diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/scriptinterface.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/scriptinterface.py index 1212f51..ec86b13 100644 --- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/scriptinterface.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/scriptinterface.py @@ -158,14 +158,6 @@ def decryptk4mobi(infile, outdir, rscpath): serialstr = serialstr.strip() if serialstr != '': serialnums = serialstr.split(',') - files = os.listdir(rscpath) - filefilter = re.compile("\.k4a$", 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 = [] files = os.listdir(rscpath) filefilter = re.compile("\.k4i$", re.IGNORECASE) @@ -174,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) diff --git a/DeDRM_calibre_plugin/DeDRM_plugin.zip b/DeDRM_calibre_plugin/DeDRM_plugin.zip index d268a33..ce87f3b 100644 Binary files a/DeDRM_calibre_plugin/DeDRM_plugin.zip and b/DeDRM_calibre_plugin/DeDRM_plugin.zip differ diff --git a/DeDRM_calibre_plugin/DeDRM_plugin/DeDRM_Kindle for Andoid_Help.htm b/DeDRM_calibre_plugin/DeDRM_plugin/DeDRM_Kindle for Android_Help.htm similarity index 100% rename from DeDRM_calibre_plugin/DeDRM_plugin/DeDRM_Kindle for Andoid_Help.htm rename to DeDRM_calibre_plugin/DeDRM_plugin/DeDRM_Kindle for Android_Help.htm diff --git a/DeDRM_calibre_plugin/DeDRM_plugin/__init__.py b/DeDRM_calibre_plugin/DeDRM_plugin/__init__.py index 82329fe..5919f5b 100644 --- a/DeDRM_calibre_plugin/DeDRM_plugin/__init__.py +++ b/DeDRM_calibre_plugin/DeDRM_plugin/__init__.py @@ -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): diff --git a/DeDRM_calibre_plugin/DeDRM_plugin/androidkindlekey.py b/DeDRM_calibre_plugin/DeDRM_plugin/androidkindlekey.py index 7a25710..2c539ee 100644 --- a/DeDRM_calibre_plugin/DeDRM_plugin/androidkindlekey.py +++ b/DeDRM_calibre_plugin/DeDRM_plugin/androidkindlekey.py @@ -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 ] []".format(progname) + print u" {0:s} [-h] [-b ] []".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 diff --git a/DeDRM_calibre_plugin/DeDRM_plugin/config.py b/DeDRM_calibre_plugin/DeDRM_plugin/config.py index dcba6eb..b3a21a1 100644 --- a/DeDRM_calibre_plugin/DeDRM_plugin/config.py +++ b/DeDRM_calibre_plugin/DeDRM_plugin/config.py @@ -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 {0} already exists!\nSkipping key file {1}.\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 {1} 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 {0} already exists!\nSkipping key file {1}.\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 {1} 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"

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"

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"

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"

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"

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 least 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_calibre_plugin/DeDRM_plugin/k4mobidedrm.py b/DeDRM_calibre_plugin/DeDRM_plugin/k4mobidedrm.py index f4dd3fb..4ce5aab 100644 --- a/DeDRM_calibre_plugin/DeDRM_plugin/k4mobidedrm.py +++ b/DeDRM_calibre_plugin/DeDRM_plugin/k4mobidedrm.py @@ -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 ] [-p ] [-s ] [ -a ] ".format(progname) + print u" {0} [-k ] [-p ] [-s ] [ -a ] ".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__': diff --git a/DeDRM_calibre_plugin/DeDRM_plugin/prefs.py b/DeDRM_calibre_plugin/DeDRM_plugin/prefs.py index f0f494c..c1bfcb9 100644 --- a/DeDRM_calibre_plugin/DeDRM_plugin/prefs.py +++ b/DeDRM_calibre_plugin/DeDRM_plugin/prefs.py @@ -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: diff --git a/DeDRM_calibre_plugin/DeDRM_plugin/scriptinterface.py b/DeDRM_calibre_plugin/DeDRM_plugin/scriptinterface.py index 3be643f..ec86b13 100644 --- a/DeDRM_calibre_plugin/DeDRM_plugin/scriptinterface.py +++ b/DeDRM_calibre_plugin/DeDRM_plugin/scriptinterface.py @@ -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)