]> xmof Git - DeDRM.git/commitdiff
tools v6.0.9
authorApprentice Alf <apprenticealf@gmail.com>
Sat, 7 Mar 2015 21:18:50 +0000 (21:18 +0000)
committerApprentice Alf <apprenticealf@gmail.com>
Sat, 7 Mar 2015 21:18:50 +0000 (21:18 +0000)
obok added to other tools

13 files changed:
DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/DeDRM_Help.htm
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/__init__.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/config.py
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/DeDRM_Help.htm
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/__init__.py
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/config.py
DeDRM_calibre_plugin/DeDRM_plugin.zip
DeDRM_calibre_plugin/DeDRM_plugin/DeDRM_Help.htm
DeDRM_calibre_plugin/DeDRM_plugin/__init__.py
DeDRM_calibre_plugin/DeDRM_plugin/config.py
Other_Tools/Kindle_for_Android_Patches/kindle_version_4.0.2.1/kindle4.0.2.1.patch [new file with mode: 0644]
Other_Tools/Kobo/obok_2.01.py [new file with mode: 0644]

index cfd9fcc7435fee6d9fb44124b9c56c3f0d7d01aa..6ed5ade09a5ae15d6910d2e0414456551b55b810 100644 (file)
        <key>CFBundleExecutable</key>
        <string>droplet</string>
        <key>CFBundleGetInfoString</key>
-       <string>DeDRM AppleScript 6.0.8. Written 2010–2013 by Apprentice Alf and others.</string>
+       <string>DeDRM AppleScript 6.0.9. Written 2010–2013 by Apprentice Alf and others.</string>
        <key>CFBundleIconFile</key>
        <string>DeDRM</string>
        <key>CFBundleIdentifier</key>
        <string>com.apple.ScriptEditor.id.707CCCD5-0C6C-4BEB-B67C-B6E866ADE85A</string>
        <key>CFBundleInfoDictionaryVersion</key>
-       <string>6.0.8</string>
+       <string>6.0.9</string>
        <key>CFBundleName</key>
        <string>DeDRM</string>
        <key>CFBundlePackageType</key>
        <string>APPL</string>
        <key>CFBundleShortVersionString</key>
-       <string>6.0.8</string>
+       <string>6.0.9</string>
        <key>CFBundleSignature</key>
        <string>dplt</string>
        <key>LSRequiresCarbon</key>
index 69edade3a069d6bff0605f4a302182f01f54d4aa..f0c51a8d13ba8c1c65e30a936af99f5677f3f95b 100644 (file)
@@ -17,7 +17,7 @@ p {margin-top: 0}
 
 <body>
 
-<h1>DeDRM Plugin <span class="version">(v6.0.0)</span></h1>
+<h1>DeDRM Plugin <span class="version">(v6.0.9)</span></h1>
 
 <p>This plugin removes DRM from ebooks when they are imported into calibre. If you already have DRMed ebooks in your calibre library, you will need to remove them and import them again.</p>
 
index 37d4cb155e503921e5717b541a2a6e3e6501dbb5..232b6bc9e1218443ceee67021e4299e4681a8334 100644 (file)
@@ -35,13 +35,14 @@ __docformat__ = 'restructuredtext en'
 #   6.0.6 - Fix up an incorrect function call
 #   6.0.7 - Error handling for incomplete PDF metadata
 #   6.0.8 - Fixes a Wine key issue and topaz support
+#   6.0.9 - Ported to work with newer versions of Calibre (moved to Qt5). Still supports older Qt4 versions.
 
 """
 Decrypt DRMed ebooks.
 """
 
 PLUGIN_NAME = u"DeDRM"
-PLUGIN_VERSION_TUPLE = (6, 0, 8)
+PLUGIN_VERSION_TUPLE = (6, 0, 9)
 PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE])
 # Include an html helpfile in the plugin's zipfile with the following name.
 RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
index 1e584768b04e559e852b2cf347b61b9ee10b0f26..b5c5300dc5e313e427c1585fdc11769da109e697 100644 (file)
@@ -9,16 +9,24 @@ __license__ = 'GPL v3'
 import os, traceback, json
 
 # PyQT4 modules (part of calibre).
-from PyQt4.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
+try:
+    from PyQt5.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
                       QGroupBox, QPushButton, QListWidget, QListWidgetItem,
-                      QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl, QString)
-from PyQt4 import QtGui
-
+                      QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl)
+except ImportError:
+    from PyQt4.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
+                      QGroupBox, QPushButton, QListWidget, QListWidgetItem,
+                      QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl)
+try:
+    from PyQt5 import Qt as QtGui
+except ImportError:
+    from PyQt4 import QtGui
+    
 from zipfile import ZipFile
 
 # calibre modules and constants.
 from calibre.gui2 import (error_dialog, question_dialog, info_dialog, open_url,
-                            choose_dir, choose_files)
+                            choose_dir, choose_files, choose_save_file)
 from calibre.utils.config import dynamic, config_dir, JSONConfig
 from calibre.constants import iswindows, isosx
 
@@ -267,7 +275,7 @@ class ManageKeysDialog(QDialog):
 
     def getwineprefix(self):
         if self.wineprefix is not None:
-            return unicode(self.wp_lineedit.text().toUtf8(), 'utf8').strip()
+            return unicode(self.wp_lineedit.text()).strip()
         return u""
 
     def populate_list(self):
@@ -316,7 +324,7 @@ class ManageKeysDialog(QDialog):
         if d.result() != d.Accepted:
             # rename cancelled or moot.
             return
-        keyname = unicode(self.listy.currentItem().text().toUtf8(),'utf8')
+        keyname = unicode(self.listy.currentItem().text())
         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]
@@ -328,7 +336,7 @@ class ManageKeysDialog(QDialog):
     def delete_key(self):
         if not self.listy.currentItem():
             return
-        keyname = unicode(self.listy.currentItem().text().toUtf8(), 'utf8')
+        keyname = unicode(self.listy.currentItem().text())
         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:
@@ -352,9 +360,10 @@ class ManageKeysDialog(QDialog):
         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)
+        unique_dlg_name = PLUGIN_NAME + u"import {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory
+        caption = u"Select {0} files to import".format(self.key_type_name)
+        filters = [(u"{0} files".format(self.key_type_name), [self.keyfile_ext])]
+        files = choose_files(self, unique_dlg_name, caption, filters, all_files=False)
         counter = 0
         skipped = 0
         if files:
@@ -408,17 +417,14 @@ class ManageKeysDialog(QDialog):
             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))
+        keyname = unicode(self.listy.currentItem().text())
+        unique_dlg_name = PLUGIN_NAME + u"export {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory
+        caption = u"Save {0} File as...".format(self.key_type_name)
+        filters = [(u"{0} Files".format(self.key_type_name), [u"{0}".format(self.keyfile_ext)])]
+        defaultname = u"{0}.{1}".format(keyname, self.keyfile_ext)
+        filename = choose_save_file(self, unique_dlg_name,  caption, filters, all_files=False, initial_filename=defaultname)
         if filename:
-            dynamic[PLUGIN_NAME + 'save_dir'] = os.path.split(filename)[0]
-            with file(filename, 'w') as fname:
+            with file(filename, 'wb') as fname:
                 if self.binary_file:
                     fname.write(self.plugin_keys[keyname].decode('hex'))
                 elif self.json_file:
@@ -458,7 +464,7 @@ class RenameKeyDialog(QDialog):
         self.resize(self.sizeHint())
 
     def accept(self):
-        if self.key_ledit.text().isEmpty() or unicode(self.key_ledit.text()).isspace():
+        if not unicode(self.key_ledit.text()) 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)
@@ -479,7 +485,7 @@ class RenameKeyDialog(QDialog):
 
     @property
     def key_name(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
 
 
@@ -553,7 +559,7 @@ class AddBandNKeyDialog(QDialog):
 
     @property
     def key_name(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     @property
     def key_value(self):
@@ -562,11 +568,11 @@ class AddBandNKeyDialog(QDialog):
 
     @property
     def user_name(self):
-        return unicode(self.name_ledit.text().toUtf8(), 'utf8').strip().lower().replace(' ','')
+        return unicode(self.name_ledit.text()).strip().lower().replace(' ','')
 
     @property
     def cc_number(self):
-        return unicode(self.cc_ledit.text().toUtf8(), 'utf8').strip().replace(' ', '').replace('-','')
+        return unicode(self.cc_ledit.text()).strip().replace(' ', '').replace('-','')
 
 
     def accept(self):
@@ -634,7 +640,7 @@ class AddEReaderDialog(QDialog):
 
     @property
     def key_name(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     @property
     def key_value(self):
@@ -643,11 +649,11 @@ class AddEReaderDialog(QDialog):
 
     @property
     def user_name(self):
-        return unicode(self.name_ledit.text().toUtf8(), 'utf8').strip().lower().replace(' ','')
+        return unicode(self.name_ledit.text()).strip().lower().replace(' ','')
 
     @property
     def cc_number(self):
-        return unicode(self.cc_ledit.text().toUtf8(), 'utf8').strip().replace(' ', '').replace('-','')
+        return unicode(self.cc_ledit.text()).strip().replace(' ', '').replace('-','')
 
 
     def accept(self):
@@ -719,7 +725,7 @@ class AddAdeptDialog(QDialog):
 
     @property
     def key_name(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     @property
     def key_value(self):
@@ -792,7 +798,7 @@ class AddKindleDialog(QDialog):
 
     @property
     def key_name(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     @property
     def key_value(self):
@@ -841,11 +847,11 @@ class AddSerialDialog(QDialog):
 
     @property
     def key_name(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     @property
     def key_value(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     def accept(self):
         if len(self.key_name) == 0 or self.key_name.isspace():
@@ -889,11 +895,11 @@ class AddPIDDialog(QDialog):
 
     @property
     def key_name(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     @property
     def key_value(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     def accept(self):
         if len(self.key_name) == 0 or self.key_name.isspace():
index 69edade3a069d6bff0605f4a302182f01f54d4aa..f0c51a8d13ba8c1c65e30a936af99f5677f3f95b 100644 (file)
@@ -17,7 +17,7 @@ p {margin-top: 0}
 
 <body>
 
-<h1>DeDRM Plugin <span class="version">(v6.0.0)</span></h1>
+<h1>DeDRM Plugin <span class="version">(v6.0.9)</span></h1>
 
 <p>This plugin removes DRM from ebooks when they are imported into calibre. If you already have DRMed ebooks in your calibre library, you will need to remove them and import them again.</p>
 
index 37d4cb155e503921e5717b541a2a6e3e6501dbb5..232b6bc9e1218443ceee67021e4299e4681a8334 100644 (file)
@@ -35,13 +35,14 @@ __docformat__ = 'restructuredtext en'
 #   6.0.6 - Fix up an incorrect function call
 #   6.0.7 - Error handling for incomplete PDF metadata
 #   6.0.8 - Fixes a Wine key issue and topaz support
+#   6.0.9 - Ported to work with newer versions of Calibre (moved to Qt5). Still supports older Qt4 versions.
 
 """
 Decrypt DRMed ebooks.
 """
 
 PLUGIN_NAME = u"DeDRM"
-PLUGIN_VERSION_TUPLE = (6, 0, 8)
+PLUGIN_VERSION_TUPLE = (6, 0, 9)
 PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE])
 # Include an html helpfile in the plugin's zipfile with the following name.
 RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
index 1e584768b04e559e852b2cf347b61b9ee10b0f26..b5c5300dc5e313e427c1585fdc11769da109e697 100644 (file)
@@ -9,16 +9,24 @@ __license__ = 'GPL v3'
 import os, traceback, json
 
 # PyQT4 modules (part of calibre).
-from PyQt4.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
+try:
+    from PyQt5.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
                       QGroupBox, QPushButton, QListWidget, QListWidgetItem,
-                      QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl, QString)
-from PyQt4 import QtGui
-
+                      QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl)
+except ImportError:
+    from PyQt4.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
+                      QGroupBox, QPushButton, QListWidget, QListWidgetItem,
+                      QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl)
+try:
+    from PyQt5 import Qt as QtGui
+except ImportError:
+    from PyQt4 import QtGui
+    
 from zipfile import ZipFile
 
 # calibre modules and constants.
 from calibre.gui2 import (error_dialog, question_dialog, info_dialog, open_url,
-                            choose_dir, choose_files)
+                            choose_dir, choose_files, choose_save_file)
 from calibre.utils.config import dynamic, config_dir, JSONConfig
 from calibre.constants import iswindows, isosx
 
@@ -267,7 +275,7 @@ class ManageKeysDialog(QDialog):
 
     def getwineprefix(self):
         if self.wineprefix is not None:
-            return unicode(self.wp_lineedit.text().toUtf8(), 'utf8').strip()
+            return unicode(self.wp_lineedit.text()).strip()
         return u""
 
     def populate_list(self):
@@ -316,7 +324,7 @@ class ManageKeysDialog(QDialog):
         if d.result() != d.Accepted:
             # rename cancelled or moot.
             return
-        keyname = unicode(self.listy.currentItem().text().toUtf8(),'utf8')
+        keyname = unicode(self.listy.currentItem().text())
         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]
@@ -328,7 +336,7 @@ class ManageKeysDialog(QDialog):
     def delete_key(self):
         if not self.listy.currentItem():
             return
-        keyname = unicode(self.listy.currentItem().text().toUtf8(), 'utf8')
+        keyname = unicode(self.listy.currentItem().text())
         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:
@@ -352,9 +360,10 @@ class ManageKeysDialog(QDialog):
         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)
+        unique_dlg_name = PLUGIN_NAME + u"import {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory
+        caption = u"Select {0} files to import".format(self.key_type_name)
+        filters = [(u"{0} files".format(self.key_type_name), [self.keyfile_ext])]
+        files = choose_files(self, unique_dlg_name, caption, filters, all_files=False)
         counter = 0
         skipped = 0
         if files:
@@ -408,17 +417,14 @@ class ManageKeysDialog(QDialog):
             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))
+        keyname = unicode(self.listy.currentItem().text())
+        unique_dlg_name = PLUGIN_NAME + u"export {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory
+        caption = u"Save {0} File as...".format(self.key_type_name)
+        filters = [(u"{0} Files".format(self.key_type_name), [u"{0}".format(self.keyfile_ext)])]
+        defaultname = u"{0}.{1}".format(keyname, self.keyfile_ext)
+        filename = choose_save_file(self, unique_dlg_name,  caption, filters, all_files=False, initial_filename=defaultname)
         if filename:
-            dynamic[PLUGIN_NAME + 'save_dir'] = os.path.split(filename)[0]
-            with file(filename, 'w') as fname:
+            with file(filename, 'wb') as fname:
                 if self.binary_file:
                     fname.write(self.plugin_keys[keyname].decode('hex'))
                 elif self.json_file:
@@ -458,7 +464,7 @@ class RenameKeyDialog(QDialog):
         self.resize(self.sizeHint())
 
     def accept(self):
-        if self.key_ledit.text().isEmpty() or unicode(self.key_ledit.text()).isspace():
+        if not unicode(self.key_ledit.text()) 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)
@@ -479,7 +485,7 @@ class RenameKeyDialog(QDialog):
 
     @property
     def key_name(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
 
 
@@ -553,7 +559,7 @@ class AddBandNKeyDialog(QDialog):
 
     @property
     def key_name(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     @property
     def key_value(self):
@@ -562,11 +568,11 @@ class AddBandNKeyDialog(QDialog):
 
     @property
     def user_name(self):
-        return unicode(self.name_ledit.text().toUtf8(), 'utf8').strip().lower().replace(' ','')
+        return unicode(self.name_ledit.text()).strip().lower().replace(' ','')
 
     @property
     def cc_number(self):
-        return unicode(self.cc_ledit.text().toUtf8(), 'utf8').strip().replace(' ', '').replace('-','')
+        return unicode(self.cc_ledit.text()).strip().replace(' ', '').replace('-','')
 
 
     def accept(self):
@@ -634,7 +640,7 @@ class AddEReaderDialog(QDialog):
 
     @property
     def key_name(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     @property
     def key_value(self):
@@ -643,11 +649,11 @@ class AddEReaderDialog(QDialog):
 
     @property
     def user_name(self):
-        return unicode(self.name_ledit.text().toUtf8(), 'utf8').strip().lower().replace(' ','')
+        return unicode(self.name_ledit.text()).strip().lower().replace(' ','')
 
     @property
     def cc_number(self):
-        return unicode(self.cc_ledit.text().toUtf8(), 'utf8').strip().replace(' ', '').replace('-','')
+        return unicode(self.cc_ledit.text()).strip().replace(' ', '').replace('-','')
 
 
     def accept(self):
@@ -719,7 +725,7 @@ class AddAdeptDialog(QDialog):
 
     @property
     def key_name(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     @property
     def key_value(self):
@@ -792,7 +798,7 @@ class AddKindleDialog(QDialog):
 
     @property
     def key_name(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     @property
     def key_value(self):
@@ -841,11 +847,11 @@ class AddSerialDialog(QDialog):
 
     @property
     def key_name(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     @property
     def key_value(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     def accept(self):
         if len(self.key_name) == 0 or self.key_name.isspace():
@@ -889,11 +895,11 @@ class AddPIDDialog(QDialog):
 
     @property
     def key_name(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     @property
     def key_value(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     def accept(self):
         if len(self.key_name) == 0 or self.key_name.isspace():
index 58d817484d8597a28a344d4cfdc375c419fe25b1..b314cb065dcb40946ea70c0767660592b9d9bfa6 100644 (file)
Binary files a/DeDRM_calibre_plugin/DeDRM_plugin.zip and b/DeDRM_calibre_plugin/DeDRM_plugin.zip differ
index 69edade3a069d6bff0605f4a302182f01f54d4aa..f0c51a8d13ba8c1c65e30a936af99f5677f3f95b 100644 (file)
@@ -17,7 +17,7 @@ p {margin-top: 0}
 
 <body>
 
-<h1>DeDRM Plugin <span class="version">(v6.0.0)</span></h1>
+<h1>DeDRM Plugin <span class="version">(v6.0.9)</span></h1>
 
 <p>This plugin removes DRM from ebooks when they are imported into calibre. If you already have DRMed ebooks in your calibre library, you will need to remove them and import them again.</p>
 
index 37d4cb155e503921e5717b541a2a6e3e6501dbb5..232b6bc9e1218443ceee67021e4299e4681a8334 100644 (file)
@@ -35,13 +35,14 @@ __docformat__ = 'restructuredtext en'
 #   6.0.6 - Fix up an incorrect function call
 #   6.0.7 - Error handling for incomplete PDF metadata
 #   6.0.8 - Fixes a Wine key issue and topaz support
+#   6.0.9 - Ported to work with newer versions of Calibre (moved to Qt5). Still supports older Qt4 versions.
 
 """
 Decrypt DRMed ebooks.
 """
 
 PLUGIN_NAME = u"DeDRM"
-PLUGIN_VERSION_TUPLE = (6, 0, 8)
+PLUGIN_VERSION_TUPLE = (6, 0, 9)
 PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE])
 # Include an html helpfile in the plugin's zipfile with the following name.
 RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
index 1e584768b04e559e852b2cf347b61b9ee10b0f26..b5c5300dc5e313e427c1585fdc11769da109e697 100644 (file)
@@ -9,16 +9,24 @@ __license__ = 'GPL v3'
 import os, traceback, json
 
 # PyQT4 modules (part of calibre).
-from PyQt4.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
+try:
+    from PyQt5.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
                       QGroupBox, QPushButton, QListWidget, QListWidgetItem,
-                      QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl, QString)
-from PyQt4 import QtGui
-
+                      QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl)
+except ImportError:
+    from PyQt4.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
+                      QGroupBox, QPushButton, QListWidget, QListWidgetItem,
+                      QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl)
+try:
+    from PyQt5 import Qt as QtGui
+except ImportError:
+    from PyQt4 import QtGui
+    
 from zipfile import ZipFile
 
 # calibre modules and constants.
 from calibre.gui2 import (error_dialog, question_dialog, info_dialog, open_url,
-                            choose_dir, choose_files)
+                            choose_dir, choose_files, choose_save_file)
 from calibre.utils.config import dynamic, config_dir, JSONConfig
 from calibre.constants import iswindows, isosx
 
@@ -267,7 +275,7 @@ class ManageKeysDialog(QDialog):
 
     def getwineprefix(self):
         if self.wineprefix is not None:
-            return unicode(self.wp_lineedit.text().toUtf8(), 'utf8').strip()
+            return unicode(self.wp_lineedit.text()).strip()
         return u""
 
     def populate_list(self):
@@ -316,7 +324,7 @@ class ManageKeysDialog(QDialog):
         if d.result() != d.Accepted:
             # rename cancelled or moot.
             return
-        keyname = unicode(self.listy.currentItem().text().toUtf8(),'utf8')
+        keyname = unicode(self.listy.currentItem().text())
         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]
@@ -328,7 +336,7 @@ class ManageKeysDialog(QDialog):
     def delete_key(self):
         if not self.listy.currentItem():
             return
-        keyname = unicode(self.listy.currentItem().text().toUtf8(), 'utf8')
+        keyname = unicode(self.listy.currentItem().text())
         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:
@@ -352,9 +360,10 @@ class ManageKeysDialog(QDialog):
         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)
+        unique_dlg_name = PLUGIN_NAME + u"import {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory
+        caption = u"Select {0} files to import".format(self.key_type_name)
+        filters = [(u"{0} files".format(self.key_type_name), [self.keyfile_ext])]
+        files = choose_files(self, unique_dlg_name, caption, filters, all_files=False)
         counter = 0
         skipped = 0
         if files:
@@ -408,17 +417,14 @@ class ManageKeysDialog(QDialog):
             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))
+        keyname = unicode(self.listy.currentItem().text())
+        unique_dlg_name = PLUGIN_NAME + u"export {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory
+        caption = u"Save {0} File as...".format(self.key_type_name)
+        filters = [(u"{0} Files".format(self.key_type_name), [u"{0}".format(self.keyfile_ext)])]
+        defaultname = u"{0}.{1}".format(keyname, self.keyfile_ext)
+        filename = choose_save_file(self, unique_dlg_name,  caption, filters, all_files=False, initial_filename=defaultname)
         if filename:
-            dynamic[PLUGIN_NAME + 'save_dir'] = os.path.split(filename)[0]
-            with file(filename, 'w') as fname:
+            with file(filename, 'wb') as fname:
                 if self.binary_file:
                     fname.write(self.plugin_keys[keyname].decode('hex'))
                 elif self.json_file:
@@ -458,7 +464,7 @@ class RenameKeyDialog(QDialog):
         self.resize(self.sizeHint())
 
     def accept(self):
-        if self.key_ledit.text().isEmpty() or unicode(self.key_ledit.text()).isspace():
+        if not unicode(self.key_ledit.text()) 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)
@@ -479,7 +485,7 @@ class RenameKeyDialog(QDialog):
 
     @property
     def key_name(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
 
 
@@ -553,7 +559,7 @@ class AddBandNKeyDialog(QDialog):
 
     @property
     def key_name(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     @property
     def key_value(self):
@@ -562,11 +568,11 @@ class AddBandNKeyDialog(QDialog):
 
     @property
     def user_name(self):
-        return unicode(self.name_ledit.text().toUtf8(), 'utf8').strip().lower().replace(' ','')
+        return unicode(self.name_ledit.text()).strip().lower().replace(' ','')
 
     @property
     def cc_number(self):
-        return unicode(self.cc_ledit.text().toUtf8(), 'utf8').strip().replace(' ', '').replace('-','')
+        return unicode(self.cc_ledit.text()).strip().replace(' ', '').replace('-','')
 
 
     def accept(self):
@@ -634,7 +640,7 @@ class AddEReaderDialog(QDialog):
 
     @property
     def key_name(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     @property
     def key_value(self):
@@ -643,11 +649,11 @@ class AddEReaderDialog(QDialog):
 
     @property
     def user_name(self):
-        return unicode(self.name_ledit.text().toUtf8(), 'utf8').strip().lower().replace(' ','')
+        return unicode(self.name_ledit.text()).strip().lower().replace(' ','')
 
     @property
     def cc_number(self):
-        return unicode(self.cc_ledit.text().toUtf8(), 'utf8').strip().replace(' ', '').replace('-','')
+        return unicode(self.cc_ledit.text()).strip().replace(' ', '').replace('-','')
 
 
     def accept(self):
@@ -719,7 +725,7 @@ class AddAdeptDialog(QDialog):
 
     @property
     def key_name(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     @property
     def key_value(self):
@@ -792,7 +798,7 @@ class AddKindleDialog(QDialog):
 
     @property
     def key_name(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     @property
     def key_value(self):
@@ -841,11 +847,11 @@ class AddSerialDialog(QDialog):
 
     @property
     def key_name(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     @property
     def key_value(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     def accept(self):
         if len(self.key_name) == 0 or self.key_name.isspace():
@@ -889,11 +895,11 @@ class AddPIDDialog(QDialog):
 
     @property
     def key_name(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     @property
     def key_value(self):
-        return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
+        return unicode(self.key_ledit.text()).strip()
 
     def accept(self):
         if len(self.key_name) == 0 or self.key_name.isspace():
diff --git a/Other_Tools/Kindle_for_Android_Patches/kindle_version_4.0.2.1/kindle4.0.2.1.patch b/Other_Tools/Kindle_for_Android_Patches/kindle_version_4.0.2.1/kindle4.0.2.1.patch
new file mode 100644 (file)
index 0000000..010f68f
--- /dev/null
@@ -0,0 +1,238 @@
+Only in kindle4.0.2.1: build
+diff -r -u10 kindle4.0.2.1_orig/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali kindle4.0.2.1/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali
+--- kindle4.0.2.1_orig/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali 2013-05-22 18:39:03.000000000 -0500
++++ kindle4.0.2.1/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali      2013-05-23 16:54:53.000000000 -0500
+@@ -36,20 +36,22 @@
+ .field private maxCpuSpeed:J
+ .field private maxMemory:J
+ .field private minCpuSpeed:J
+ .field private resources:Landroid/content/res/Resources;
+ .field private security:Lcom/mobipocket/android/library/reader/AndroidSecurity;
++.field private pidList:Ljava/lang/String;
++
+ .field private totalMemory:J
+ # direct methods
+ .method static constructor <clinit>()V
+     .locals 1
+     .prologue
+     .line 30
+     const-class v0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;
+@@ -72,20 +74,24 @@
+     .prologue
+     .line 130
+     invoke-direct {p0}, Ljava/lang/Object;-><init>()V
+     .line 131
+     iput-object p1, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->security:Lcom/mobipocket/android/library/reader/AndroidSecurity;
+     .line 132
+     iput-object p2, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->deviceType:Lcom/amazon/kcp/application/AmazonDeviceType;
++    const-string v0, "Open DRMed book to show PID list."
++
++    iput-object v0, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String;
++
+     .line 133
+     sget-object v0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->TAG:Ljava/lang/String;
+     new-instance v0, Ljava/lang/StringBuilder;
+     invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V
+     const-string v1, "Device Type is set to \""
+     invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
+@@ -1235,10 +1241,33 @@
+     move-result-wide v0
+     iput-wide v0, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->totalMemory:J
+     .line 308
+     :cond_0
+     iget-wide v0, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->totalMemory:J
+     return-wide v0
+ .end method
++
++.method public getPidList()Ljava/lang/String;
++    .locals 1
++
++    .prologue
++    .line 15
++    iget-object v0, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String;
++
++    return-object v0
++.end method
++
++.method public setPidList(Ljava/lang/String;)V
++    .locals 0
++    .parameter "value"
++
++    .prologue
++    .line 11
++    iput-object p1, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String;
++
++    .line 12
++    return-void
++.end method
++
+diff -r -u10 kindle4.0.2.1_orig/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali kindle4.0.2.1/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali
+--- kindle4.0.2.1_orig/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali       2013-05-22 18:39:03.000000000 -0500
++++ kindle4.0.2.1/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali    2013-05-23 16:55:58.000000000 -0500
+@@ -23,10 +23,16 @@
+ .end method
+ .method public abstract getDeviceTypeId()Ljava/lang/String;
+ .end method
+ .method public abstract getOsVersion()Ljava/lang/String;
+ .end method
+ .method public abstract getPid()Ljava/lang/String;
+ .end method
++
++.method public abstract getPidList()Ljava/lang/String;
++.end method
++
++.method public abstract setPidList(Ljava/lang/String;)V
++.end method
+diff -r -u10 kindle4.0.2.1_orig/smali/com/amazon/kcp/info/AboutActivity.smali kindle4.0.2.1/smali/com/amazon/kcp/info/AboutActivity.smali
+--- kindle4.0.2.1_orig/smali/com/amazon/kcp/info/AboutActivity.smali   2013-05-22 18:39:03.000000000 -0500
++++ kindle4.0.2.1/smali/com/amazon/kcp/info/AboutActivity.smali        2013-05-23 17:18:14.000000000 -0500
+@@ -486,20 +486,71 @@
+     .end local v2           #screenDpi:Ljava/lang/String;
+     :cond_0
+     iget-object v5, p0, Lcom/amazon/kcp/info/AboutActivity;->detailItemList:Ljava/util/List;
+     invoke-interface {v5, v0}, Ljava/util/List;->add(Ljava/lang/Object;)Z
+     .line 317
+     return-void
+ .end method
++.method private populatePIDList()V
++    .locals 7
++
++    .prologue
++    .line 313
++    invoke-static {}, Lcom/amazon/kcp/application/DeviceInformationProviderFactory;->getProvider()Lcom/amazon/kcp/application/IDeviceInformationProvider;
++
++    move-result-object v0
++
++    invoke-interface {v0}, Lcom/amazon/kcp/application/IDeviceInformationProvider;->getPidList()Ljava/lang/String;
++
++    move-result-object v1
++
++    .line 314
++    .local v1, PidList:Ljava/lang/String;
++    iget-object v3, p0, Lcom/amazon/kcp/info/AboutActivity;->groupItemList:Ljava/util/List;
++
++    new-instance v4, Lcom/amazon/kcp/info/AboutActivity$GroupItem;
++
++    const-string v5, "PID List"
++
++    const v6, 0x1
++
++    invoke-direct {v4, p0, v5, v6}, Lcom/amazon/kcp/info/AboutActivity$GroupItem;-><init>(Lcom/amazon/kcp/info/AboutActivity;Ljava/lang/String;Z)V
++
++    invoke-interface {v3, v4}, Ljava/util/List;->add(Ljava/lang/Object;)Z
++
++    .line 315
++    new-instance v2, Ljava/util/ArrayList;
++
++    invoke-direct {v2}, Ljava/util/ArrayList;-><init>()V
++
++    .line 316
++    .local v2, children:Ljava/util/List;,"Ljava/util/List<Lcom/amazon/kcp/info/AboutActivity$DetailItem;>;"
++    new-instance v3, Lcom/amazon/kcp/info/AboutActivity$DetailItem;
++
++    const-string v4, "PIDs"
++
++    invoke-direct {v3, p0, v4, v1}, Lcom/amazon/kcp/info/AboutActivity$DetailItem;-><init>(Lcom/amazon/kcp/info/AboutActivity;Ljava/lang/String;Ljava/lang/String;)V
++
++    invoke-interface {v2, v3}, Ljava/util/List;->add(Ljava/lang/Object;)Z
++
++    .line 317
++    iget-object v3, p0, Lcom/amazon/kcp/info/AboutActivity;->detailItemList:Ljava/util/List;
++
++    invoke-interface {v3, v2}, Ljava/util/List;->add(Ljava/lang/Object;)Z
++
++    .line 318
++    return-void
++.end method
++
+ .method private populateDisplayItems()V
+     .locals 1
+     .prologue
+     .line 171
+     iget-object v0, p0, Lcom/amazon/kcp/info/AboutActivity;->groupItemList:Ljava/util/List;
+     if-nez v0, :cond_0
+     .line 173
+@@ -531,20 +582,22 @@
+     .line 192
+     invoke-direct {p0}, Lcom/amazon/kcp/info/AboutActivity;->populateRamInformation()V
+     .line 193
+     invoke-direct {p0}, Lcom/amazon/kcp/info/AboutActivity;->populateStorageInformation()V
+     .line 194
+     invoke-direct {p0}, Lcom/amazon/kcp/info/AboutActivity;->populateDisplayInformation()V
++    invoke-direct {p0}, Lcom/amazon/kcp/info/AboutActivity;->populatePIDList()V
++
+     .line 195
+     return-void
+     .line 177
+     :cond_0
+     iget-object v0, p0, Lcom/amazon/kcp/info/AboutActivity;->groupItemList:Ljava/util/List;
+     invoke-interface {v0}, Ljava/util/List;->clear()V
+     goto :goto_0
+diff -r -u10 kindle4.0.2.1_orig/smali/com/amazon/system/security/Security.smali kindle4.0.2.1/smali/com/amazon/system/security/Security.smali
+--- kindle4.0.2.1_orig/smali/com/amazon/system/security/Security.smali 2013-05-22 18:39:04.000000000 -0500
++++ kindle4.0.2.1/smali/com/amazon/system/security/Security.smali      2013-05-23 17:19:05.000000000 -0500
+@@ -920,20 +920,30 @@
+     .line 350
+     :cond_2
+     add-int/lit8 v8, v8, 0x1
+     .line 351
+     sget-object v0, Lcom/amazon/system/security/Security;->CUSTOM_PID_FOR_BUNDLED_DICTIONARY_DRM:Ljava/lang/String;
+     aput-object v0, v6, v8
++    invoke-static {}, Lcom/amazon/kcp/application/DeviceInformationProviderFactory;->getProvider()Lcom/amazon/kcp/application/IDeviceInformationProvider;
++
++    move-result-object v5
++
++    invoke-static {v6}, Ljava/util/Arrays;->toString([Ljava/lang/Object;)Ljava/lang/String;
++
++    move-result-object v2
++
++    invoke-interface {v5, v2}, Lcom/amazon/kcp/application/IDeviceInformationProvider;->setPidList(Ljava/lang/String;)V
++
+     .line 353
+     return-object v6
+ .end method
+ # virtual methods
+ .method public customDrmOnly()I
+     .locals 1
+     .prologue
diff --git a/Other_Tools/Kobo/obok_2.01.py b/Other_Tools/Kobo/obok_2.01.py
new file mode 100644 (file)
index 0000000..3ed7cbd
--- /dev/null
@@ -0,0 +1,231 @@
+#!/usr/bin/env python
+#
+# Updated September 2013 by Anon
+# Version 2.01
+# Incorporated minor fixes posted at Apprentice Alf's.
+#
+# Updates July 2012 by Michael Newton
+# PWSD ID is no longer a MAC address, but should always
+# be stored in the registry. Script now works with OS X
+# and checks plist for values instead of registry. Must
+# have biplist installed for OS X support.
+#
+##########################################################
+#                    KOBO DRM CRACK BY                   #
+#                      PHYSISTICATED                     #
+##########################################################
+# This app was made for Python 2.7 on Windows 32-bit
+#
+# This app needs pycrypto - get from here:
+# http://www.voidspace.org.uk/python/modules.shtml
+#
+# Usage: obok.py
+# Choose the book you want to decrypt
+#
+# Shouts to my krew - you know who you are - and one in
+# particular who gave me a lot of help with this - thank
+# you so much!
+#
+# Kopimi /K\
+# Keep sharing, keep copying, but remember that nothing is
+# for free - make sure you compensate your favorite
+# authors - and cut out the middle man whenever possible
+# ;) ;) ;)
+#
+# DRM AUTOPSY
+# The Kobo DRM was incredibly easy to crack, but it took
+# me months to get around to making this. Here's the
+# basics of how it works:
+# 1: Get MAC address of first NIC in ipconfig (sometimes 
+# stored in registry as pwsdid)
+# 2: Get user ID (stored in tons of places, this gets it
+# from HKEY_CURRENT_USER\Software\Kobo\Kobo Desktop 
+# Edition\Browser\cookies)
+# 3: Concatenate and SHA256, take the second half - this
+# is your master key
+# 4: Open %LOCALAPPDATA%\Kobo Desktop Editions\Kobo.sqlite
+# and dump content_keys
+# 5: Unbase64 the keys, then decode these with the master
+# key - these are your page keys
+# 6: Unzip EPUB of your choice, decrypt each page with its
+# page key, then zip back up again
+#
+# WHY USE THIS WHEN INEPT WORKS FINE? (adobe DRM stripper)
+# Inept works very well, but authors on Kobo can choose
+# what DRM they want to use - and some have chosen not to
+# let people download them with Adobe Digital Editions -
+# they would rather lock you into a single platform.
+#
+# With Obok, you can sync Kobo Desktop, decrypt all your
+# ebooks, and then use them on whatever device you want
+# - you bought them, you own them, you can do what you
+# like with them.
+#
+# Obok is Kobo backwards, but it is also means "next to"
+# in Polish.
+# When you buy a real book, it is right next to you. You
+# can read it at home, at work, on a train, you can lend
+# it to a friend, you can scribble on it, and add your own
+# explanations/translations.
+#
+# Obok gives you this power over your ebooks - no longer
+# are you restricted to one device. This allows you to
+# embed foreign fonts into your books, as older Kobo's
+# can't display them properly. You can read your books
+# on your phones, in different PC readers, and different
+# ereader devices. You can share them with your friends
+# too, if you like - you can do that with a real book
+# after all.
+# 
+"""
+Decrypt Kobo encrypted EPUB books.
+"""
+
+import os
+import sys
+if sys.platform.startswith('win'):
+    import _winreg
+elif sys.platform.startswith('darwin'):
+    from biplist import readPlist
+import re
+import string
+import hashlib
+import sqlite3
+import base64
+import binascii
+import zipfile
+from Crypto.Cipher import AES
+
+def SHA256(raw):
+    return hashlib.sha256(raw).hexdigest()
+
+def RemoveAESPadding(contents):
+    lastchar = binascii.b2a_hex(contents[-1:])
+    strlen = int(lastchar, 16)
+    padding = strlen
+    if(strlen == 1):
+        return contents[:-1]
+    if(strlen < 16):
+        for i in range(strlen):
+            testchar = binascii.b2a_hex(contents[-strlen:-(strlen-1)])
+            if(testchar != lastchar):
+                padding = 0
+    if(padding > 0):
+        contents = contents[:-padding]
+    return contents
+
+def GetVolumeKeys(dbase, enc):
+    volumekeys = {}
+    for row in dbase.execute("SELECT * from content_keys"):
+        if(row[0] not in volumekeys):
+            volumekeys[row[0]] = {}
+        volumekeys[row[0]][row[1]] = {}
+        volumekeys[row[0]][row[1]]["encryptedkey"] = base64.b64decode(row[2])
+        volumekeys[row[0]][row[1]]["decryptedkey"] = enc.decrypt(volumekeys[row[0]][row[1]]["encryptedkey"])
+    # get book name
+    for key in volumekeys.keys():
+        volumekeys[key]["title"] = dbase.execute("SELECT Title from content where ContentID = '%s'" % (key)).fetchone()[0]
+    return volumekeys
+
+def ByteArrayToString(bytearr):
+    wincheck = re.match("@ByteArray\\((.+)\\)", bytearr)
+    if wincheck:
+        return wincheck.group(1)
+    return bytearr
+
+def GetUserHexKey(prefs = ""):
+    "find wsuid and pwsdid"
+    wsuid = ""
+    pwsdid = ""
+    if sys.platform.startswith('win'):
+        regkey_browser = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, "Software\\Kobo\\Kobo Desktop Edition\\Browser")
+        cookies = _winreg.QueryValueEx(regkey_browser, "cookies")
+        bytearrays = cookies[0]
+    elif sys.platform.startswith('darwin'):
+        cookies = readPlist(prefs)
+        bytearrays = cookies["Browser.cookies"]
+    for bytearr in bytearrays:
+        cookie = ByteArrayToString(bytearr)
+        print cookie
+        wsuidcheck = re.match("^wsuid=([0-9a-f-]+)", cookie)
+        if(wsuidcheck):
+            wsuid = wsuidcheck.group(1)
+        pwsdidcheck = re.match("^pwsdid=([0-9a-f-]+)", cookie)
+        if (pwsdidcheck):
+            pwsdid = pwsdidcheck.group(1)
+
+    if(wsuid == "" or pwsdid == ""):
+        print "wsuid or pwsdid key not found :/"
+        exit()
+    preuserkey = string.join((pwsdid, wsuid), "")
+    print SHA256(pwsdid)
+    userkey = SHA256(preuserkey)
+    return userkey[32:]
+
+# get dirs
+if sys.platform.startswith('win'):
+    delim = "\\"
+    if (sys.getwindowsversion().major > 5):
+        kobodir = string.join((os.environ['LOCALAPPDATA'], "Kobo\\Kobo Desktop Edition"), delim)
+    else:
+        kobodir = string.join((os.environ['USERPROFILE'], "Local Settings\\Application Data\\Kobo\\Kobo Desktop Edition"), delim)
+    prefs = ""
+elif sys.platform.startswith('darwin'):
+    delim = "/"
+    kobodir = string.join((os.environ['HOME'], "Library/Application Support/Kobo/Kobo Desktop Edition"), delim)
+    prefs = string.join((os.environ['HOME'], "Library/Preferences/com.kobo.Kobo Desktop Edition.plist"), delim)
+sqlitefile = string.join((kobodir, "Kobo.sqlite"), delim)
+bookdir = string.join((kobodir, "kepub"), delim)
+
+# get key
+userkeyhex = GetUserHexKey(prefs)
+# load into AES
+userkey = binascii.a2b_hex(userkeyhex)
+enc = AES.new(userkey, AES.MODE_ECB)
+
+# open sqlite
+conn = sqlite3.connect(sqlitefile)
+dbcursor = conn.cursor()
+# get volume keys
+volumekeys = GetVolumeKeys(dbcursor, enc)
+
+# choose a volumeID
+
+volumeid = ""
+print "Choose a book to decrypt:"
+i = 1
+for key in volumekeys.keys():
+    print "%d: %s" % (i, volumekeys[key]["title"])
+    i += 1
+
+num = input("...")
+
+i = 1
+for key in volumekeys.keys():
+    if(i == num):
+        volumeid = key
+    i += 1
+
+if(volumeid == ""):
+    exit()
+
+zippath = string.join((bookdir, volumeid), delim)
+
+z = zipfile.ZipFile(zippath, "r")
+# make filename out of Unicode alphanumeric and whitespace equivalents from title
+outname = "%s.epub" % (re.sub("[^\s\w]", "", volumekeys[volumeid]["title"], 0, re.UNICODE))
+zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
+for filename in z.namelist():
+    #print filename
+    # read in and decrypt
+    if(filename in volumekeys[volumeid]):
+        # do decrypted version
+        pagekey = volumekeys[volumeid][filename]["decryptedkey"]
+        penc = AES.new(pagekey, AES.MODE_ECB)
+        contents = RemoveAESPadding(penc.decrypt(z.read(filename)))
+        # need to fix padding
+        zout.writestr(filename, contents)
+    else:
+        zout.writestr(filename, z.read(filename))
+
+print "Book saved as %s%s%s" % (os.getcwd(), delim, outname)
\ No newline at end of file