Merge branch 'nook_url_support'

This commit is contained in:
apprenticeharper 2015-07-13 18:02:48 +01:00
commit c16d767b00
22 changed files with 882 additions and 108 deletions

View File

@ -48,7 +48,6 @@
<key>bundleDividerCollapsed</key>
<false/>
<key>bundlePositionOfDivider</key>
<real>739</real>
<key>dividerCollapsed</key>
<false/>
<key>eventLogLevel</key>
@ -56,9 +55,9 @@
<key>name</key>
<string>ScriptWindowState</string>
<key>positionOfDivider</key>
<real>800</real>
<real>439</real>
<key>savedFrame</key>
<string>74 36 1257 985 0 0 1680 1027 </string>
<string>128 98 1246 778 0 0 1680 1027 </string>
<key>selectedTab</key>
<string>log</string>
</dict>

View File

@ -24,28 +24,17 @@ li {margin-top: 0.5em}
<h3>Changes at Barnes & Noble</h3>
<p>In mid-2014, Barnes & Noble changed the way they generated encryption keys. Instead of deriving the key from the user's name and credit card number, they started generating a random key themselves, sending that key through to devices when they connected to the Barnes & Noble servers. This means that some users will find that no combination of their name and CC# will work in decrypting their ebooks.</p>
<p>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.</p>
<p>There is a work-around. Barnes & Nobles desktop app NOOK Study generates a log file that contains the encryption key. You can download NOOK Study from <a href="https://yuzu.com/nsdownload">https://yuzu.com/nsdownload</a>.</p>
<p>Once downloaded, install the application, register with your Barnes & Noble or nook account, and download at least one DRMed ebook through NOOK Study. It will be saved somewhere in a folder called "My Barnes & Noble eBooks" in your Documents folder.</p>
<p>Now import that book into calibre. The log file and the key in the log should be automatically found by the plugin and used to decrypt the book.</p>
<p>If the automatic process doesn't work for you, you can still find extract it manually and save it as a .b64 file for import into the plugin's preferences as follows:</p>
<ol><li>In NOOK Study, select Settings/About (Windows) or NOOK Study/About NOOK Study (Mac) and in the dialog that appears click the link at the bottom to copy the log into the clipboard.</li>
<li>Paste the copied log into a text editor</li>
<li>Search for the text CCHashResponseV1</li>
<li>On the line below which starts with ccHash, copy the text between the " marks after ccHash, but don't include the " marks.</li>
<li>Save that text in a new <b>plain text</b> file, with file name extension .b64 (for example, key.b64)</li>
<li>Import that file into the preferences through this dialog, using the "Import Existing Key Files" button.</li>
</ol>
<p>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.
<h3>Old instructions: Creating New Keys:</h3>
<h3>Creating New Keys:</h3>
<p>On the right-hand side of the plugins 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.</p>
<ul>
<li><span class="bold">Unique Key Name:</span> this is a unique name you choose to help you identify the key. This name will show in the list of configured keys. Choose something that will help you remember the data (name, cc#) it was created with.</li>
<li><span class="bold">Your Name:</span> This is the name used by Barnes and Noble to generate your encryption key. Seemingly at random, Barnes and Noble choose one of three places from which to take this name. Most commonly, its your name as set in your Barnes &amp; Noble account, My Account page, directly under PERSONAL INFORMATION. Sometimes it is the the name used in the default shipping address, and sometimes its the name listed for the active credit card. If these names are different in your Barnes and Noble account preferences, I suggest creating one key for each version of your name. This name will not be stored anywhere on your computer or in calibre. It will only be used in the creation of the one-way hash/key thats stored in the preferences.</li>
<li><span class="bold">Credit Card#:</span> this is the default credit card number that was on file with Barnes and Noble at the time of download of the ebook to be de-DRMed. Just enter the 16 (15 for American Express) digits. As with the name, this number will not be stored anywhere on your computer or in calibre. It will only be used in the creation of the one-way hash/key thats stored in the preferences.</li>
<li><span class="bold">Unique Key Name:</span> this is a unique name you choose to help you identify the key. This name will show in the list of configured keys. Choose something that will help you remember the data (account email address) it was created with.</li>
<li><span class="bold">B&N/nook account email address:</span> This is the default email address for your Barnes and Noble/nook account. This email will not be stored anywhere on your computer or in calibre. It will only be used to fetch the account key that from the B&N server, and it is that key that will be stored in the preferences.</li>
<li><span class="bold">B&N/nook account password:</span> this is the password for your Barnes and Noble/nook account. As with the email address, this will not be stored anywhere on your computer or in calibre. It will only be used to fetch the key from the B&N server.</li>
</ul>
<p>Click the OK button to create and store the generated key. Or Cancel if you dont want to create a key.</p>
@ -69,6 +58,11 @@ li {margin-top: 0.5em}
<p>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.</p>
<h3>NOOK Study</h3>
<p>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.</p>
</body>
</html>

View File

@ -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:

View File

@ -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"<p>Enter your name as it appears in your B&N " +
u"account or on your credit card.</p>" +
self.name_ledit.setToolTip(_(u"<p>Enter your email address as it appears in your B&N " +
u"account.</p>" +
u"<p>It will only be used to generate this " +
u"one-time key and won\'t be stored anywhere " +
u"key and won\'t be stored anywhere " +
u"in calibre or on your computer.</p>" +
u"<p>(ex: Jonathan Smith)"))
u"<p>eg: apprenticeharper@gmail.com</p>"))
name_group.addWidget(self.name_ledit)
name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self)
name_disclaimer_label.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"<p>Enter the full credit card number on record " +
u"in your B&N account.</p>" +
u"<p>No spaces or dashes... just the numbers. " +
u"This number will only be used to generate this " +
u"one-time key and won\'t be stored anywhere in " +
self.cc_ledit.setToolTip(_(u"<p>Enter the password " +
u"for your B&N account.</p>" +
u"<p>The password will only be used to generate this " +
u"key and won\'t be stored anywhere in " +
u"calibre or on your computer."))
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 <i>least</i> 4 characters long!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)

View File

@ -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('<a href="http://www.foo.com/">Help</a>', self)
help_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard)
help_label.setAlignment(Qt.AlignRight)
help_label.linkActivated.connect(self.help_link_activated)
help_layout.addWidget(help_label)
keys_group_box = QGroupBox(_(u"{0}s".format(self.key_type_name)), self)
layout.addWidget(keys_group_box)
keys_group_box_layout = QHBoxLayout()
keys_group_box.setLayout(keys_group_box_layout)
self.listy = QListWidget(self)
self.listy.setToolTip(u"{0}s that will be used to decrypt ebooks".format(self.key_type_name))
self.listy.setSelectionMode(QAbstractItemView.SingleSelection)
self.populate_list()
keys_group_box_layout.addWidget(self.listy)
button_layout = QVBoxLayout()
keys_group_box_layout.addLayout(button_layout)
self._add_key_button = QtGui.QToolButton(self)
self._add_key_button.setToolTip(u"Create new {0}".format(self.key_type_name))
self._add_key_button.setIcon(QIcon(I('plus.png')))
self._add_key_button.clicked.connect(self.add_key)
button_layout.addWidget(self._add_key_button)
self._delete_key_button = QtGui.QToolButton(self)
self._delete_key_button.setToolTip(_(u"Delete highlighted key"))
self._delete_key_button.setIcon(QIcon(I('list_remove.png')))
self._delete_key_button.clicked.connect(self.delete_key)
button_layout.addWidget(self._delete_key_button)
if type(self.plugin_keys) == dict:
self._rename_key_button = QtGui.QToolButton(self)
self._rename_key_button.setToolTip(_(u"Rename highlighted key"))
self._rename_key_button.setIcon(QIcon(I('edit-select-all.png')))
self._rename_key_button.clicked.connect(self.rename_key)
button_layout.addWidget(self._rename_key_button)
self.export_key_button = QtGui.QToolButton(self)
self.export_key_button.setToolTip(u"Save highlighted key to a .{0} file".format(self.keyfile_ext))
self.export_key_button.setIcon(QIcon(I('save.png')))
self.export_key_button.clicked.connect(self.export_key)
button_layout.addWidget(self.export_key_button)
spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
button_layout.addItem(spacerItem)
layout.addSpacing(5)
migrate_layout = QHBoxLayout()
layout.addLayout(migrate_layout)
if self.import_key:
migrate_layout.setAlignment(Qt.AlignJustify)
self.migrate_btn = QPushButton(u"Import Existing Keyfiles", self)
self.migrate_btn.setToolTip(u"Import *.{0} files (created using other tools).".format(self.keyfile_ext))
self.migrate_btn.clicked.connect(self.migrate_wrapper)
migrate_layout.addWidget(self.migrate_btn)
migrate_layout.addStretch()
self.button_box = QDialogButtonBox(QDialogButtonBox.Close)
self.button_box.rejected.connect(self.close)
migrate_layout.addWidget(self.button_box)
self.resize(self.sizeHint())
def populate_list(self):
if type(self.plugin_keys) == dict:
for key in self.plugin_keys.keys():
self.listy.addItem(QListWidgetItem(key))
else:
for key in self.plugin_keys:
self.listy.addItem(QListWidgetItem(key))
def add_key(self):
d = self.create_key(self)
d.exec_()
if d.result() != d.Accepted:
# New key generation cancelled.
return
new_key_value = d.key_value
if type(self.plugin_keys) == dict:
if new_key_value in self.plugin_keys.values():
old_key_name = [name for name, value in self.plugin_keys.iteritems() if value == new_key_value][0]
info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
u"The new {1} is the same as the existing {1} named <strong>{0}</strong> and has not been added.".format(old_key_name,self.key_type_name), show=True)
return
self.plugin_keys[d.key_name] = new_key_value
else:
if new_key_value in self.plugin_keys:
info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
u"This {0} is already in the list of {0}s has not been added.".format(self.key_type_name), show=True)
return
self.plugin_keys.append(d.key_value)
self.listy.clear()
self.populate_list()
def rename_key(self):
if not self.listy.currentItem():
errmsg = u"No {0} selected to rename. Highlight a keyfile first.".format(self.key_type_name)
r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
_(errmsg), show=True, show_copy_button=False)
return
d = RenameKeyDialog(self)
d.exec_()
if d.result() != d.Accepted:
# rename cancelled or moot.
return
keyname = unicode(self.listy.currentItem().text().toUtf8(),'utf8')
if not question_dialog(self, "{0} {1}: Confirm Rename".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to rename the {2} named <strong>{0}</strong> to <strong>{1}</strong>?".format(keyname,d.key_name,self.key_type_name), show_copy_button=False, default_yes=False):
return
self.plugin_keys[d.key_name] = self.plugin_keys[keyname]
del self.plugin_keys[keyname]
self.listy.clear()
self.populate_list()
def delete_key(self):
if not self.listy.currentItem():
return
keyname = unicode(self.listy.currentItem().text().toUtf8(), 'utf8')
if not question_dialog(self, "{0} {1}: Confirm Delete".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to delete the {1} <strong>{0}</strong>?".format(keyname, self.key_type_name), show_copy_button=False, default_yes=False):
return
if type(self.plugin_keys) == dict:
del self.plugin_keys[keyname]
else:
self.plugin_keys.remove(keyname)
self.listy.clear()
self.populate_list()
def help_link_activated(self, url):
def get_help_file_resource():
# Copy the HTML helpfile to the plugin directory each time the
# link is clicked in case the helpfile is updated in newer plugins.
help_file_name = u"{0}_{1}_Help.htm".format(PLUGIN_NAME, self.key_type_name)
file_path = os.path.join(config_dir, u"plugins", u"DeDRM", u"help", help_file_name)
with open(file_path,'w') as f:
f.write(self.parent.load_resource(help_file_name))
return file_path
url = 'file:///' + get_help_file_resource()
open_url(QUrl(url))
def migrate_files(self):
dynamic[PLUGIN_NAME + u"config_dir"] = config_dir
files = choose_files(self, PLUGIN_NAME + u"config_dir",
u"Select {0} files to import".format(self.key_type_name), [(u"{0} files".format(self.key_type_name), [self.keyfile_ext])], False)
counter = 0
skipped = 0
if files:
for filename in files:
fpath = os.path.join(config_dir, filename)
filename = os.path.basename(filename)
new_key_name = os.path.splitext(os.path.basename(filename))[0]
with open(fpath,'rb') as keyfile:
new_key_value = keyfile.read()
if self.binary_file:
new_key_value = new_key_value.encode('hex')
elif self.json_file:
new_key_value = json.loads(new_key_value)
match = False
for key in self.plugin_keys.keys():
if uStrCmp(new_key_name, key, True):
skipped += 1
msg = u"A key with the name <strong>{0}</strong> already exists!\nSkipping key file <strong>{1}</strong>.\nRename the existing key and import again".format(new_key_name,filename)
inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
_(msg), show_copy_button=False, show=True)
match = True
break
if not match:
if new_key_value in self.plugin_keys.values():
old_key_name = [name for name, value in self.plugin_keys.iteritems() if value == new_key_value][0]
skipped += 1
info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
u"The key in file {0} is the same as the existing key <strong>{1}</strong> and has been skipped.".format(filename,old_key_name), show_copy_button=False, show=True)
else:
counter += 1
self.plugin_keys[new_key_name] = new_key_value
msg = u""
if counter+skipped > 1:
if counter > 0:
msg += u"Imported <strong>{0:d}</strong> key {1}. ".format(counter, u"file" if counter == 1 else u"files")
if skipped > 0:
msg += u"Skipped <strong>{0:d}</strong> key {1}.".format(skipped, u"file" if counter == 1 else u"files")
inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
_(msg), show_copy_button=False, show=True)
return counter > 0
def migrate_wrapper(self):
if self.migrate_files():
self.listy.clear()
self.populate_list()
def export_key(self):
if not self.listy.currentItem():
errmsg = u"No keyfile selected to export. Highlight a keyfile first."
r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
_(errmsg), show=True, show_copy_button=False)
return
filter = QString(u"{0} Files (*.{1})".format(self.key_type_name, self.keyfile_ext))
keyname = unicode(self.listy.currentItem().text().toUtf8(), 'utf8')
if dynamic.get(PLUGIN_NAME + 'save_dir'):
defaultname = os.path.join(dynamic.get(PLUGIN_NAME + 'save_dir'), u"{0}.{1}".format(keyname , self.keyfile_ext))
else:
defaultname = os.path.join(os.path.expanduser('~'), u"{0}.{1}".format(keyname , self.keyfile_ext))
filename = unicode(QtGui.QFileDialog.getSaveFileName(self, u"Save {0} File as...".format(self.key_type_name), defaultname,
u"{0} Files (*.{1})".format(self.key_type_name,self.keyfile_ext), filter))
if filename:
dynamic[PLUGIN_NAME + 'save_dir'] = os.path.split(filename)[0]
with file(filename, 'w') as fname:
if self.binary_file:
fname.write(self.plugin_keys[keyname].decode('hex'))
elif self.json_file:
fname.write(json.dumps(self.plugin_keys[keyname]))
else:
fname.write(self.plugin_keys[keyname])
class RenameKeyDialog(QDialog):
def __init__(self, parent=None,):
print repr(self), repr(parent)
QDialog.__init__(self, parent)
self.parent = parent
self.setWindowTitle("{0} {1}: Rename {0}".format(PLUGIN_NAME, PLUGIN_VERSION, parent.key_type_name))
layout = QVBoxLayout(self)
self.setLayout(layout)
data_group_box = QGroupBox('', self)
layout.addWidget(data_group_box)
data_group_box_layout = QVBoxLayout()
data_group_box.setLayout(data_group_box_layout)
data_group_box_layout.addWidget(QLabel('New Key Name:', self))
self.key_ledit = QLineEdit(self.parent.listy.currentItem().text(), self)
self.key_ledit.setToolTip(u"Enter a new name for this existing {0}.".format(parent.key_type_name))
data_group_box_layout.addWidget(self.key_ledit)
layout.addSpacing(20)
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.button_box.accepted.connect(self.accept)
self.button_box.rejected.connect(self.reject)
layout.addWidget(self.button_box)
self.resize(self.sizeHint())
def accept(self):
if self.key_ledit.text().isEmpty() or unicode(self.key_ledit.text()).isspace():
errmsg = u"Key name field cannot be empty!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
_(errmsg), show=True, show_copy_button=False)
if len(self.key_ledit.text()) < 4:
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
_(errmsg), show=True, show_copy_button=False)
if uStrCmp(self.key_ledit.text(), self.parent.listy.currentItem().text()):
# Same exact name ... do nothing.
return QDialog.reject(self)
for k in self.parent.plugin_keys.keys():
if (uStrCmp(self.key_ledit.text(), k, True) and
not uStrCmp(k, self.parent.listy.currentItem().text(), True)):
errmsg = u"The key name <strong>{0}</strong> is already being used.".format(self.key_ledit.text())
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
_(errmsg), show=True, show_copy_button=False)
QDialog.accept(self)
@property
def key_name(self):
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
class AddBandNKeyDialog(QDialog):
def __init__(self, parent=None,):
QDialog.__init__(self, parent)
self.parent = parent
self.setWindowTitle(u"{0} {1}: Create New Barnes & Noble Key".format(PLUGIN_NAME, PLUGIN_VERSION))
layout = QVBoxLayout(self)
self.setLayout(layout)
data_group_box = QGroupBox(u"", self)
layout.addWidget(data_group_box)
data_group_box_layout = QVBoxLayout()
data_group_box.setLayout(data_group_box_layout)
key_group = QHBoxLayout()
data_group_box_layout.addLayout(key_group)
key_group.addWidget(QLabel(u"Unique Key Name:", self))
self.key_ledit = QLineEdit("", self)
self.key_ledit.setToolTip(_(u"<p>Enter an identifying name for this new key.</p>" +
u"<p>It should be something that will help you remember " +
u"what personal information was used to create it."))
key_group.addWidget(self.key_ledit)
key_label = QLabel(_(''), self)
key_label.setAlignment(Qt.AlignHCenter)
data_group_box_layout.addWidget(key_label)
name_group = QHBoxLayout()
data_group_box_layout.addLayout(name_group)
name_group.addWidget(QLabel(u"Your Name:", self))
self.name_ledit = QLineEdit(u"", self)
self.name_ledit.setToolTip(_(u"<p>Enter your name as it appears in your B&N " +
u"account or on your credit card.</p>" +
u"<p>It will only be used to generate this " +
u"one-time key and won\'t be stored anywhere " +
u"in calibre or on your computer.</p>" +
u"<p>(ex: Jonathan Smith)"))
name_group.addWidget(self.name_ledit)
name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self)
name_disclaimer_label.setAlignment(Qt.AlignHCenter)
data_group_box_layout.addWidget(name_disclaimer_label)
ccn_group = QHBoxLayout()
data_group_box_layout.addLayout(ccn_group)
ccn_group.addWidget(QLabel(u"Credit Card#:", self))
self.cc_ledit = QLineEdit(u"", self)
self.cc_ledit.setToolTip(_(u"<p>Enter the full credit card number on record " +
u"in your B&N account.</p>" +
u"<p>No spaces or dashes... just the numbers. " +
u"This number will only be used to generate this " +
u"one-time key and won\'t be stored anywhere in " +
u"calibre or on your computer."))
ccn_group.addWidget(self.cc_ledit)
ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self)
ccn_disclaimer_label.setAlignment(Qt.AlignHCenter)
data_group_box_layout.addWidget(ccn_disclaimer_label)
layout.addSpacing(10)
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.button_box.accepted.connect(self.accept)
self.button_box.rejected.connect(self.reject)
layout.addWidget(self.button_box)
self.resize(self.sizeHint())
@property
def key_name(self):
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
@property
def key_value(self):
return generate_bandn_key(self.user_name,self.cc_number)
@property
def user_name(self):
return unicode(self.name_ledit.text().toUtf8(), 'utf8').strip().lower().replace(' ','')
@property
def cc_number(self):
return unicode(self.cc_ledit.text().toUtf8(), 'utf8').strip().replace(' ', '').replace('-','')
def accept(self):
if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace():
errmsg = u"All fields are required!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
if not self.cc_number.isdigit():
errmsg = u"Numbers only in the credit card number field!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
if len(self.key_name) < 4:
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
QDialog.accept(self)
class AddEReaderDialog(QDialog):
def __init__(self, parent=None,):
QDialog.__init__(self, parent)
self.parent = parent
self.setWindowTitle(u"{0} {1}: Create New eReader Key".format(PLUGIN_NAME, PLUGIN_VERSION))
layout = QVBoxLayout(self)
self.setLayout(layout)
data_group_box = QGroupBox(u"", self)
layout.addWidget(data_group_box)
data_group_box_layout = QVBoxLayout()
data_group_box.setLayout(data_group_box_layout)
key_group = QHBoxLayout()
data_group_box_layout.addLayout(key_group)
key_group.addWidget(QLabel(u"Unique Key Name:", self))
self.key_ledit = QLineEdit("", self)
self.key_ledit.setToolTip(u"<p>Enter an identifying name for this new key.\nIt should be something that will help you remember what personal information was used to create it.")
key_group.addWidget(self.key_ledit)
key_label = QLabel(_(''), self)
key_label.setAlignment(Qt.AlignHCenter)
data_group_box_layout.addWidget(key_label)
name_group = QHBoxLayout()
data_group_box_layout.addLayout(name_group)
name_group.addWidget(QLabel(u"Your Name:", self))
self.name_ledit = QLineEdit(u"", self)
self.name_ledit.setToolTip(u"Enter the name for this eReader key, usually the name on your credit card.\nIt will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.\n(ex: Mr Jonathan Q Smith)")
name_group.addWidget(self.name_ledit)
name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self)
name_disclaimer_label.setAlignment(Qt.AlignHCenter)
data_group_box_layout.addWidget(name_disclaimer_label)
ccn_group = QHBoxLayout()
data_group_box_layout.addLayout(ccn_group)
ccn_group.addWidget(QLabel(u"Credit Card#:", self))
self.cc_ledit = QLineEdit(u"", self)
self.cc_ledit.setToolTip(u"<p>Enter the last 8 digits of credit card number for this eReader key.\nThey will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.")
ccn_group.addWidget(self.cc_ledit)
ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self)
ccn_disclaimer_label.setAlignment(Qt.AlignHCenter)
data_group_box_layout.addWidget(ccn_disclaimer_label)
layout.addSpacing(10)
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.button_box.accepted.connect(self.accept)
self.button_box.rejected.connect(self.reject)
layout.addWidget(self.button_box)
self.resize(self.sizeHint())
@property
def key_name(self):
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
@property
def key_value(self):
return generate_ereader_key(self.user_name,self.cc_number).encode('hex')
@property
def user_name(self):
return unicode(self.name_ledit.text().toUtf8(), 'utf8').strip().lower().replace(' ','')
@property
def cc_number(self):
return unicode(self.cc_ledit.text().toUtf8(), 'utf8').strip().replace(' ', '').replace('-','')
def accept(self):
if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace():
errmsg = u"All fields are required!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
if not self.cc_number.isdigit():
errmsg = u"Numbers only in the credit card number field!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
if len(self.key_name) < 4:
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
QDialog.accept(self)
class AddAdeptDialog(QDialog):
def __init__(self, parent=None,):
QDialog.__init__(self, parent)
self.parent = parent
self.setWindowTitle(u"{0} {1}: Getting Default Adobe Digital Editions Key".format(PLUGIN_NAME, PLUGIN_VERSION))
layout = QVBoxLayout(self)
self.setLayout(layout)
try:
self.default_key = retrieve_adept_keys()[0]
except:
self.default_key = u""
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
if len(self.default_key)>0:
data_group_box = QGroupBox(u"", self)
layout.addWidget(data_group_box)
data_group_box_layout = QVBoxLayout()
data_group_box.setLayout(data_group_box_layout)
key_group = QHBoxLayout()
data_group_box_layout.addLayout(key_group)
key_group.addWidget(QLabel(u"Unique Key Name:", self))
self.key_ledit = QLineEdit("", self)
self.key_ledit.setToolTip(u"<p>Enter an identifying name for the current default Adobe Digital Editions key.")
key_group.addWidget(self.key_ledit)
key_label = QLabel(_(''), self)
key_label.setAlignment(Qt.AlignHCenter)
data_group_box_layout.addWidget(key_label)
self.button_box.accepted.connect(self.accept)
else:
default_key_error = QLabel(u"The default encryption key for Adobe Digital Editions could not be found.", self)
default_key_error.setAlignment(Qt.AlignHCenter)
layout.addWidget(default_key_error)
# if no default, bot buttons do the same
self.button_box.accepted.connect(self.reject)
self.button_box.rejected.connect(self.reject)
layout.addWidget(self.button_box)
self.resize(self.sizeHint())
@property
def key_name(self):
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
@property
def key_value(self):
return self.default_key.encode('hex')
def accept(self):
if len(self.key_name) == 0 or self.key_name.isspace():
errmsg = u"All fields are required!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
if len(self.key_name) < 4:
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
QDialog.accept(self)
class AddKindleDialog(QDialog):
def __init__(self, parent=None,):
QDialog.__init__(self, parent)
self.parent = parent
self.setWindowTitle(u"{0} {1}: Getting Default Kindle for Mac/PC Key".format(PLUGIN_NAME, PLUGIN_VERSION))
layout = QVBoxLayout(self)
self.setLayout(layout)
try:
self.default_key = retrieve_kindle_keys()[0]
except:
self.default_key = u""
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
if len(self.default_key)>0:
data_group_box = QGroupBox(u"", self)
layout.addWidget(data_group_box)
data_group_box_layout = QVBoxLayout()
data_group_box.setLayout(data_group_box_layout)
key_group = QHBoxLayout()
data_group_box_layout.addLayout(key_group)
key_group.addWidget(QLabel(u"Unique Key Name:", self))
self.key_ledit = QLineEdit("", self)
self.key_ledit.setToolTip(u"<p>Enter an identifying name for the current default Kindle for Mac/PC key.")
key_group.addWidget(self.key_ledit)
key_label = QLabel(_(''), self)
key_label.setAlignment(Qt.AlignHCenter)
data_group_box_layout.addWidget(key_label)
self.button_box.accepted.connect(self.accept)
else:
default_key_error = QLabel(u"The default encryption key for Kindle for Mac/PC could not be found.", self)
default_key_error.setAlignment(Qt.AlignHCenter)
layout.addWidget(default_key_error)
# if no default, bot buttons do the same
self.button_box.accepted.connect(self.reject)
self.button_box.rejected.connect(self.reject)
layout.addWidget(self.button_box)
self.resize(self.sizeHint())
@property
def key_name(self):
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
@property
def key_value(self):
return self.default_key
def accept(self):
if len(self.key_name) == 0 or self.key_name.isspace():
errmsg = u"All fields are required!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
if len(self.key_name) < 4:
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
QDialog.accept(self)
class AddSerialDialog(QDialog):
def __init__(self, parent=None,):
QDialog.__init__(self, parent)
self.parent = parent
self.setWindowTitle(u"{0} {1}: Add New EInk Kindle Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION))
layout = QVBoxLayout(self)
self.setLayout(layout)
data_group_box = QGroupBox(u"", self)
layout.addWidget(data_group_box)
data_group_box_layout = QVBoxLayout()
data_group_box.setLayout(data_group_box_layout)
key_group = QHBoxLayout()
data_group_box_layout.addLayout(key_group)
key_group.addWidget(QLabel(u"EInk Kindle Serial Number:", self))
self.key_ledit = QLineEdit("", self)
self.key_ledit.setToolTip(u"Enter an eInk Kindle serial number. EInk Kindle serial numbers are 16 characters long and usually start with a 'B' or a '9'. Kindle Serial Numbers are case-sensitive, so be sure to enter the upper and lower case letters unchanged.")
key_group.addWidget(self.key_ledit)
key_label = QLabel(_(''), self)
key_label.setAlignment(Qt.AlignHCenter)
data_group_box_layout.addWidget(key_label)
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.button_box.accepted.connect(self.accept)
self.button_box.rejected.connect(self.reject)
layout.addWidget(self.button_box)
self.resize(self.sizeHint())
@property
def key_name(self):
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
@property
def key_value(self):
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
def accept(self):
if len(self.key_name) == 0 or self.key_name.isspace():
errmsg = u"Please enter an eInk Kindle Serial Number or click Cancel in the dialog."
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
if len(self.key_name) != 16:
errmsg = u"EInk Kindle Serial Numbers must be 16 characters long. This is {0:d} characters long.".format(len(self.key_name))
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
QDialog.accept(self)
class AddPIDDialog(QDialog):
def __init__(self, parent=None,):
QDialog.__init__(self, parent)
self.parent = parent
self.setWindowTitle(u"{0} {1}: Add New Mobipocket PID".format(PLUGIN_NAME, PLUGIN_VERSION))
layout = QVBoxLayout(self)
self.setLayout(layout)
data_group_box = QGroupBox(u"", self)
layout.addWidget(data_group_box)
data_group_box_layout = QVBoxLayout()
data_group_box.setLayout(data_group_box_layout)
key_group = QHBoxLayout()
data_group_box_layout.addLayout(key_group)
key_group.addWidget(QLabel(u"PID:", self))
self.key_ledit = QLineEdit("", self)
self.key_ledit.setToolTip(u"Enter a Mobipocket PID. Mobipocket PIDs are 8 or 10 characters long. Mobipocket PIDs are case-sensitive, so be sure to enter the upper and lower case letters unchanged.")
key_group.addWidget(self.key_ledit)
key_label = QLabel(_(''), self)
key_label.setAlignment(Qt.AlignHCenter)
data_group_box_layout.addWidget(key_label)
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.button_box.accepted.connect(self.accept)
self.button_box.rejected.connect(self.reject)
layout.addWidget(self.button_box)
self.resize(self.sizeHint())
@property
def key_name(self):
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
@property
def key_value(self):
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
def accept(self):
if len(self.key_name) == 0 or self.key_name.isspace():
errmsg = u"Please enter a Mobipocket PID or click Cancel in the dialog."
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
if len(self.key_name) != 8 and len(self.key_name) != 10:
errmsg = u"Mobipocket PIDs must be 8 or 10 characters long. This is {0:d} characters long.".format(len(self.key_name))
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
QDialog.accept(self)

View File

@ -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

View File

@ -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
# <http://www.gnu.org/licenses/>
# 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>(.+?)</ccHash', the_page).group(1)
except:
found = ''
if len(found)!=28:
# try the URL from android devices
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
fetch_url += urllib.quote(password,'')+"&devID=hobbes_9.3.50818_"+random+"&emailAddress="
fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
#print fetch_url
found = ''
try:
req = urllib2.Request(fetch_url)
response = urllib2.urlopen(req)
the_page = response.read()
#print the_page
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
except:
found = ''
return found

View File

@ -13,6 +13,7 @@
# 6.0.5 - Fix typo
# 6.2.0 - Update to match plugin and AppleScript
# 6.2.1 - Fix for non-ascii user names
# 6.2.2 - Added URL method for B&N/nook books
# 6.3.0 - Add in Android support
__version__ = '6.3.0'

View File

@ -1,4 +1,4 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
@ -59,7 +59,7 @@ li {margin-top: 0.5em}
<p>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.</p>
<h3>NOOK Study</h3>
<p>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.</p>
<p>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.</p>

View File

@ -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:

View File

@ -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)

View File

@ -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

View File

@ -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
# <http://www.gnu.org/licenses/>
# 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>(.+?)</ccHash', the_page).group(1)
except:
found = ''
if len(found)!=28:
# try the URL from android devices
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
fetch_url += urllib.quote(password,'')+"&devID=hobbes_9.3.50818_"+random+"&emailAddress="
fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
#print fetch_url
found = ''
try:
req = urllib2.Request(fetch_url)
response = urllib2.urlopen(req)
the_page = response.read()
#print the_page
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
except:
found = ''
return found

View File

@ -20,7 +20,7 @@ Installation
------------
0. If you don't already have a correct version of Python and PyCrypto installed, follow the "Installing Python on Windows" and "Installing PyCrypto on Windows" sections below before continuing.
1. Drag the DeDRM_App folder from tools_v6.2.0/DeDRM_Application_Windows to your "My Documents" folder.
1. Drag the DeDRM_App folder from tools_v6.2.2/DeDRM_Application_Windows to your "My Documents" folder.
2. Open the DeDRM_App folder you've just dragged, and make a short-cut of the DeDRM_Drop_Target.bat file (right-click/Create Shortcut). Drag the shortcut file onto your Desktop.

View File

@ -1,4 +1,4 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
@ -59,7 +59,7 @@ li {margin-top: 0.5em}
<p>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.</p>
<h3>NOOK Study</h3>
<p>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.</p>
<p>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.</p>

View File

@ -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:

View File

@ -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)

View File

@ -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

View File

@ -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
# <http://www.gnu.org/licenses/>
# 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>(.+?)</ccHash', the_page).group(1)
except:
found = ''
if len(found)!=28:
# try the URL from android devices
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
fetch_url += urllib.quote(password,'')+"&devID=hobbes_9.3.50818_"+random+"&emailAddress="
fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
#print fetch_url
found = ''
try:
req = urllib2.Request(fetch_url)
response = urllib2.urlopen(req)
the_page = response.read()
#print the_page
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
except:
found = ''
return found

View File

@ -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

View File

@ -1,7 +1,7 @@
Welcome to the tools!
=====================
This ReadMe_First.txt is meant to give users a quick overview of what is available and how to get started. This document is part of the Tools v6.2.0 archive from Apprentice Alf's Blog: http://apprenticealf.wordpress.com/
This ReadMe_First.txt is meant to give users a quick overview of what is available and how to get started. This document is part of the Tools v6.2.2 archive from Apprentice Alf's Blog: http://apprenticealf.wordpress.com/
The is archive includes tools to remove DRM from:
@ -25,7 +25,7 @@ If you re-post these tools, a link to the blog would be appreciated.
DeDRM plugin for calibre (Mac OS X, Windows, and Linux)
-------------------------------------------------------
If you already use calibre, the quickest and easiest way, especially on Windows, to remove DRM from your ebooks is to install the DeDRM plugin from the DeDRM_calibre_plugin folder, following the instructions and configuration directions provided in the ReadMe and the help links.
Calibre is an open source freeware ebook library manager. It is the best tool around for keeping track of your ebooks. The DeDRM plugin for calibre provides the simplest way, especially on Windows, to remove DRM from your ebooks. Just install the DeDRM plugin from the DeDRM_calibre_plugin folder, following the instructions and configuration directions provided in the ReadMe and the help links.
Once installed and configured, you can simply add a DRM book to calibre and the DeDRMed version will be imported into the calibre database. Note that DRM removal only occurs on IMPORT not on CONVERSION or at any other time, not even conversion to other formats. If you have already imported DRM books you'll need to remove them from calibre and re-import them.
@ -60,10 +60,13 @@ For instructions, see the obok_plugin_ReadMe.txt file in the Obok_calibre_plugin
Other_Tools
-----------
This is a folder other tools that may be useful for DRMed ebooks from certain sources or for Linux users. Most users won't need any of these tools.
This is a folder of other tools that may be useful for DRMed ebooks from certain sources or for Linux users. Most users won't need any of these tools.
DRM_Key_Scripts
This folder contains python scripts that create or extract or fetch encryption keyfiles for Barnes and Noble ePubs, Adobe Digital Editions ePubs, Kindle for Mac/PC and Kindle for Android ebooks. These files are needed for the Windows stand-alone DeDRM application.
B&N_Download_Helper
A Javascript to enable a download button at the B&N website for ebooks that normally won't download to your PC. Another one only for the adventurous.
A Javascript to enable a download button at the B&N website for ebooks that normally won't download to your PC. Only for the adventurous.
DRM_Key_Scripts
This folder contains python scripts that create or extract or fetch encryption keyfiles for Barnes and Noble ePubs, Adobe Digital Editions ePubs, Kindle for Mac/PC and Kindle for Android ebooks.