diff --git a/DeDRM_Macintosh_Application/DeDRM.app.txt b/DeDRM_Macintosh_Application/DeDRM.app.txt index d9c2f55..e79ff4e 100644 Binary files a/DeDRM_Macintosh_Application/DeDRM.app.txt and b/DeDRM_Macintosh_Application/DeDRM.app.txt differ diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist index 1e1d4cc..2beb96d 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist @@ -48,7 +48,6 @@ bundleDividerCollapsed bundlePositionOfDivider - 739 dividerCollapsed eventLogLevel @@ -56,9 +55,9 @@ name ScriptWindowState positionOfDivider - 800 + 439 savedFrame - 74 36 1257 985 0 0 1680 1027 + 128 98 1246 778 0 0 1680 1027 selectedTab log diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Barnes and Noble Key_Help.htm b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Barnes and Noble Key_Help.htm index 47da891..8f22f21 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Barnes and Noble Key_Help.htm +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Barnes and Noble Key_Help.htm @@ -24,28 +24,17 @@ li {margin-top: 0.5em}

Changes at Barnes & Noble

-

In mid-2014, Barnes & Noble changed the way they generated encryption keys. Instead of deriving the key from the user's name and credit card number, they started generating a random key themselves, sending that key through to devices when they connected to the Barnes & Noble servers. This means that some users will find that no combination of their name and CC# will work in decrypting their ebooks.

+

In mid-2014, Barnes & Noble changed the way they generated encryption keys. Instead of deriving the key from the user's name and credit card number, they started generating a random key themselves, sending that key through to devices when they connected to the Barnes & Noble servers. This means that most users will now find that no combination of their name and CC# will work in decrypting their recently downloaded ebooks.

-

There is a work-around. Barnes & Noble’s desktop app NOOK Study generates a log file that contains the encryption key. You can download NOOK Study from https://yuzu.com/nsdownload.

-

Once downloaded, install the application, register with your Barnes & Noble or nook account, and download at least one DRMed ebook through NOOK Study. It will be saved somewhere in a folder called "My Barnes & Noble eBooks" in your Documents folder.

-

Now import that book into calibre. The log file and the key in the log should be automatically found by the plugin and used to decrypt the book.

-

If the automatic process doesn't work for you, you can still find extract it manually and save it as a .b64 file for import into the plugin's preferences as follows:

-
  1. In NOOK Study, select Settings/About (Windows) or NOOK Study/About NOOK Study (Mac) and in the dialog that appears click the link at the bottom to copy the log into the clipboard.
  2. -
  3. Paste the copied log into a text editor
  4. -
  5. Search for the text CCHashResponseV1
  6. -
  7. On the line below which starts with ccHash, copy the text between the " marks after ccHash, but don't include the " marks.
  8. -
  9. Save that text in a new plain text file, with file name extension .b64 (for example, key.b64)
  10. -
  11. Import that file into the preferences through this dialog, using the "Import Existing Key Files" button.
  12. -
+

Someone commenting at Apprentice Alf's blog detailed a way to retrieve a new account key using the account's email address and password. This method has now been incorporated into the plugin. - -

Old instructions: Creating New Keys:

+

Creating New Keys:

On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog for entering the necessary data to generate a new key.

Click the OK button to create and store the generated key. Or Cancel if you don’t want to create a key.

@@ -69,6 +58,11 @@ li {margin-top: 0.5em}

Once done creating/deleting/renaming/importing decryption keys, click Close to exit the customization dialogue. Your changes wil only be saved permanently when you click OK in the main configuration dialog.

+

NOOK Study

+

Books downloaded through NOOK Study may or may not use the key found using the above method. If a book is not decrypted successfully with any of the keys, the plugin will attempt to recover keys from the NOOK Study log file and use them.

+ + + diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/__init__.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/__init__.py index 0ba33f4..82329fe 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/__init__.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/__init__.py @@ -40,8 +40,10 @@ __docformat__ = 'restructuredtext en' # 6.2.0 - Support for getting B&N key from nook Study log. Fix for UTF-8 filenames in Adobe ePubs. # Fix for not copying needed files. Fix for getting default Adobe key for PDFs # 6.2.1 - Fix for non-ascii Windows user names +# 6.2.2 - Added URL method for B&N/nook books # 6.3.0 - Added in Kindle for Android serial number solution + """ Decrypt DRMed ebooks. """ @@ -254,7 +256,7 @@ class DeDRM(FileTypePlugin): # Store the new successful key in the defaults print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION) try: - dedrmprefs.addnamedvaluetoprefs('bandnkeys','default_key',keyvalue) + dedrmprefs.addnamedvaluetoprefs('bandnkeys','nook_Study_key',keyvalue) dedrmprefs.writeprefs() print u"{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) except: diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/config.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/config.py index 1357b49..dcba6eb 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/config.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/config.py @@ -545,14 +545,14 @@ class AddBandNKeyDialog(QDialog): name_group = QHBoxLayout() data_group_box_layout.addLayout(name_group) - name_group.addWidget(QLabel(u"Your Name:", self)) + name_group.addWidget(QLabel(u"B&N/nook account email address:", 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.

" + + self.name_ledit.setToolTip(_(u"

Enter your email address as it appears in your B&N " + + u"account.

" + u"

It will only be used to generate this " + - u"one-time key and won\'t be stored anywhere " + + u"key and won\'t be stored anywhere " + u"in calibre or on your computer.

" + - u"

(ex: Jonathan Smith)")) + u"

eg: apprenticeharper@gmail.com

")) 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) @@ -560,13 +560,12 @@ class AddBandNKeyDialog(QDialog): ccn_group = QHBoxLayout() data_group_box_layout.addLayout(ccn_group) - ccn_group.addWidget(QLabel(u"Credit Card#:", self)) + ccn_group.addWidget(QLabel(u"B&N/nook account password:", 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 " + + self.cc_ledit.setToolTip(_(u"

Enter the password " + + u"for your B&N account.

" + + u"

The password will only be used to generate this " + + u"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) @@ -587,8 +586,8 @@ class AddBandNKeyDialog(QDialog): @property def key_value(self): - from calibre_plugins.dedrm.ignoblekeygen import generate_key as generate_bandn_key - return generate_bandn_key(self.user_name,self.cc_number) + from calibre_plugins.dedrm.ignoblekeyfetch import fetch_key as fetch_bandn_key + return fetch_bandn_key(self.user_name,self.cc_number) @property def user_name(self): @@ -596,16 +595,13 @@ class AddBandNKeyDialog(QDialog): @property def cc_number(self): - return unicode(self.cc_ledit.text()).strip().replace(' ', '').replace('-','') + return unicode(self.cc_ledit.text()).strip() 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) diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/dialogs.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/dialogs.py new file mode 100644 index 0000000..9921e4e --- /dev/null +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/dialogs.py @@ -0,0 +1,719 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +from __future__ import with_statement +__license__ = 'GPL v3' + +# Standard Python modules. +import os, sys, re, hashlib +import json + +from PyQt4.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QListWidget, QListWidgetItem, QAbstractItemView, QLineEdit, QPushButton, QIcon, QGroupBox, QDialog, QDialogButtonBox, QUrl, QString) +from PyQt4 import QtGui + +# calibre modules and constants. +from calibre.gui2 import (error_dialog, question_dialog, info_dialog, open_url, + choose_dir, choose_files) +from calibre.utils.config import dynamic, config_dir, JSONConfig + +from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION +from calibre_plugins.dedrm.utilities import (uStrCmp, DETAILED_MESSAGE, parseCustString) +from calibre_plugins.dedrm.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/ignoblekey.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignoblekey.py index 4e9eead..dbadc5d 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignoblekey.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignoblekey.py @@ -4,7 +4,7 @@ from __future__ import with_statement # ignoblekey.py -# Copyright © 2015 Apprentice Alf +# Copyright © 2015 Apprentice Alf and Apprentice Harper # Based on kindlekey.py, Copyright © 2010-2013 by some_updates and Apprentice Alf @@ -13,13 +13,14 @@ from __future__ import with_statement # Revision history: # 1.0 - Initial release +# 1.1 - remove duplicates and return last key as single key """ Get Barnes & Noble EPUB user key from nook Studio log file """ __license__ = 'GPL v3' -__version__ = "1.0" +__version__ = "1.1" import sys import os @@ -143,7 +144,7 @@ def getNookLogFiles(): paths.add(path) except WindowsError: pass - + for path in paths: # look for nookStudy log file logpath = path +'\\Barnes & Noble\\NOOKstudy\\logs\\BNClientLog.txt' @@ -199,7 +200,7 @@ def nookkeys(files = []): if fileKeys: print u"Found {0} keys in the Nook Study log files".format(len(fileKeys)) keys.extend(fileKeys) - return keys + return list(set(keys)) # interface for Python DeDRM # returns single key or multiple keys, depending on path or file passed in @@ -209,7 +210,7 @@ def getkey(outpath, files=[]): if not os.path.isdir(outpath): outfile = outpath with file(outfile, 'w') as keyfileout: - keyfileout.write(keys[0]) + keyfileout.write(keys[-1]) print u"Saved a key to {0}".format(outfile) else: keycount = 0 diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignoblekeyfetch.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignoblekeyfetch.py index 2ecbe96..c91e6f3 100644 --- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignoblekeyfetch.py +++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignoblekeyfetch.py @@ -3,13 +3,14 @@ from __future__ import with_statement -# ignoblekeyfetch.pyw, version 1.0 +# ignoblekeyfetch.pyw, version 1.1 # Copyright © 2015 Apprentice Harper # Released under the terms of the GNU General Public Licence, version 3 # # Based on discoveries by "Nobody You Know" +# Code partly based on ignoblekeygen.py by several people. # Windows users: Before running this program, you must first install Python. # We recommend ActiveState Python 2.7.X for Windows from @@ -17,11 +18,12 @@ from __future__ import with_statement # Then save this script file as ignoblekeyfetch.pyw and double-click on it to run it. # # Mac OS X users: Save this script file as ignoblekeyfetch.pyw. You can run this -# program from the command line (python ignoblekeygen.pyw) or by double-clicking +# program from the command line (python ignoblekeyfetch.pyw) or by double-clicking # it when it has been associated with PythonLauncher. # Revision history: -# 1.0 - Initial release +# 1.0 - Initial version +# 1.1 - Try second URL if first one fails """ Fetch Barnes & Noble EPUB user key from B&N servers using email and password @@ -87,7 +89,7 @@ def unicode_argv(): xrange(start, argc.value)] # if we don't have any arguments at all, just pass back script name # this should never happen - return [u"ignoblekeygen.py"] + return [u"ignoblekeyfetch.py"] else: argvencoding = sys.stdin.encoding if argvencoding == None: @@ -99,7 +101,7 @@ class IGNOBLEError(Exception): pass def fetch_key(email, password): - # remove spaces and case from name and CC numbers. + # change name and CC numbers to utf-8 if unicode if type(email)==unicode: email = email.encode('utf-8') if type(password)==unicode: @@ -108,7 +110,9 @@ def fetch_key(email, password): import random random = "%030x" % random.randrange(16**30) - import urllib, urllib2 + import urllib, urllib2, re + + # try the URL from nook for PC fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword=" fetch_url += urllib.quote(password,'')+"&devID=PC_BN_2.5.6.9575_"+random+"&emailAddress=" fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB" @@ -120,12 +124,26 @@ def fetch_key(email, password): response = urllib2.urlopen(req) the_page = response.read() #print the_page - - import re - found = re.search('ccHash>(.+?)(.+?) @@ -59,7 +59,7 @@ li {margin-top: 0.5em}

Once done creating/deleting/renaming/importing decryption keys, click Close to exit the customization dialogue. Your changes wil only be saved permanently when you click OK in the main configuration dialog.

NOOK Study

-

Books downloaded through NOOK Study may or may not use the key fetched using the above method. If a books is not decrypted successfully with any of the keys, the plugin will attempt to recover a key from the NOOK Study log file and use that.

+

Books downloaded through NOOK Study may or may not use the key found using the above method. If a book is not decrypted successfully with any of the keys, the plugin will attempt to recover keys from the NOOK Study log file and use them.

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 0ba33f4..82329fe 100644 --- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/__init__.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/__init__.py @@ -40,8 +40,10 @@ __docformat__ = 'restructuredtext en' # 6.2.0 - Support for getting B&N key from nook Study log. Fix for UTF-8 filenames in Adobe ePubs. # Fix for not copying needed files. Fix for getting default Adobe key for PDFs # 6.2.1 - Fix for non-ascii Windows user names +# 6.2.2 - Added URL method for B&N/nook books # 6.3.0 - Added in Kindle for Android serial number solution + """ Decrypt DRMed ebooks. """ @@ -254,7 +256,7 @@ class DeDRM(FileTypePlugin): # Store the new successful key in the defaults print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION) try: - dedrmprefs.addnamedvaluetoprefs('bandnkeys','default_key',keyvalue) + dedrmprefs.addnamedvaluetoprefs('bandnkeys','nook_Study_key',keyvalue) dedrmprefs.writeprefs() print u"{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) except: 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 432a0ee..dcba6eb 100644 --- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/config.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/config.py @@ -109,15 +109,14 @@ class ConfigWidget(QWidget): self.ereader_button.setToolTip(_(u"Click to manage keys for eReader ebooks")) self.ereader_button.setText(u"eReader ebooks") self.ereader_button.clicked.connect(self.ereader_keys) - - button_layout.addWidget(self.adept_button) - button_layout.addWidget(self.kindle_key_button) button_layout.addWidget(self.kindle_serial_button) button_layout.addWidget(self.kindle_android_button) button_layout.addWidget(self.bandn_button) button_layout.addWidget(self.mobi_button) button_layout.addWidget(self.ereader_button) - + button_layout.addWidget(self.adept_button) + button_layout.addWidget(self.kindle_key_button) + self.resize(self.sizeHint()) def kindle_serials(self): @@ -125,7 +124,7 @@ class ConfigWidget(QWidget): d.exec_() def kindle_android_serials(self): - d = ManageKeysDialog(self,u"Kindle for Andoid",self.tempdedrmprefs['androidserials'], AddAndroidSerialDialog, 'ab') + d = ManageKeysDialog(self,u"Kindle for Andoid Serial Number",self.tempdedrmprefs['androidserials'], AddAndroidSerialDialog, 'ab') d.exec_() def kindle_keys(self): @@ -901,7 +900,7 @@ class AddAndroidSerialDialog(QDialog): 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.") + self.key_ledit.setToolTip(u"Enter a Kindle for ANdroid serial number. These can be found using the androidkindlekey.py script.") key_group.addWidget(self.key_ledit) key_label = QLabel(_(''), self) key_label.setAlignment(Qt.AlignHCenter) diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignoblekey.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignoblekey.py index 4e9eead..dbadc5d 100644 --- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignoblekey.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignoblekey.py @@ -4,7 +4,7 @@ from __future__ import with_statement # ignoblekey.py -# Copyright © 2015 Apprentice Alf +# Copyright © 2015 Apprentice Alf and Apprentice Harper # Based on kindlekey.py, Copyright © 2010-2013 by some_updates and Apprentice Alf @@ -13,13 +13,14 @@ from __future__ import with_statement # Revision history: # 1.0 - Initial release +# 1.1 - remove duplicates and return last key as single key """ Get Barnes & Noble EPUB user key from nook Studio log file """ __license__ = 'GPL v3' -__version__ = "1.0" +__version__ = "1.1" import sys import os @@ -143,7 +144,7 @@ def getNookLogFiles(): paths.add(path) except WindowsError: pass - + for path in paths: # look for nookStudy log file logpath = path +'\\Barnes & Noble\\NOOKstudy\\logs\\BNClientLog.txt' @@ -199,7 +200,7 @@ def nookkeys(files = []): if fileKeys: print u"Found {0} keys in the Nook Study log files".format(len(fileKeys)) keys.extend(fileKeys) - return keys + return list(set(keys)) # interface for Python DeDRM # returns single key or multiple keys, depending on path or file passed in @@ -209,7 +210,7 @@ def getkey(outpath, files=[]): if not os.path.isdir(outpath): outfile = outpath with file(outfile, 'w') as keyfileout: - keyfileout.write(keys[0]) + keyfileout.write(keys[-1]) print u"Saved a key to {0}".format(outfile) else: keycount = 0 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 2ecbe96..02495e7 100644 --- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignoblekeyfetch.py +++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignoblekeyfetch.py @@ -3,13 +3,14 @@ from __future__ import with_statement -# ignoblekeyfetch.pyw, version 1.0 +# ignoblekeyfetch.pyw, version 1.1 # Copyright © 2015 Apprentice Harper # Released under the terms of the GNU General Public Licence, version 3 # # Based on discoveries by "Nobody You Know" +# Code partly based on ignoblekeygen.py by several people. # Windows users: Before running this program, you must first install Python. # We recommend ActiveState Python 2.7.X for Windows from @@ -17,11 +18,12 @@ from __future__ import with_statement # Then save this script file as ignoblekeyfetch.pyw and double-click on it to run it. # # Mac OS X users: Save this script file as ignoblekeyfetch.pyw. You can run this -# program from the command line (python ignoblekeygen.pyw) or by double-clicking +# program from the command line (python ignoblekeyfetch.pyw) or by double-clicking # it when it has been associated with PythonLauncher. # Revision history: -# 1.0 - Initial release +# 1.0 - Initial version +# 1.1 - Try second URL if first one fails """ Fetch Barnes & Noble EPUB user key from B&N servers using email and password @@ -87,7 +89,7 @@ def unicode_argv(): xrange(start, argc.value)] # if we don't have any arguments at all, just pass back script name # this should never happen - return [u"ignoblekeygen.py"] + return [u"ignoblekeyfetch.py"] else: argvencoding = sys.stdin.encoding if argvencoding == None: @@ -99,7 +101,7 @@ class IGNOBLEError(Exception): pass def fetch_key(email, password): - # remove spaces and case from name and CC numbers. + # change name and CC numbers to utf-8 if unicode if type(email)==unicode: email = email.encode('utf-8') if type(password)==unicode: @@ -108,7 +110,9 @@ def fetch_key(email, password): import random random = "%030x" % random.randrange(16**30) - import urllib, urllib2 + import urllib, urllib2, re + + # try the URL from nook for PC fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword=" fetch_url += urllib.quote(password,'')+"&devID=PC_BN_2.5.6.9575_"+random+"&emailAddress=" fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB" @@ -120,12 +124,26 @@ def fetch_key(email, password): response = urllib2.urlopen(req) the_page = response.read() #print the_page - - import re - found = re.search('ccHash>(.+?)(.+?) @@ -59,7 +59,7 @@ li {margin-top: 0.5em}

Once done creating/deleting/renaming/importing decryption keys, click Close to exit the customization dialogue. Your changes wil only be saved permanently when you click OK in the main configuration dialog.

NOOK Study

-

Books downloaded through NOOK Study may or may not use the key fetched using the above method. If a books is not decrypted successfully with any of the keys, the plugin will attempt to recover a key from the NOOK Study log file and use that.

+

Books downloaded through NOOK Study may or may not use the key found using the above method. If a book is not decrypted successfully with any of the keys, the plugin will attempt to recover keys from the NOOK Study log file and use them.

diff --git a/DeDRM_calibre_plugin/DeDRM_plugin/__init__.py b/DeDRM_calibre_plugin/DeDRM_plugin/__init__.py index 0ba33f4..82329fe 100644 --- a/DeDRM_calibre_plugin/DeDRM_plugin/__init__.py +++ b/DeDRM_calibre_plugin/DeDRM_plugin/__init__.py @@ -40,8 +40,10 @@ __docformat__ = 'restructuredtext en' # 6.2.0 - Support for getting B&N key from nook Study log. Fix for UTF-8 filenames in Adobe ePubs. # Fix for not copying needed files. Fix for getting default Adobe key for PDFs # 6.2.1 - Fix for non-ascii Windows user names +# 6.2.2 - Added URL method for B&N/nook books # 6.3.0 - Added in Kindle for Android serial number solution + """ Decrypt DRMed ebooks. """ @@ -254,7 +256,7 @@ class DeDRM(FileTypePlugin): # Store the new successful key in the defaults print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION) try: - dedrmprefs.addnamedvaluetoprefs('bandnkeys','default_key',keyvalue) + dedrmprefs.addnamedvaluetoprefs('bandnkeys','nook_Study_key',keyvalue) dedrmprefs.writeprefs() print u"{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) except: diff --git a/DeDRM_calibre_plugin/DeDRM_plugin/config.py b/DeDRM_calibre_plugin/DeDRM_plugin/config.py index 432a0ee..dcba6eb 100644 --- a/DeDRM_calibre_plugin/DeDRM_plugin/config.py +++ b/DeDRM_calibre_plugin/DeDRM_plugin/config.py @@ -109,15 +109,14 @@ class ConfigWidget(QWidget): self.ereader_button.setToolTip(_(u"Click to manage keys for eReader ebooks")) self.ereader_button.setText(u"eReader ebooks") self.ereader_button.clicked.connect(self.ereader_keys) - - button_layout.addWidget(self.adept_button) - button_layout.addWidget(self.kindle_key_button) button_layout.addWidget(self.kindle_serial_button) button_layout.addWidget(self.kindle_android_button) button_layout.addWidget(self.bandn_button) button_layout.addWidget(self.mobi_button) button_layout.addWidget(self.ereader_button) - + button_layout.addWidget(self.adept_button) + button_layout.addWidget(self.kindle_key_button) + self.resize(self.sizeHint()) def kindle_serials(self): @@ -125,7 +124,7 @@ class ConfigWidget(QWidget): d.exec_() def kindle_android_serials(self): - d = ManageKeysDialog(self,u"Kindle for Andoid",self.tempdedrmprefs['androidserials'], AddAndroidSerialDialog, 'ab') + d = ManageKeysDialog(self,u"Kindle for Andoid Serial Number",self.tempdedrmprefs['androidserials'], AddAndroidSerialDialog, 'ab') d.exec_() def kindle_keys(self): @@ -901,7 +900,7 @@ class AddAndroidSerialDialog(QDialog): 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.") + self.key_ledit.setToolTip(u"Enter a Kindle for ANdroid serial number. These can be found using the androidkindlekey.py script.") key_group.addWidget(self.key_ledit) key_label = QLabel(_(''), self) key_label.setAlignment(Qt.AlignHCenter) diff --git a/DeDRM_calibre_plugin/DeDRM_plugin/ignoblekey.py b/DeDRM_calibre_plugin/DeDRM_plugin/ignoblekey.py index 4e9eead..dbadc5d 100644 --- a/DeDRM_calibre_plugin/DeDRM_plugin/ignoblekey.py +++ b/DeDRM_calibre_plugin/DeDRM_plugin/ignoblekey.py @@ -4,7 +4,7 @@ from __future__ import with_statement # ignoblekey.py -# Copyright © 2015 Apprentice Alf +# Copyright © 2015 Apprentice Alf and Apprentice Harper # Based on kindlekey.py, Copyright © 2010-2013 by some_updates and Apprentice Alf @@ -13,13 +13,14 @@ from __future__ import with_statement # Revision history: # 1.0 - Initial release +# 1.1 - remove duplicates and return last key as single key """ Get Barnes & Noble EPUB user key from nook Studio log file """ __license__ = 'GPL v3' -__version__ = "1.0" +__version__ = "1.1" import sys import os @@ -143,7 +144,7 @@ def getNookLogFiles(): paths.add(path) except WindowsError: pass - + for path in paths: # look for nookStudy log file logpath = path +'\\Barnes & Noble\\NOOKstudy\\logs\\BNClientLog.txt' @@ -199,7 +200,7 @@ def nookkeys(files = []): if fileKeys: print u"Found {0} keys in the Nook Study log files".format(len(fileKeys)) keys.extend(fileKeys) - return keys + return list(set(keys)) # interface for Python DeDRM # returns single key or multiple keys, depending on path or file passed in @@ -209,7 +210,7 @@ def getkey(outpath, files=[]): if not os.path.isdir(outpath): outfile = outpath with file(outfile, 'w') as keyfileout: - keyfileout.write(keys[0]) + keyfileout.write(keys[-1]) print u"Saved a key to {0}".format(outfile) else: keycount = 0 diff --git a/DeDRM_calibre_plugin/DeDRM_plugin/ignoblekeyfetch.py b/DeDRM_calibre_plugin/DeDRM_plugin/ignoblekeyfetch.py index 2ecbe96..c91e6f3 100644 --- a/DeDRM_calibre_plugin/DeDRM_plugin/ignoblekeyfetch.py +++ b/DeDRM_calibre_plugin/DeDRM_plugin/ignoblekeyfetch.py @@ -3,13 +3,14 @@ from __future__ import with_statement -# ignoblekeyfetch.pyw, version 1.0 +# ignoblekeyfetch.pyw, version 1.1 # Copyright © 2015 Apprentice Harper # Released under the terms of the GNU General Public Licence, version 3 # # Based on discoveries by "Nobody You Know" +# Code partly based on ignoblekeygen.py by several people. # Windows users: Before running this program, you must first install Python. # We recommend ActiveState Python 2.7.X for Windows from @@ -17,11 +18,12 @@ from __future__ import with_statement # Then save this script file as ignoblekeyfetch.pyw and double-click on it to run it. # # Mac OS X users: Save this script file as ignoblekeyfetch.pyw. You can run this -# program from the command line (python ignoblekeygen.pyw) or by double-clicking +# program from the command line (python ignoblekeyfetch.pyw) or by double-clicking # it when it has been associated with PythonLauncher. # Revision history: -# 1.0 - Initial release +# 1.0 - Initial version +# 1.1 - Try second URL if first one fails """ Fetch Barnes & Noble EPUB user key from B&N servers using email and password @@ -87,7 +89,7 @@ def unicode_argv(): xrange(start, argc.value)] # if we don't have any arguments at all, just pass back script name # this should never happen - return [u"ignoblekeygen.py"] + return [u"ignoblekeyfetch.py"] else: argvencoding = sys.stdin.encoding if argvencoding == None: @@ -99,7 +101,7 @@ class IGNOBLEError(Exception): pass def fetch_key(email, password): - # remove spaces and case from name and CC numbers. + # change name and CC numbers to utf-8 if unicode if type(email)==unicode: email = email.encode('utf-8') if type(password)==unicode: @@ -108,7 +110,9 @@ def fetch_key(email, password): import random random = "%030x" % random.randrange(16**30) - import urllib, urllib2 + import urllib, urllib2, re + + # try the URL from nook for PC fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword=" fetch_url += urllib.quote(password,'')+"&devID=PC_BN_2.5.6.9575_"+random+"&emailAddress=" fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB" @@ -120,12 +124,26 @@ def fetch_key(email, password): response = urllib2.urlopen(req) the_page = response.read() #print the_page - - import re - found = re.search('ccHash>(.+?)(.+?)