Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10963f6011 | ||
|
|
72968d2124 | ||
|
|
3e95168972 | ||
|
|
ecf1d76d90 | ||
|
|
e59f0f346d | ||
|
|
b6046d3f4b | ||
|
|
a863a4856a | ||
|
|
a13d08c3bc | ||
|
|
9434751a72 | ||
|
|
fc156852a4 | ||
|
|
0c67fd43a2 | ||
|
|
00a5c4e1d1 | ||
|
|
4ea0d81144 | ||
|
|
b1cccf4b25 | ||
|
|
fe6074949b | ||
|
|
2db7ee8894 | ||
|
|
93d8758462 | ||
|
|
f97bc078db | ||
|
|
2e96db6cdc | ||
|
|
0d530c0c46 | ||
|
|
488924d443 | ||
|
|
07485be2c0 | ||
|
|
e5e269fbae | ||
|
|
d54dc38c2d | ||
|
|
3c322f3695 | ||
|
|
c112c28f58 | ||
|
|
b8606cd182 | ||
|
|
f2190a6755 | ||
|
|
33d8a63f61 |
Binary file not shown.
@@ -24,7 +24,7 @@
|
|||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>droplet</string>
|
<string>droplet</string>
|
||||||
<key>CFBundleGetInfoString</key>
|
<key>CFBundleGetInfoString</key>
|
||||||
<string>DeDRM AppleScript 6.3.2 Written 2010–2015 by Apprentice Alf et al.</string>
|
<string>DeDRM AppleScript 6.3.5 Written 2010–2016 by Apprentice Alf et al.</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>DeDRM</string>
|
<string>DeDRM</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>6.3.2</string>
|
<string>6.3.5</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>dplt</string>
|
<string>dplt</string>
|
||||||
<key>LSRequiresCarbon</key>
|
<key>LSRequiresCarbon</key>
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ __docformat__ = 'restructuredtext en'
|
|||||||
# 6.3.0 - Added in Kindle for Android serial number solution
|
# 6.3.0 - Added in Kindle for Android serial number solution
|
||||||
# 6.3.1 - Version number bump for clarity
|
# 6.3.1 - Version number bump for clarity
|
||||||
# 6.3.2 - Fixed Kindle for Android help file
|
# 6.3.2 - Fixed Kindle for Android help file
|
||||||
|
# 6.3.3 - Bug fix for Kindle for PC support
|
||||||
|
# 6.3.4 - Fixes for Kindle for Android, Linux, and Kobo 3.17
|
||||||
|
# 6.3.5 - Fixes for Linux, and Kobo 3.19 and more logging
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -51,7 +54,7 @@ Decrypt DRMed ebooks.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
PLUGIN_NAME = u"DeDRM"
|
PLUGIN_NAME = u"DeDRM"
|
||||||
PLUGIN_VERSION_TUPLE = (6, 3, 2)
|
PLUGIN_VERSION_TUPLE = (6, 3, 5)
|
||||||
PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE])
|
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.
|
# Include an html helpfile in the plugin's zipfile with the following name.
|
||||||
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
|
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
|
||||||
@@ -94,7 +97,7 @@ class DeDRM(FileTypePlugin):
|
|||||||
supported_platforms = ['linux', 'osx', 'windows']
|
supported_platforms = ['linux', 'osx', 'windows']
|
||||||
author = u"Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
|
author = u"Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
|
||||||
version = PLUGIN_VERSION_TUPLE
|
version = PLUGIN_VERSION_TUPLE
|
||||||
minimum_calibre_version = (0, 7, 55) # Compiled python libraries cannot be imported in earlier versions.
|
minimum_calibre_version = (1, 0, 0) # Compiled python libraries cannot be imported in earlier versions.
|
||||||
file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','tpz'])
|
file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','tpz'])
|
||||||
on_import = True
|
on_import = True
|
||||||
priority = 600
|
priority = 600
|
||||||
@@ -147,7 +150,7 @@ class DeDRM(FileTypePlugin):
|
|||||||
try:
|
try:
|
||||||
open(file_path,'wb').write(data)
|
open(file_path,'wb').write(data)
|
||||||
except:
|
except:
|
||||||
print u"{0} v{1}: Exception when copying needed library files after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
print u"{0} v{1}: Exception when copying needed library files".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -294,11 +297,15 @@ class DeDRM(FileTypePlugin):
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
result = 1
|
result = 1
|
||||||
|
|
||||||
of.close()
|
try:
|
||||||
|
of.close()
|
||||||
|
except:
|
||||||
|
print u"{0} v{1}: Exception closing temporary file after {2:.1f} seconds. Ignored.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
|
|
||||||
if result == 0:
|
if result == 0:
|
||||||
# Decryption was successful.
|
# Decryption was successful.
|
||||||
# Return the modified PersistentTemporary file to calibre.
|
# Return the modified PersistentTemporary file to calibre.
|
||||||
|
print u"{0} v{1}: Decrypted with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)
|
||||||
return of.name
|
return of.name
|
||||||
|
|
||||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)
|
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)
|
||||||
@@ -358,6 +365,7 @@ class DeDRM(FileTypePlugin):
|
|||||||
except:
|
except:
|
||||||
print u"{0} v{1}: Exception when saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
print u"{0} v{1}: Exception when saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
print u"{0} v{1}: Decrypted with new default key after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||||
# Return the modified PersistentTemporary file to calibre.
|
# Return the modified PersistentTemporary file to calibre.
|
||||||
return of.name
|
return of.name
|
||||||
|
|
||||||
@@ -523,7 +531,7 @@ class DeDRM(FileTypePlugin):
|
|||||||
if len(newkeys) > 0:
|
if len(newkeys) > 0:
|
||||||
print u"{0} v{1}: Found {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")
|
print u"{0} v{1}: Found {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")
|
||||||
try:
|
try:
|
||||||
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],self.starttime)
|
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],[],self.starttime)
|
||||||
decoded = True
|
decoded = True
|
||||||
# store the new successful keys in the defaults
|
# store the new successful keys in the defaults
|
||||||
print u"{0} v{1}: Saving {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")
|
print u"{0} v{1}: Saving {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")
|
||||||
|
|||||||
@@ -16,16 +16,18 @@ from __future__ import with_statement
|
|||||||
# - and added in unicode command line support
|
# - and added in unicode command line support
|
||||||
# 1.3 - added in TkInter interface, output to a file
|
# 1.3 - added in TkInter interface, output to a file
|
||||||
# 1.4 - Fix some problems identified by Aldo Bleeker
|
# 1.4 - Fix some problems identified by Aldo Bleeker
|
||||||
|
# 1.5 - Fix another problem identified by Aldo Bleeker
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Retrieve Kindle for Android Serial Number.
|
Retrieve Kindle for Android Serial Number.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = '1.4'
|
__version__ = '1.5'
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import traceback
|
||||||
import getopt
|
import getopt
|
||||||
import tempfile
|
import tempfile
|
||||||
import zlib
|
import zlib
|
||||||
@@ -220,20 +222,30 @@ def get_serials2(path=STORAGE2):
|
|||||||
userdata_keys = cursor.fetchall()
|
userdata_keys = cursor.fetchall()
|
||||||
dsns = []
|
dsns = []
|
||||||
for userdata_row in userdata_keys:
|
for userdata_row in userdata_keys:
|
||||||
if userdata_row:
|
try:
|
||||||
userdata_utf8 = userdata_row[0].encode('utf8')
|
if userdata_row and userdata_row[0]:
|
||||||
if len(userdata_utf8) > 0:
|
userdata_utf8 = userdata_row[0].encode('utf8')
|
||||||
dsns.append(userdata_utf8)
|
if len(userdata_utf8) > 0:
|
||||||
|
dsns.append(userdata_utf8)
|
||||||
|
except:
|
||||||
|
print "Error getting one of the device serial name keys"
|
||||||
|
traceback.print_exc()
|
||||||
|
pass
|
||||||
dsns = list(set(dsns))
|
dsns = list(set(dsns))
|
||||||
|
|
||||||
cursor.execute('''select userdata_value from userdata where userdata_key like '%/%kindle.account.tokens%' ''')
|
cursor.execute('''select userdata_value from userdata where userdata_key like '%/%kindle.account.tokens%' ''')
|
||||||
userdata_keys = cursor.fetchall()
|
userdata_keys = cursor.fetchall()
|
||||||
tokens = []
|
tokens = []
|
||||||
for userdata_row in userdata_keys:
|
for userdata_row in userdata_keys:
|
||||||
if userdata_row:
|
try:
|
||||||
userdata_utf8 = userdata_row[0].encode('utf8')
|
if userdata_row and userdata_row[0]:
|
||||||
if len(userdata_utf8) > 0:
|
userdata_utf8 = userdata_row[0].encode('utf8')
|
||||||
tokens.append(userdata_utf8)
|
if len(userdata_utf8) > 0:
|
||||||
|
tokens.append(userdata_utf8)
|
||||||
|
except:
|
||||||
|
print "Error getting one of the account token keys"
|
||||||
|
traceback.print_exc()
|
||||||
|
pass
|
||||||
tokens = list(set(tokens))
|
tokens = list(set(tokens))
|
||||||
|
|
||||||
serials = []
|
serials = []
|
||||||
@@ -377,7 +389,6 @@ def gui_main():
|
|||||||
import Tkconstants
|
import Tkconstants
|
||||||
import tkMessageBox
|
import tkMessageBox
|
||||||
import tkFileDialog
|
import tkFileDialog
|
||||||
import traceback
|
|
||||||
except:
|
except:
|
||||||
print "Tkinter not installed"
|
print "Tkinter not installed"
|
||||||
return cli_main()
|
return cli_main()
|
||||||
|
|||||||
@@ -3,18 +3,19 @@
|
|||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
|
||||||
# ineptepub.pyw, version 6.1
|
# ineptepub.pyw, version 6.3
|
||||||
# Copyright © 2009-2010 by i♥cabbages
|
# Copyright © 2009-2010 by i♥cabbages
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
# Released under the terms of the GNU General Public Licence, version 3
|
||||||
# <http://www.gnu.org/licenses/>
|
# <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf
|
# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf
|
||||||
|
# Modified 2015–2016 by Apprentice Harper
|
||||||
|
|
||||||
# Windows users: Before running this program, you must first install Python 2.6
|
# Windows users: Before running this program, you must first install Python 2.7
|
||||||
# from <http://www.python.org/download/> and PyCrypto from
|
# from <http://www.python.org/download/> and PyCrypto from
|
||||||
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
|
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
|
||||||
# install the version for Python 2.6). Save this script file as
|
# install the version for Python 2.7). Save this script file as
|
||||||
# ineptepub.pyw and double-click on it to run it.
|
# ineptepub.pyw and double-click on it to run it.
|
||||||
#
|
#
|
||||||
# Mac OS X users: Save this script file as ineptepub.pyw. You can run this
|
# Mac OS X users: Save this script file as ineptepub.pyw. You can run this
|
||||||
@@ -38,13 +39,14 @@ from __future__ import with_statement
|
|||||||
# 6.0 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
# 6.0 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||||
# 6.1 - Work if TkInter is missing
|
# 6.1 - Work if TkInter is missing
|
||||||
# 6.2 - Handle UTF-8 file names inside an ePub, fix by Jose Luis
|
# 6.2 - Handle UTF-8 file names inside an ePub, fix by Jose Luis
|
||||||
|
# 6.3 - Add additional check on DER file sanity
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Decrypt Adobe Digital Editions encrypted ePub books.
|
Decrypt Adobe Digital Editions encrypted ePub books.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "6.2"
|
__version__ = "6.3"
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@@ -169,9 +171,14 @@ def _load_crypto_libcrypto():
|
|||||||
def __init__(self, der):
|
def __init__(self, der):
|
||||||
buf = create_string_buffer(der)
|
buf = create_string_buffer(der)
|
||||||
pp = c_char_pp(cast(buf, c_char_p))
|
pp = c_char_pp(cast(buf, c_char_p))
|
||||||
rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
|
rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
|
||||||
if rsa is None:
|
if rsa is None:
|
||||||
raise ADEPTError('Error parsing ADEPT user key DER')
|
raise ADEPTError('Error parsing ADEPT user key DER')
|
||||||
|
# check if pointer is not NULL
|
||||||
|
try:
|
||||||
|
c = self._rsa.contents
|
||||||
|
except ValueError:
|
||||||
|
raise ADEPTError('Error parsing ADEPT user key DER')
|
||||||
|
|
||||||
def decrypt(self, from_):
|
def decrypt(self, from_):
|
||||||
rsa = self._rsa
|
rsa = self._rsa
|
||||||
@@ -313,6 +320,12 @@ def _load_crypto_pycrypto():
|
|||||||
key = [key.getChild(x).value for x in xrange(1, 4)]
|
key = [key.getChild(x).value for x in xrange(1, 4)]
|
||||||
key = [self.bytesToNumber(v) for v in key]
|
key = [self.bytesToNumber(v) for v in key]
|
||||||
self._rsa = _RSA.construct(key)
|
self._rsa = _RSA.construct(key)
|
||||||
|
# check if pointer is not NULL
|
||||||
|
try:
|
||||||
|
c = self._rsa.contents
|
||||||
|
except ValueError:
|
||||||
|
raise ADEPTError('Error parsing ADEPT user key DER')
|
||||||
|
|
||||||
|
|
||||||
def bytesToNumber(self, bytes):
|
def bytesToNumber(self, bytes):
|
||||||
total = 0L
|
total = 0L
|
||||||
|
|||||||
@@ -3,18 +3,19 @@
|
|||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
|
||||||
# ineptpdf.pyw, version 7.11
|
# ineptpdf.pyw, version 8.0.2
|
||||||
# Copyright © 2009-2010 by i♥cabbages
|
# Copyright © 2009-2010 by i♥cabbages
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
# Released under the terms of the GNU General Public Licence, version 3
|
||||||
# <http://www.gnu.org/licenses/>
|
# <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf
|
# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf
|
||||||
|
# Modified 2015-2016 by Apprentice Harper
|
||||||
|
|
||||||
# Windows users: Before running this program, you must first install Python 2.6
|
# Windows users: Before running this program, you must first install Python 2.7
|
||||||
# from <http://www.python.org/download/> and PyCrypto from
|
# from <http://www.python.org/download/> and PyCrypto from
|
||||||
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
|
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
|
||||||
# install the version for Python 2.6). Save this script file as
|
# install the version for Python 2.7). Save this script file as
|
||||||
# ineptpdf.pyw and double-click on it to run it.
|
# ineptpdf.pyw and double-click on it to run it.
|
||||||
#
|
#
|
||||||
# Mac OS X users: Save this script file as ineptpdf.pyw. You can run this
|
# Mac OS X users: Save this script file as ineptpdf.pyw. You can run this
|
||||||
@@ -53,13 +54,15 @@ from __future__ import with_statement
|
|||||||
# 7.14 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
# 7.14 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||||
# 8.0 - Work if TkInter is missing
|
# 8.0 - Work if TkInter is missing
|
||||||
# 8.0.1 - Broken Metadata fix.
|
# 8.0.1 - Broken Metadata fix.
|
||||||
|
# 8.0.2 - Add additional check on DER file sanity
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Decrypts Adobe ADEPT-encrypted PDF files.
|
Decrypts Adobe ADEPT-encrypted PDF files.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "8.0.1"
|
__version__ = "8.0.2"
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@@ -198,6 +201,11 @@ def _load_crypto_libcrypto():
|
|||||||
rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
|
rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
|
||||||
if rsa is None:
|
if rsa is None:
|
||||||
raise ADEPTError('Error parsing ADEPT user key DER')
|
raise ADEPTError('Error parsing ADEPT user key DER')
|
||||||
|
# check if pointer is not NULL
|
||||||
|
try:
|
||||||
|
c = self._rsa.contents
|
||||||
|
except ValueError:
|
||||||
|
raise ADEPTError('Error parsing ADEPT user key DER')
|
||||||
|
|
||||||
def decrypt(self, from_):
|
def decrypt(self, from_):
|
||||||
rsa = self._rsa
|
rsa = self._rsa
|
||||||
@@ -383,6 +391,11 @@ def _load_crypto_pycrypto():
|
|||||||
key = [key.getChild(x).value for x in xrange(1, 4)]
|
key = [key.getChild(x).value for x in xrange(1, 4)]
|
||||||
key = [self.bytesToNumber(v) for v in key]
|
key = [self.bytesToNumber(v) for v in key]
|
||||||
self._rsa = _RSA.construct(key)
|
self._rsa = _RSA.construct(key)
|
||||||
|
# check if pointer is not NULL
|
||||||
|
try:
|
||||||
|
c = self._rsa.contents
|
||||||
|
except ValueError:
|
||||||
|
raise ADEPTError('Error parsing ADEPT user key DER')
|
||||||
|
|
||||||
def bytesToNumber(self, bytes):
|
def bytesToNumber(self, bytes):
|
||||||
total = 0L
|
total = 0L
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
|
|||||||
if not os.path.exists(outdirpath):
|
if not os.path.exists(outdirpath):
|
||||||
os.makedirs(outdirpath)
|
os.makedirs(outdirpath)
|
||||||
|
|
||||||
|
wineprefix = os.path.abspath(os.path.expanduser(os.path.expandvars(wineprefix)))
|
||||||
if wineprefix != "" and os.path.exists(wineprefix):
|
if wineprefix != "" and os.path.exists(wineprefix):
|
||||||
cmdline = u"WINEPREFIX=\"{2}\" wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
cmdline = u"WINEPREFIX=\"{2}\" wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
||||||
else:
|
else:
|
||||||
@@ -38,8 +39,20 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
|
|||||||
result = p2.wait("wait")
|
result = p2.wait("wait")
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
print u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
|
print u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
|
||||||
return []
|
if wineprefix != "" and os.path.exists(wineprefix):
|
||||||
|
cmdline = u"WINEPREFIX=\"{2}\" wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
||||||
|
else:
|
||||||
|
cmdline = u"wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath)
|
||||||
|
print u"{0} v{1}: Command line: “{2}”".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline)
|
||||||
|
|
||||||
|
try:
|
||||||
|
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||||
|
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False)
|
||||||
|
result = p2.wait("wait")
|
||||||
|
except Exception, e:
|
||||||
|
print u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
|
||||||
|
|
||||||
|
# try finding winekeys anyway, even if above code errored
|
||||||
winekeys = []
|
winekeys = []
|
||||||
# get any files with extension in the output dir
|
# get any files with extension in the output dir
|
||||||
files = [f for f in os.listdir(outdirpath) if f.endswith(extension)]
|
files = [f for f in os.listdir(outdirpath) if f.endswith(extension)]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# DeDRM.pyw
|
# DeDRM.pyw
|
||||||
# Copyright 2010-2015 some_updates, Apprentice Alf and Apprentice Harper
|
# Copyright 2010-2016 some_updates, Apprentice Alf and Apprentice Harper
|
||||||
|
|
||||||
# Revision history:
|
# Revision history:
|
||||||
# 6.0.0 - Release along with unified plugin
|
# 6.0.0 - Release along with unified plugin
|
||||||
@@ -17,8 +17,11 @@
|
|||||||
# 6.3.0 - Add in Android support
|
# 6.3.0 - Add in Android support
|
||||||
# 6.3.1 - Version bump for clarity
|
# 6.3.1 - Version bump for clarity
|
||||||
# 6.3.2 - Version bump to match plugin
|
# 6.3.2 - Version bump to match plugin
|
||||||
|
# 6.3.3 - Version bump to match plugin
|
||||||
|
# 6.3.4 - Version bump to match plugin
|
||||||
|
# 6.3.5 - Version bump to match plugin
|
||||||
|
|
||||||
__version__ = '6.3.2'
|
__version__ = '6.3.5'
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os, os.path
|
import os, os.path
|
||||||
@@ -44,6 +44,9 @@ __docformat__ = 'restructuredtext en'
|
|||||||
# 6.3.0 - Added in Kindle for Android serial number solution
|
# 6.3.0 - Added in Kindle for Android serial number solution
|
||||||
# 6.3.1 - Version number bump for clarity
|
# 6.3.1 - Version number bump for clarity
|
||||||
# 6.3.2 - Fixed Kindle for Android help file
|
# 6.3.2 - Fixed Kindle for Android help file
|
||||||
|
# 6.3.3 - Bug fix for Kindle for PC support
|
||||||
|
# 6.3.4 - Fixes for Kindle for Android, Linux, and Kobo 3.17
|
||||||
|
# 6.3.5 - Fixes for Linux, and Kobo 3.19 and more logging
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -51,7 +54,7 @@ Decrypt DRMed ebooks.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
PLUGIN_NAME = u"DeDRM"
|
PLUGIN_NAME = u"DeDRM"
|
||||||
PLUGIN_VERSION_TUPLE = (6, 3, 2)
|
PLUGIN_VERSION_TUPLE = (6, 3, 5)
|
||||||
PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE])
|
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.
|
# Include an html helpfile in the plugin's zipfile with the following name.
|
||||||
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
|
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
|
||||||
@@ -94,7 +97,7 @@ class DeDRM(FileTypePlugin):
|
|||||||
supported_platforms = ['linux', 'osx', 'windows']
|
supported_platforms = ['linux', 'osx', 'windows']
|
||||||
author = u"Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
|
author = u"Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
|
||||||
version = PLUGIN_VERSION_TUPLE
|
version = PLUGIN_VERSION_TUPLE
|
||||||
minimum_calibre_version = (0, 7, 55) # Compiled python libraries cannot be imported in earlier versions.
|
minimum_calibre_version = (1, 0, 0) # Compiled python libraries cannot be imported in earlier versions.
|
||||||
file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','tpz'])
|
file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','tpz'])
|
||||||
on_import = True
|
on_import = True
|
||||||
priority = 600
|
priority = 600
|
||||||
@@ -147,7 +150,7 @@ class DeDRM(FileTypePlugin):
|
|||||||
try:
|
try:
|
||||||
open(file_path,'wb').write(data)
|
open(file_path,'wb').write(data)
|
||||||
except:
|
except:
|
||||||
print u"{0} v{1}: Exception when copying needed library files after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
print u"{0} v{1}: Exception when copying needed library files".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -294,11 +297,15 @@ class DeDRM(FileTypePlugin):
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
result = 1
|
result = 1
|
||||||
|
|
||||||
of.close()
|
try:
|
||||||
|
of.close()
|
||||||
|
except:
|
||||||
|
print u"{0} v{1}: Exception closing temporary file after {2:.1f} seconds. Ignored.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
|
|
||||||
if result == 0:
|
if result == 0:
|
||||||
# Decryption was successful.
|
# Decryption was successful.
|
||||||
# Return the modified PersistentTemporary file to calibre.
|
# Return the modified PersistentTemporary file to calibre.
|
||||||
|
print u"{0} v{1}: Decrypted with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)
|
||||||
return of.name
|
return of.name
|
||||||
|
|
||||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)
|
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)
|
||||||
@@ -358,6 +365,7 @@ class DeDRM(FileTypePlugin):
|
|||||||
except:
|
except:
|
||||||
print u"{0} v{1}: Exception when saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
print u"{0} v{1}: Exception when saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
print u"{0} v{1}: Decrypted with new default key after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||||
# Return the modified PersistentTemporary file to calibre.
|
# Return the modified PersistentTemporary file to calibre.
|
||||||
return of.name
|
return of.name
|
||||||
|
|
||||||
@@ -523,7 +531,7 @@ class DeDRM(FileTypePlugin):
|
|||||||
if len(newkeys) > 0:
|
if len(newkeys) > 0:
|
||||||
print u"{0} v{1}: Found {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")
|
print u"{0} v{1}: Found {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")
|
||||||
try:
|
try:
|
||||||
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],self.starttime)
|
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],[],self.starttime)
|
||||||
decoded = True
|
decoded = True
|
||||||
# store the new successful keys in the defaults
|
# store the new successful keys in the defaults
|
||||||
print u"{0} v{1}: Saving {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")
|
print u"{0} v{1}: Saving {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")
|
||||||
|
|||||||
@@ -16,16 +16,18 @@ from __future__ import with_statement
|
|||||||
# - and added in unicode command line support
|
# - and added in unicode command line support
|
||||||
# 1.3 - added in TkInter interface, output to a file
|
# 1.3 - added in TkInter interface, output to a file
|
||||||
# 1.4 - Fix some problems identified by Aldo Bleeker
|
# 1.4 - Fix some problems identified by Aldo Bleeker
|
||||||
|
# 1.5 - Fix another problem identified by Aldo Bleeker
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Retrieve Kindle for Android Serial Number.
|
Retrieve Kindle for Android Serial Number.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = '1.4'
|
__version__ = '1.5'
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import traceback
|
||||||
import getopt
|
import getopt
|
||||||
import tempfile
|
import tempfile
|
||||||
import zlib
|
import zlib
|
||||||
@@ -220,20 +222,30 @@ def get_serials2(path=STORAGE2):
|
|||||||
userdata_keys = cursor.fetchall()
|
userdata_keys = cursor.fetchall()
|
||||||
dsns = []
|
dsns = []
|
||||||
for userdata_row in userdata_keys:
|
for userdata_row in userdata_keys:
|
||||||
if userdata_row:
|
try:
|
||||||
userdata_utf8 = userdata_row[0].encode('utf8')
|
if userdata_row and userdata_row[0]:
|
||||||
if len(userdata_utf8) > 0:
|
userdata_utf8 = userdata_row[0].encode('utf8')
|
||||||
dsns.append(userdata_utf8)
|
if len(userdata_utf8) > 0:
|
||||||
|
dsns.append(userdata_utf8)
|
||||||
|
except:
|
||||||
|
print "Error getting one of the device serial name keys"
|
||||||
|
traceback.print_exc()
|
||||||
|
pass
|
||||||
dsns = list(set(dsns))
|
dsns = list(set(dsns))
|
||||||
|
|
||||||
cursor.execute('''select userdata_value from userdata where userdata_key like '%/%kindle.account.tokens%' ''')
|
cursor.execute('''select userdata_value from userdata where userdata_key like '%/%kindle.account.tokens%' ''')
|
||||||
userdata_keys = cursor.fetchall()
|
userdata_keys = cursor.fetchall()
|
||||||
tokens = []
|
tokens = []
|
||||||
for userdata_row in userdata_keys:
|
for userdata_row in userdata_keys:
|
||||||
if userdata_row:
|
try:
|
||||||
userdata_utf8 = userdata_row[0].encode('utf8')
|
if userdata_row and userdata_row[0]:
|
||||||
if len(userdata_utf8) > 0:
|
userdata_utf8 = userdata_row[0].encode('utf8')
|
||||||
tokens.append(userdata_utf8)
|
if len(userdata_utf8) > 0:
|
||||||
|
tokens.append(userdata_utf8)
|
||||||
|
except:
|
||||||
|
print "Error getting one of the account token keys"
|
||||||
|
traceback.print_exc()
|
||||||
|
pass
|
||||||
tokens = list(set(tokens))
|
tokens = list(set(tokens))
|
||||||
|
|
||||||
serials = []
|
serials = []
|
||||||
@@ -377,7 +389,6 @@ def gui_main():
|
|||||||
import Tkconstants
|
import Tkconstants
|
||||||
import tkMessageBox
|
import tkMessageBox
|
||||||
import tkFileDialog
|
import tkFileDialog
|
||||||
import traceback
|
|
||||||
except:
|
except:
|
||||||
print "Tkinter not installed"
|
print "Tkinter not installed"
|
||||||
return cli_main()
|
return cli_main()
|
||||||
|
|||||||
@@ -3,18 +3,19 @@
|
|||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
|
||||||
# ineptepub.pyw, version 6.1
|
# ineptepub.pyw, version 6.3
|
||||||
# Copyright © 2009-2010 by i♥cabbages
|
# Copyright © 2009-2010 by i♥cabbages
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
# Released under the terms of the GNU General Public Licence, version 3
|
||||||
# <http://www.gnu.org/licenses/>
|
# <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf
|
# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf
|
||||||
|
# Modified 2015–2016 by Apprentice Harper
|
||||||
|
|
||||||
# Windows users: Before running this program, you must first install Python 2.6
|
# Windows users: Before running this program, you must first install Python 2.7
|
||||||
# from <http://www.python.org/download/> and PyCrypto from
|
# from <http://www.python.org/download/> and PyCrypto from
|
||||||
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
|
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
|
||||||
# install the version for Python 2.6). Save this script file as
|
# install the version for Python 2.7). Save this script file as
|
||||||
# ineptepub.pyw and double-click on it to run it.
|
# ineptepub.pyw and double-click on it to run it.
|
||||||
#
|
#
|
||||||
# Mac OS X users: Save this script file as ineptepub.pyw. You can run this
|
# Mac OS X users: Save this script file as ineptepub.pyw. You can run this
|
||||||
@@ -38,13 +39,14 @@ from __future__ import with_statement
|
|||||||
# 6.0 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
# 6.0 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||||
# 6.1 - Work if TkInter is missing
|
# 6.1 - Work if TkInter is missing
|
||||||
# 6.2 - Handle UTF-8 file names inside an ePub, fix by Jose Luis
|
# 6.2 - Handle UTF-8 file names inside an ePub, fix by Jose Luis
|
||||||
|
# 6.3 - Add additional check on DER file sanity
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Decrypt Adobe Digital Editions encrypted ePub books.
|
Decrypt Adobe Digital Editions encrypted ePub books.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "6.2"
|
__version__ = "6.3"
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@@ -169,9 +171,14 @@ def _load_crypto_libcrypto():
|
|||||||
def __init__(self, der):
|
def __init__(self, der):
|
||||||
buf = create_string_buffer(der)
|
buf = create_string_buffer(der)
|
||||||
pp = c_char_pp(cast(buf, c_char_p))
|
pp = c_char_pp(cast(buf, c_char_p))
|
||||||
rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
|
rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
|
||||||
if rsa is None:
|
if rsa is None:
|
||||||
raise ADEPTError('Error parsing ADEPT user key DER')
|
raise ADEPTError('Error parsing ADEPT user key DER')
|
||||||
|
# check if pointer is not NULL
|
||||||
|
try:
|
||||||
|
c = self._rsa.contents
|
||||||
|
except ValueError:
|
||||||
|
raise ADEPTError('Error parsing ADEPT user key DER')
|
||||||
|
|
||||||
def decrypt(self, from_):
|
def decrypt(self, from_):
|
||||||
rsa = self._rsa
|
rsa = self._rsa
|
||||||
@@ -313,6 +320,12 @@ def _load_crypto_pycrypto():
|
|||||||
key = [key.getChild(x).value for x in xrange(1, 4)]
|
key = [key.getChild(x).value for x in xrange(1, 4)]
|
||||||
key = [self.bytesToNumber(v) for v in key]
|
key = [self.bytesToNumber(v) for v in key]
|
||||||
self._rsa = _RSA.construct(key)
|
self._rsa = _RSA.construct(key)
|
||||||
|
# check if pointer is not NULL
|
||||||
|
try:
|
||||||
|
c = self._rsa.contents
|
||||||
|
except ValueError:
|
||||||
|
raise ADEPTError('Error parsing ADEPT user key DER')
|
||||||
|
|
||||||
|
|
||||||
def bytesToNumber(self, bytes):
|
def bytesToNumber(self, bytes):
|
||||||
total = 0L
|
total = 0L
|
||||||
|
|||||||
@@ -3,18 +3,19 @@
|
|||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
|
||||||
# ineptpdf.pyw, version 7.11
|
# ineptpdf.pyw, version 8.0.2
|
||||||
# Copyright © 2009-2010 by i♥cabbages
|
# Copyright © 2009-2010 by i♥cabbages
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
# Released under the terms of the GNU General Public Licence, version 3
|
||||||
# <http://www.gnu.org/licenses/>
|
# <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf
|
# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf
|
||||||
|
# Modified 2015-2016 by Apprentice Harper
|
||||||
|
|
||||||
# Windows users: Before running this program, you must first install Python 2.6
|
# Windows users: Before running this program, you must first install Python 2.7
|
||||||
# from <http://www.python.org/download/> and PyCrypto from
|
# from <http://www.python.org/download/> and PyCrypto from
|
||||||
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
|
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
|
||||||
# install the version for Python 2.6). Save this script file as
|
# install the version for Python 2.7). Save this script file as
|
||||||
# ineptpdf.pyw and double-click on it to run it.
|
# ineptpdf.pyw and double-click on it to run it.
|
||||||
#
|
#
|
||||||
# Mac OS X users: Save this script file as ineptpdf.pyw. You can run this
|
# Mac OS X users: Save this script file as ineptpdf.pyw. You can run this
|
||||||
@@ -53,13 +54,15 @@ from __future__ import with_statement
|
|||||||
# 7.14 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
# 7.14 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||||
# 8.0 - Work if TkInter is missing
|
# 8.0 - Work if TkInter is missing
|
||||||
# 8.0.1 - Broken Metadata fix.
|
# 8.0.1 - Broken Metadata fix.
|
||||||
|
# 8.0.2 - Add additional check on DER file sanity
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Decrypts Adobe ADEPT-encrypted PDF files.
|
Decrypts Adobe ADEPT-encrypted PDF files.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "8.0.1"
|
__version__ = "8.0.2"
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@@ -198,6 +201,11 @@ def _load_crypto_libcrypto():
|
|||||||
rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
|
rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
|
||||||
if rsa is None:
|
if rsa is None:
|
||||||
raise ADEPTError('Error parsing ADEPT user key DER')
|
raise ADEPTError('Error parsing ADEPT user key DER')
|
||||||
|
# check if pointer is not NULL
|
||||||
|
try:
|
||||||
|
c = self._rsa.contents
|
||||||
|
except ValueError:
|
||||||
|
raise ADEPTError('Error parsing ADEPT user key DER')
|
||||||
|
|
||||||
def decrypt(self, from_):
|
def decrypt(self, from_):
|
||||||
rsa = self._rsa
|
rsa = self._rsa
|
||||||
@@ -383,6 +391,11 @@ def _load_crypto_pycrypto():
|
|||||||
key = [key.getChild(x).value for x in xrange(1, 4)]
|
key = [key.getChild(x).value for x in xrange(1, 4)]
|
||||||
key = [self.bytesToNumber(v) for v in key]
|
key = [self.bytesToNumber(v) for v in key]
|
||||||
self._rsa = _RSA.construct(key)
|
self._rsa = _RSA.construct(key)
|
||||||
|
# check if pointer is not NULL
|
||||||
|
try:
|
||||||
|
c = self._rsa.contents
|
||||||
|
except ValueError:
|
||||||
|
raise ADEPTError('Error parsing ADEPT user key DER')
|
||||||
|
|
||||||
def bytesToNumber(self, bytes):
|
def bytesToNumber(self, bytes):
|
||||||
total = 0L
|
total = 0L
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
|
|||||||
if not os.path.exists(outdirpath):
|
if not os.path.exists(outdirpath):
|
||||||
os.makedirs(outdirpath)
|
os.makedirs(outdirpath)
|
||||||
|
|
||||||
|
wineprefix = os.path.abspath(os.path.expanduser(os.path.expandvars(wineprefix)))
|
||||||
if wineprefix != "" and os.path.exists(wineprefix):
|
if wineprefix != "" and os.path.exists(wineprefix):
|
||||||
cmdline = u"WINEPREFIX=\"{2}\" wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
cmdline = u"WINEPREFIX=\"{2}\" wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
||||||
else:
|
else:
|
||||||
@@ -38,8 +39,20 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
|
|||||||
result = p2.wait("wait")
|
result = p2.wait("wait")
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
print u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
|
print u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
|
||||||
return []
|
if wineprefix != "" and os.path.exists(wineprefix):
|
||||||
|
cmdline = u"WINEPREFIX=\"{2}\" wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
||||||
|
else:
|
||||||
|
cmdline = u"wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath)
|
||||||
|
print u"{0} v{1}: Command line: “{2}”".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline)
|
||||||
|
|
||||||
|
try:
|
||||||
|
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||||
|
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False)
|
||||||
|
result = p2.wait("wait")
|
||||||
|
except Exception, e:
|
||||||
|
print u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
|
||||||
|
|
||||||
|
# try finding winekeys anyway, even if above code errored
|
||||||
winekeys = []
|
winekeys = []
|
||||||
# get any files with extension in the output dir
|
# get any files with extension in the output dir
|
||||||
files = [f for f in os.listdir(outdirpath) if f.endswith(extension)]
|
files = [f for f in os.listdir(outdirpath) if f.endswith(extension)]
|
||||||
|
|||||||
Binary file not shown.
@@ -44,6 +44,9 @@ __docformat__ = 'restructuredtext en'
|
|||||||
# 6.3.0 - Added in Kindle for Android serial number solution
|
# 6.3.0 - Added in Kindle for Android serial number solution
|
||||||
# 6.3.1 - Version number bump for clarity
|
# 6.3.1 - Version number bump for clarity
|
||||||
# 6.3.2 - Fixed Kindle for Android help file
|
# 6.3.2 - Fixed Kindle for Android help file
|
||||||
|
# 6.3.3 - Bug fix for Kindle for PC support
|
||||||
|
# 6.3.4 - Fixes for Kindle for Android, Linux, and Kobo 3.17
|
||||||
|
# 6.3.5 - Fixes for Linux, and Kobo 3.19 and more logging
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -51,7 +54,7 @@ Decrypt DRMed ebooks.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
PLUGIN_NAME = u"DeDRM"
|
PLUGIN_NAME = u"DeDRM"
|
||||||
PLUGIN_VERSION_TUPLE = (6, 3, 2)
|
PLUGIN_VERSION_TUPLE = (6, 3, 5)
|
||||||
PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE])
|
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.
|
# Include an html helpfile in the plugin's zipfile with the following name.
|
||||||
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
|
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
|
||||||
@@ -94,7 +97,7 @@ class DeDRM(FileTypePlugin):
|
|||||||
supported_platforms = ['linux', 'osx', 'windows']
|
supported_platforms = ['linux', 'osx', 'windows']
|
||||||
author = u"Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
|
author = u"Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
|
||||||
version = PLUGIN_VERSION_TUPLE
|
version = PLUGIN_VERSION_TUPLE
|
||||||
minimum_calibre_version = (0, 7, 55) # Compiled python libraries cannot be imported in earlier versions.
|
minimum_calibre_version = (1, 0, 0) # Compiled python libraries cannot be imported in earlier versions.
|
||||||
file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','tpz'])
|
file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','tpz'])
|
||||||
on_import = True
|
on_import = True
|
||||||
priority = 600
|
priority = 600
|
||||||
@@ -147,7 +150,7 @@ class DeDRM(FileTypePlugin):
|
|||||||
try:
|
try:
|
||||||
open(file_path,'wb').write(data)
|
open(file_path,'wb').write(data)
|
||||||
except:
|
except:
|
||||||
print u"{0} v{1}: Exception when copying needed library files after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
print u"{0} v{1}: Exception when copying needed library files".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -294,11 +297,15 @@ class DeDRM(FileTypePlugin):
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
result = 1
|
result = 1
|
||||||
|
|
||||||
of.close()
|
try:
|
||||||
|
of.close()
|
||||||
|
except:
|
||||||
|
print u"{0} v{1}: Exception closing temporary file after {2:.1f} seconds. Ignored.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
|
|
||||||
if result == 0:
|
if result == 0:
|
||||||
# Decryption was successful.
|
# Decryption was successful.
|
||||||
# Return the modified PersistentTemporary file to calibre.
|
# Return the modified PersistentTemporary file to calibre.
|
||||||
|
print u"{0} v{1}: Decrypted with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)
|
||||||
return of.name
|
return of.name
|
||||||
|
|
||||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)
|
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)
|
||||||
@@ -358,6 +365,7 @@ class DeDRM(FileTypePlugin):
|
|||||||
except:
|
except:
|
||||||
print u"{0} v{1}: Exception when saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
print u"{0} v{1}: Exception when saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
print u"{0} v{1}: Decrypted with new default key after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||||
# Return the modified PersistentTemporary file to calibre.
|
# Return the modified PersistentTemporary file to calibre.
|
||||||
return of.name
|
return of.name
|
||||||
|
|
||||||
@@ -523,7 +531,7 @@ class DeDRM(FileTypePlugin):
|
|||||||
if len(newkeys) > 0:
|
if len(newkeys) > 0:
|
||||||
print u"{0} v{1}: Found {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")
|
print u"{0} v{1}: Found {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")
|
||||||
try:
|
try:
|
||||||
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],self.starttime)
|
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],[],self.starttime)
|
||||||
decoded = True
|
decoded = True
|
||||||
# store the new successful keys in the defaults
|
# store the new successful keys in the defaults
|
||||||
print u"{0} v{1}: Saving {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")
|
print u"{0} v{1}: Saving {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")
|
||||||
|
|||||||
@@ -16,16 +16,18 @@ from __future__ import with_statement
|
|||||||
# - and added in unicode command line support
|
# - and added in unicode command line support
|
||||||
# 1.3 - added in TkInter interface, output to a file
|
# 1.3 - added in TkInter interface, output to a file
|
||||||
# 1.4 - Fix some problems identified by Aldo Bleeker
|
# 1.4 - Fix some problems identified by Aldo Bleeker
|
||||||
|
# 1.5 - Fix another problem identified by Aldo Bleeker
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Retrieve Kindle for Android Serial Number.
|
Retrieve Kindle for Android Serial Number.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = '1.4'
|
__version__ = '1.5'
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import traceback
|
||||||
import getopt
|
import getopt
|
||||||
import tempfile
|
import tempfile
|
||||||
import zlib
|
import zlib
|
||||||
@@ -220,20 +222,30 @@ def get_serials2(path=STORAGE2):
|
|||||||
userdata_keys = cursor.fetchall()
|
userdata_keys = cursor.fetchall()
|
||||||
dsns = []
|
dsns = []
|
||||||
for userdata_row in userdata_keys:
|
for userdata_row in userdata_keys:
|
||||||
if userdata_row:
|
try:
|
||||||
userdata_utf8 = userdata_row[0].encode('utf8')
|
if userdata_row and userdata_row[0]:
|
||||||
if len(userdata_utf8) > 0:
|
userdata_utf8 = userdata_row[0].encode('utf8')
|
||||||
dsns.append(userdata_utf8)
|
if len(userdata_utf8) > 0:
|
||||||
|
dsns.append(userdata_utf8)
|
||||||
|
except:
|
||||||
|
print "Error getting one of the device serial name keys"
|
||||||
|
traceback.print_exc()
|
||||||
|
pass
|
||||||
dsns = list(set(dsns))
|
dsns = list(set(dsns))
|
||||||
|
|
||||||
cursor.execute('''select userdata_value from userdata where userdata_key like '%/%kindle.account.tokens%' ''')
|
cursor.execute('''select userdata_value from userdata where userdata_key like '%/%kindle.account.tokens%' ''')
|
||||||
userdata_keys = cursor.fetchall()
|
userdata_keys = cursor.fetchall()
|
||||||
tokens = []
|
tokens = []
|
||||||
for userdata_row in userdata_keys:
|
for userdata_row in userdata_keys:
|
||||||
if userdata_row:
|
try:
|
||||||
userdata_utf8 = userdata_row[0].encode('utf8')
|
if userdata_row and userdata_row[0]:
|
||||||
if len(userdata_utf8) > 0:
|
userdata_utf8 = userdata_row[0].encode('utf8')
|
||||||
tokens.append(userdata_utf8)
|
if len(userdata_utf8) > 0:
|
||||||
|
tokens.append(userdata_utf8)
|
||||||
|
except:
|
||||||
|
print "Error getting one of the account token keys"
|
||||||
|
traceback.print_exc()
|
||||||
|
pass
|
||||||
tokens = list(set(tokens))
|
tokens = list(set(tokens))
|
||||||
|
|
||||||
serials = []
|
serials = []
|
||||||
@@ -377,7 +389,6 @@ def gui_main():
|
|||||||
import Tkconstants
|
import Tkconstants
|
||||||
import tkMessageBox
|
import tkMessageBox
|
||||||
import tkFileDialog
|
import tkFileDialog
|
||||||
import traceback
|
|
||||||
except:
|
except:
|
||||||
print "Tkinter not installed"
|
print "Tkinter not installed"
|
||||||
return cli_main()
|
return cli_main()
|
||||||
|
|||||||
@@ -3,18 +3,19 @@
|
|||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
|
||||||
# ineptepub.pyw, version 6.1
|
# ineptepub.pyw, version 6.3
|
||||||
# Copyright © 2009-2010 by i♥cabbages
|
# Copyright © 2009-2010 by i♥cabbages
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
# Released under the terms of the GNU General Public Licence, version 3
|
||||||
# <http://www.gnu.org/licenses/>
|
# <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf
|
# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf
|
||||||
|
# Modified 2015–2016 by Apprentice Harper
|
||||||
|
|
||||||
# Windows users: Before running this program, you must first install Python 2.6
|
# Windows users: Before running this program, you must first install Python 2.7
|
||||||
# from <http://www.python.org/download/> and PyCrypto from
|
# from <http://www.python.org/download/> and PyCrypto from
|
||||||
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
|
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
|
||||||
# install the version for Python 2.6). Save this script file as
|
# install the version for Python 2.7). Save this script file as
|
||||||
# ineptepub.pyw and double-click on it to run it.
|
# ineptepub.pyw and double-click on it to run it.
|
||||||
#
|
#
|
||||||
# Mac OS X users: Save this script file as ineptepub.pyw. You can run this
|
# Mac OS X users: Save this script file as ineptepub.pyw. You can run this
|
||||||
@@ -38,13 +39,14 @@ from __future__ import with_statement
|
|||||||
# 6.0 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
# 6.0 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||||
# 6.1 - Work if TkInter is missing
|
# 6.1 - Work if TkInter is missing
|
||||||
# 6.2 - Handle UTF-8 file names inside an ePub, fix by Jose Luis
|
# 6.2 - Handle UTF-8 file names inside an ePub, fix by Jose Luis
|
||||||
|
# 6.3 - Add additional check on DER file sanity
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Decrypt Adobe Digital Editions encrypted ePub books.
|
Decrypt Adobe Digital Editions encrypted ePub books.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "6.2"
|
__version__ = "6.3"
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@@ -169,9 +171,14 @@ def _load_crypto_libcrypto():
|
|||||||
def __init__(self, der):
|
def __init__(self, der):
|
||||||
buf = create_string_buffer(der)
|
buf = create_string_buffer(der)
|
||||||
pp = c_char_pp(cast(buf, c_char_p))
|
pp = c_char_pp(cast(buf, c_char_p))
|
||||||
rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
|
rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
|
||||||
if rsa is None:
|
if rsa is None:
|
||||||
raise ADEPTError('Error parsing ADEPT user key DER')
|
raise ADEPTError('Error parsing ADEPT user key DER')
|
||||||
|
# check if pointer is not NULL
|
||||||
|
try:
|
||||||
|
c = self._rsa.contents
|
||||||
|
except ValueError:
|
||||||
|
raise ADEPTError('Error parsing ADEPT user key DER')
|
||||||
|
|
||||||
def decrypt(self, from_):
|
def decrypt(self, from_):
|
||||||
rsa = self._rsa
|
rsa = self._rsa
|
||||||
@@ -313,6 +320,12 @@ def _load_crypto_pycrypto():
|
|||||||
key = [key.getChild(x).value for x in xrange(1, 4)]
|
key = [key.getChild(x).value for x in xrange(1, 4)]
|
||||||
key = [self.bytesToNumber(v) for v in key]
|
key = [self.bytesToNumber(v) for v in key]
|
||||||
self._rsa = _RSA.construct(key)
|
self._rsa = _RSA.construct(key)
|
||||||
|
# check if pointer is not NULL
|
||||||
|
try:
|
||||||
|
c = self._rsa.contents
|
||||||
|
except ValueError:
|
||||||
|
raise ADEPTError('Error parsing ADEPT user key DER')
|
||||||
|
|
||||||
|
|
||||||
def bytesToNumber(self, bytes):
|
def bytesToNumber(self, bytes):
|
||||||
total = 0L
|
total = 0L
|
||||||
|
|||||||
@@ -3,18 +3,19 @@
|
|||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
|
||||||
# ineptpdf.pyw, version 7.11
|
# ineptpdf.pyw, version 8.0.2
|
||||||
# Copyright © 2009-2010 by i♥cabbages
|
# Copyright © 2009-2010 by i♥cabbages
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
# Released under the terms of the GNU General Public Licence, version 3
|
||||||
# <http://www.gnu.org/licenses/>
|
# <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf
|
# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf
|
||||||
|
# Modified 2015-2016 by Apprentice Harper
|
||||||
|
|
||||||
# Windows users: Before running this program, you must first install Python 2.6
|
# Windows users: Before running this program, you must first install Python 2.7
|
||||||
# from <http://www.python.org/download/> and PyCrypto from
|
# from <http://www.python.org/download/> and PyCrypto from
|
||||||
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
|
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
|
||||||
# install the version for Python 2.6). Save this script file as
|
# install the version for Python 2.7). Save this script file as
|
||||||
# ineptpdf.pyw and double-click on it to run it.
|
# ineptpdf.pyw and double-click on it to run it.
|
||||||
#
|
#
|
||||||
# Mac OS X users: Save this script file as ineptpdf.pyw. You can run this
|
# Mac OS X users: Save this script file as ineptpdf.pyw. You can run this
|
||||||
@@ -53,13 +54,15 @@ from __future__ import with_statement
|
|||||||
# 7.14 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
# 7.14 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||||
# 8.0 - Work if TkInter is missing
|
# 8.0 - Work if TkInter is missing
|
||||||
# 8.0.1 - Broken Metadata fix.
|
# 8.0.1 - Broken Metadata fix.
|
||||||
|
# 8.0.2 - Add additional check on DER file sanity
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Decrypts Adobe ADEPT-encrypted PDF files.
|
Decrypts Adobe ADEPT-encrypted PDF files.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "8.0.1"
|
__version__ = "8.0.2"
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@@ -198,6 +201,11 @@ def _load_crypto_libcrypto():
|
|||||||
rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
|
rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
|
||||||
if rsa is None:
|
if rsa is None:
|
||||||
raise ADEPTError('Error parsing ADEPT user key DER')
|
raise ADEPTError('Error parsing ADEPT user key DER')
|
||||||
|
# check if pointer is not NULL
|
||||||
|
try:
|
||||||
|
c = self._rsa.contents
|
||||||
|
except ValueError:
|
||||||
|
raise ADEPTError('Error parsing ADEPT user key DER')
|
||||||
|
|
||||||
def decrypt(self, from_):
|
def decrypt(self, from_):
|
||||||
rsa = self._rsa
|
rsa = self._rsa
|
||||||
@@ -383,6 +391,11 @@ def _load_crypto_pycrypto():
|
|||||||
key = [key.getChild(x).value for x in xrange(1, 4)]
|
key = [key.getChild(x).value for x in xrange(1, 4)]
|
||||||
key = [self.bytesToNumber(v) for v in key]
|
key = [self.bytesToNumber(v) for v in key]
|
||||||
self._rsa = _RSA.construct(key)
|
self._rsa = _RSA.construct(key)
|
||||||
|
# check if pointer is not NULL
|
||||||
|
try:
|
||||||
|
c = self._rsa.contents
|
||||||
|
except ValueError:
|
||||||
|
raise ADEPTError('Error parsing ADEPT user key DER')
|
||||||
|
|
||||||
def bytesToNumber(self, bytes):
|
def bytesToNumber(self, bytes):
|
||||||
total = 0L
|
total = 0L
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
|
|||||||
if not os.path.exists(outdirpath):
|
if not os.path.exists(outdirpath):
|
||||||
os.makedirs(outdirpath)
|
os.makedirs(outdirpath)
|
||||||
|
|
||||||
|
wineprefix = os.path.abspath(os.path.expanduser(os.path.expandvars(wineprefix)))
|
||||||
if wineprefix != "" and os.path.exists(wineprefix):
|
if wineprefix != "" and os.path.exists(wineprefix):
|
||||||
cmdline = u"WINEPREFIX=\"{2}\" wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
cmdline = u"WINEPREFIX=\"{2}\" wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
||||||
else:
|
else:
|
||||||
@@ -38,8 +39,20 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
|
|||||||
result = p2.wait("wait")
|
result = p2.wait("wait")
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
print u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
|
print u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
|
||||||
return []
|
if wineprefix != "" and os.path.exists(wineprefix):
|
||||||
|
cmdline = u"WINEPREFIX=\"{2}\" wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
||||||
|
else:
|
||||||
|
cmdline = u"wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath)
|
||||||
|
print u"{0} v{1}: Command line: “{2}”".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline)
|
||||||
|
|
||||||
|
try:
|
||||||
|
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||||
|
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False)
|
||||||
|
result = p2.wait("wait")
|
||||||
|
except Exception, e:
|
||||||
|
print u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
|
||||||
|
|
||||||
|
# try finding winekeys anyway, even if above code errored
|
||||||
winekeys = []
|
winekeys = []
|
||||||
# get any files with extension in the output dir
|
# get any files with extension in the output dir
|
||||||
files = [f for f in os.listdir(outdirpath) if f.endswith(extension)]
|
files = [f for f in os.listdir(outdirpath) if f.endswith(extension)]
|
||||||
|
|||||||
Binary file not shown.
@@ -19,7 +19,7 @@ except NameError:
|
|||||||
PLUGIN_NAME = 'Obok DeDRM'
|
PLUGIN_NAME = 'Obok DeDRM'
|
||||||
PLUGIN_SAFE_NAME = PLUGIN_NAME.strip().lower().replace(' ', '_')
|
PLUGIN_SAFE_NAME = PLUGIN_NAME.strip().lower().replace(' ', '_')
|
||||||
PLUGIN_DESCRIPTION = _('Removes DRM from Kobo kepubs and adds them to the library.')
|
PLUGIN_DESCRIPTION = _('Removes DRM from Kobo kepubs and adds them to the library.')
|
||||||
PLUGIN_VERSION_TUPLE = (3, 1, 3)
|
PLUGIN_VERSION_TUPLE = (6, 3, 5)
|
||||||
PLUGIN_VERSION = '.'.join([str(x) for x in PLUGIN_VERSION_TUPLE])
|
PLUGIN_VERSION = '.'.join([str(x) for x in PLUGIN_VERSION_TUPLE])
|
||||||
HELPFILE_NAME = PLUGIN_SAFE_NAME + '_Help.htm'
|
HELPFILE_NAME = PLUGIN_SAFE_NAME + '_Help.htm'
|
||||||
PLUGIN_AUTHORS = 'Anon'
|
PLUGIN_AUTHORS = 'Anon'
|
||||||
@@ -29,7 +29,7 @@ class ObokDeDRMAction(InterfaceActionBase):
|
|||||||
|
|
||||||
name = PLUGIN_NAME
|
name = PLUGIN_NAME
|
||||||
description = PLUGIN_DESCRIPTION
|
description = PLUGIN_DESCRIPTION
|
||||||
supported_platforms = ['windows', 'osx']
|
supported_platforms = ['windows', 'osx', 'linux' ]
|
||||||
author = PLUGIN_AUTHORS
|
author = PLUGIN_AUTHORS
|
||||||
version = PLUGIN_VERSION_TUPLE
|
version = PLUGIN_VERSION_TUPLE
|
||||||
minimum_calibre_version = (1, 0, 0)
|
minimum_calibre_version = (1, 0, 0)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ __license__ = 'GPL v3'
|
|||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
|
||||||
import os, zipfile
|
import os, traceback, zipfile
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PyQt5.Qt import QToolButton, QUrl
|
from PyQt5.Qt import QToolButton, QUrl
|
||||||
@@ -78,8 +78,30 @@ class InterfacePluginAction(InterfaceAction):
|
|||||||
self.current_idx = self.gui.library_view.currentIndex()
|
self.current_idx = self.gui.library_view.currentIndex()
|
||||||
|
|
||||||
print ('Running {}'.format(PLUGIN_NAME + ' v' + PLUGIN_VERSION))
|
print ('Running {}'.format(PLUGIN_NAME + ' v' + PLUGIN_VERSION))
|
||||||
|
#
|
||||||
|
# search for connected device in case serials are saved
|
||||||
|
tmpserials = cfg['kobo_serials']
|
||||||
|
device_path = None
|
||||||
|
try:
|
||||||
|
device = self.parent().device_manager.connected_device
|
||||||
|
if (device):
|
||||||
|
device_path = device._main_prefix
|
||||||
|
debug_print("get_device_settings - device_path=", device_path)
|
||||||
|
else:
|
||||||
|
debug_print("didn't find device")
|
||||||
|
except:
|
||||||
|
debug_print("Exception getting device path. Probably not an E-Ink Kobo device")
|
||||||
|
|
||||||
# Get the Kobo Library object (obok v3.01)
|
# Get the Kobo Library object (obok v3.01)
|
||||||
self.library = KoboLibrary()
|
self.library = KoboLibrary(tmpserials, device_path)
|
||||||
|
debug_print ("got kobodir %s" % self.library.kobodir)
|
||||||
|
if (self.library.kobodir == ''):
|
||||||
|
# linux and no device connected, but could be extended
|
||||||
|
# to the case where on Windows/Mac the prog is not installed
|
||||||
|
msg = _('<p>Could not find Kobo Library\n<p>Windows/Mac: do you have Kobo Desktop installed?\n<p>Windows/Mac/Linux: In case you have an Kobo eInk device, connect the device.')
|
||||||
|
showErrorDlg(msg, None)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
# Get a list of Kobo titles
|
# Get a list of Kobo titles
|
||||||
books = self.build_book_list()
|
books = self.build_book_list()
|
||||||
@@ -98,6 +120,7 @@ class InterfacePluginAction(InterfaceAction):
|
|||||||
candidate_keys = self.library.userkeys
|
candidate_keys = self.library.userkeys
|
||||||
except:
|
except:
|
||||||
print (_('Trouble retrieving keys with newer obok method.'))
|
print (_('Trouble retrieving keys with newer obok method.'))
|
||||||
|
traceback.print_exc()
|
||||||
else:
|
else:
|
||||||
if len(candidate_keys):
|
if len(candidate_keys):
|
||||||
self.userkeys.extend(candidate_keys)
|
self.userkeys.extend(candidate_keys)
|
||||||
|
|||||||
@@ -3,15 +3,23 @@ from __future__ import (unicode_literals, division, absolute_import,
|
|||||||
print_function)
|
print_function)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PyQt5.Qt import (QWidget, QLabel, QVBoxLayout, QHBoxLayout, QComboBox)
|
from PyQt5.Qt import (Qt, QGroupBox, QListWidget, QLineEdit, QDialogButtonBox, QWidget, QLabel, QDialog, QVBoxLayout, QAbstractItemView, QIcon, QHBoxLayout, QComboBox, QListWidgetItem)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from PyQt4.Qt import (QWidget, QLabel, QVBoxLayout, QHBoxLayout, QComboBox)
|
from PyQt4.Qt import (Qt, QGroupBox, QListWidget, QLineEdit, QDialogButtonBox, QWidget, QLabel, QDialog, QVBoxLayout, QAbstractItemView, QIcon, QHBoxLayout, QComboBox, QListWidgetItem)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from PyQt5 import Qt as QtGui
|
||||||
|
except ImportError:
|
||||||
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
|
from calibre.gui2 import (error_dialog, question_dialog, info_dialog, open_url)
|
||||||
from calibre.utils.config import JSONConfig, config_dir
|
from calibre.utils.config import JSONConfig, config_dir
|
||||||
|
|
||||||
plugin_prefs = JSONConfig('plugins/obok_dedrm_prefs')
|
plugin_prefs = JSONConfig('plugins/obok_dedrm_prefs')
|
||||||
plugin_prefs.defaults['finding_homes_for_formats'] = 'Ask'
|
plugin_prefs.defaults['finding_homes_for_formats'] = 'Ask'
|
||||||
|
plugin_prefs.defaults['kobo_serials'] = []
|
||||||
|
|
||||||
|
from calibre_plugins.obok_dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
|
||||||
from calibre_plugins.obok_dedrm.utilities import (debug_print)
|
from calibre_plugins.obok_dedrm.utilities import (debug_print)
|
||||||
try:
|
try:
|
||||||
debug_print("obok::config.py - loading translations")
|
debug_print("obok::config.py - loading translations")
|
||||||
@@ -26,7 +34,10 @@ class ConfigWidget(QWidget):
|
|||||||
self.plugin_action = plugin_action
|
self.plugin_action = plugin_action
|
||||||
layout = QVBoxLayout(self)
|
layout = QVBoxLayout(self)
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
# copy of preferences
|
||||||
|
self.tmpserials = plugin_prefs['kobo_serials']
|
||||||
|
|
||||||
combo_label = QLabel(_('When should Obok try to insert EPUBs into existing calibre entries?'), self)
|
combo_label = QLabel(_('When should Obok try to insert EPUBs into existing calibre entries?'), self)
|
||||||
layout.addWidget(combo_label)
|
layout.addWidget(combo_label)
|
||||||
self.find_homes = QComboBox()
|
self.find_homes = QComboBox()
|
||||||
@@ -35,6 +46,178 @@ class ConfigWidget(QWidget):
|
|||||||
self.find_homes.addItems([_('Ask'), _('Always'), _('Never')])
|
self.find_homes.addItems([_('Ask'), _('Always'), _('Never')])
|
||||||
index = self.find_homes.findText(plugin_prefs['finding_homes_for_formats'])
|
index = self.find_homes.findText(plugin_prefs['finding_homes_for_formats'])
|
||||||
self.find_homes.setCurrentIndex(index)
|
self.find_homes.setCurrentIndex(index)
|
||||||
|
|
||||||
|
self.serials_button = QtGui.QPushButton(self)
|
||||||
|
self.serials_button.setToolTip(_(u"Click to manage Kobo serial numbers for Kobo ebooks"))
|
||||||
|
self.serials_button.setText(u"Kobo devices serials")
|
||||||
|
self.serials_button.clicked.connect(self.edit_serials)
|
||||||
|
layout.addWidget(self.serials_button)
|
||||||
|
|
||||||
|
|
||||||
|
def edit_serials(self):
|
||||||
|
d = ManageKeysDialog(self,u"Kobo device serial numbers",self.tmpserials, AddSerialDialog)
|
||||||
|
d.exec_()
|
||||||
|
|
||||||
|
|
||||||
def save_settings(self):
|
def save_settings(self):
|
||||||
plugin_prefs['finding_homes_for_formats'] = unicode(self.find_homes.currentText())
|
plugin_prefs['finding_homes_for_formats'] = unicode(self.find_homes.currentText())
|
||||||
|
plugin_prefs['kobo_serials'] = self.tmpserials
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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.json_file = (keyfile_ext == u"k4i")
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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.setIcon(QIcon(I('plus.png')))
|
||||||
|
self._add_key_button.setToolTip(u"Create new {0}".format(self.key_type_name))
|
||||||
|
self._add_key_button.clicked.connect(self.add_key)
|
||||||
|
button_layout.addWidget(self._add_key_button)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
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 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())
|
||||||
|
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())
|
||||||
|
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
|
||||||
|
self.plugin_keys.remove(keyname)
|
||||||
|
|
||||||
|
self.listy.clear()
|
||||||
|
self.populate_list()
|
||||||
|
|
||||||
|
class AddSerialDialog(QDialog):
|
||||||
|
def __init__(self, parent=None,):
|
||||||
|
QDialog.__init__(self, parent)
|
||||||
|
self.parent = parent
|
||||||
|
self.setWindowTitle(u"{0} {1}: Add New eInk Kobo 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 Kobo Serial Number:", self))
|
||||||
|
self.key_ledit = QLineEdit("", self)
|
||||||
|
self.key_ledit.setToolTip(u"Enter an eInk Kobo serial number. EInk Kobo serial numbers are 13 characters long and usually start with a 'N'. Kobo Serial Numbers are case-sensitive, so be sure to enter the upper and lower case letters unchanged.")
|
||||||
|
key_group.addWidget(self.key_ledit)
|
||||||
|
|
||||||
|
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()).strip()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def key_value(self):
|
||||||
|
return unicode(self.key_ledit.text()).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) != 13:
|
||||||
|
errmsg = u"EInk Kobo Serial Numbers must be 13 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)
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.1 KiB |
@@ -1,6 +1,30 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Version 6.3.5 January 2016
|
||||||
|
# Update for latest version of Windows Desktop app.
|
||||||
|
# Support Kobo devices in the command line version.
|
||||||
|
#
|
||||||
|
# Version 3.1.9 November 2015
|
||||||
|
# Handle Kobo Desktop under wine on Linux
|
||||||
|
#
|
||||||
|
# Version 3.1.8 November 2015
|
||||||
|
# Handle the case of Kobo Arc or Vox device (i.e. don't crash).
|
||||||
|
#
|
||||||
|
# Version 3.1.7 October 2015
|
||||||
|
# Handle the case of no device or database more gracefully.
|
||||||
|
#
|
||||||
|
# Version 3.1.6 September 2015
|
||||||
|
# Enable support for Kobo devices
|
||||||
|
# More character encoding fixes (unicode strings)
|
||||||
|
#
|
||||||
|
# Version 3.1.5 September 2015
|
||||||
|
# Removed requirement that a purchase has been made.
|
||||||
|
# Also add in character encoding fixes
|
||||||
|
#
|
||||||
|
# Version 3.1.4 September 2015
|
||||||
|
# Updated for version 3.17 of the Windows Desktop app.
|
||||||
|
#
|
||||||
# Version 3.1.3 August 2015
|
# Version 3.1.3 August 2015
|
||||||
# Add translations for Portuguese and Arabic
|
# Add translations for Portuguese and Arabic
|
||||||
#
|
#
|
||||||
@@ -112,7 +136,8 @@
|
|||||||
#
|
#
|
||||||
"""Manage all Kobo books, either encrypted or DRM-free."""
|
"""Manage all Kobo books, either encrypted or DRM-free."""
|
||||||
|
|
||||||
__version__ = '3.1.3'
|
__version__ = '3.1.9'
|
||||||
|
__about__ = u"Obok v{0}\nCopyright © 2012-2015 Physisticated et al.".format(__version__)
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@@ -126,6 +151,18 @@ import hashlib
|
|||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
import string
|
import string
|
||||||
import shutil
|
import shutil
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
can_parse_xml = True
|
||||||
|
try:
|
||||||
|
from xml.etree import ElementTree as ET
|
||||||
|
# print u"using xml.etree for xml parsing"
|
||||||
|
except ImportError:
|
||||||
|
can_parse_xml = False
|
||||||
|
# print u"Cannot find xml.etree, disabling extraction of serial numbers"
|
||||||
|
|
||||||
|
# List of all known hash keys
|
||||||
|
KOBO_HASH_KEYS = ['88b3a2e13', 'XzUhGYdFp', 'NoCanLook']
|
||||||
|
|
||||||
class ENCRYPTIONError(Exception):
|
class ENCRYPTIONError(Exception):
|
||||||
pass
|
pass
|
||||||
@@ -212,6 +249,24 @@ def _load_crypto():
|
|||||||
|
|
||||||
AES = _load_crypto()
|
AES = _load_crypto()
|
||||||
|
|
||||||
|
# Wrap a stream so that output gets flushed immediately
|
||||||
|
# and also make sure that any unicode strings get
|
||||||
|
# encoded using "replace" before writing them.
|
||||||
|
class SafeUnbuffered:
|
||||||
|
def __init__(self, stream):
|
||||||
|
self.stream = stream
|
||||||
|
self.encoding = stream.encoding
|
||||||
|
if self.encoding == None:
|
||||||
|
self.encoding = "utf-8"
|
||||||
|
def write(self, data):
|
||||||
|
if isinstance(data,unicode):
|
||||||
|
data = data.encode(self.encoding,"replace")
|
||||||
|
self.stream.write(data)
|
||||||
|
self.stream.flush()
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
|
|
||||||
class KoboLibrary(object):
|
class KoboLibrary(object):
|
||||||
"""The Kobo library.
|
"""The Kobo library.
|
||||||
|
|
||||||
@@ -219,23 +274,93 @@ class KoboLibrary(object):
|
|||||||
written by the Kobo Desktop Edition application, including the list
|
written by the Kobo Desktop Edition application, including the list
|
||||||
of books, their titles, and the user's encryption key(s)."""
|
of books, their titles, and the user's encryption key(s)."""
|
||||||
|
|
||||||
def __init__ (self):
|
def __init__ (self, serials = [], device_path = None):
|
||||||
print u"Obok v{0}\nCopyright © 2012-2014 Physisticated et al.".format(__version__)
|
print __about__
|
||||||
if sys.platform.startswith('win'):
|
self.kobodir = u""
|
||||||
if sys.getwindowsversion().major > 5:
|
kobodb = u""
|
||||||
self.kobodir = os.environ['LOCALAPPDATA']
|
|
||||||
else:
|
# Order of checks
|
||||||
self.kobodir = os.path.join(os.environ['USERPROFILE'], 'Local Settings', 'Application Data')
|
# 1. first check if a device_path has been passed in, and whether
|
||||||
self.kobodir = os.path.join(self.kobodir, 'Kobo', 'Kobo Desktop Edition')
|
# we can find the sqlite db in the respective place
|
||||||
elif sys.platform.startswith('darwin'):
|
# 2. if 1., and we got some serials passed in (from saved
|
||||||
self.kobodir = os.path.join(os.environ['HOME'], 'Library', 'Application Support', 'Kobo', 'Kobo Desktop Edition')
|
# settings in calibre), just use it
|
||||||
self.bookdir = os.path.join(self.kobodir, 'kepub')
|
# 3. if 1. worked, but we didn't get serials, try to parse them
|
||||||
kobodb = os.path.join(self.kobodir, 'Kobo.sqlite')
|
# from the device, if this didn't work, unset everything
|
||||||
self.__sqlite = sqlite3.connect(kobodb)
|
# 4. if by now we don't have kobodir set, give up on device and
|
||||||
self.__cursor = self.__sqlite.cursor()
|
# try to use the Desktop app.
|
||||||
self._userkeys = []
|
|
||||||
self._books = []
|
# step 1. check whether this looks like a real device
|
||||||
self._volumeID = []
|
if (device_path):
|
||||||
|
# we got a device path
|
||||||
|
self.kobodir = os.path.join(device_path, u".kobo")
|
||||||
|
# devices use KoboReader.sqlite
|
||||||
|
kobodb = os.path.join(self.kobodir, u"KoboReader.sqlite")
|
||||||
|
if (not(os.path.isfile(kobodb))):
|
||||||
|
# device path seems to be wrong, unset it
|
||||||
|
device_path = u""
|
||||||
|
self.kobodir = u""
|
||||||
|
kobodb = u""
|
||||||
|
|
||||||
|
if (self.kobodir):
|
||||||
|
# step 3. we found a device but didn't get serials, try to get them
|
||||||
|
if (len(serials) == 0):
|
||||||
|
# we got a device path but no saved serial
|
||||||
|
# try to get the serial from the device
|
||||||
|
# print u"get_device_settings - device_path = {0}".format(device_path)
|
||||||
|
# get serial from device_path/.adobe-digital-editions/device.xml
|
||||||
|
if can_parse_xml:
|
||||||
|
devicexml = os.path.join(device_path, '.adobe-digital-editions', 'device.xml')
|
||||||
|
# print u"trying to load {0}".format(devicexml)
|
||||||
|
if (os.path.exists(devicexml)):
|
||||||
|
# print u"trying to parse {0}".format(devicexml)
|
||||||
|
xmltree = ET.parse(devicexml)
|
||||||
|
for node in xmltree.iter():
|
||||||
|
if "deviceSerial" in node.tag:
|
||||||
|
serial = node.text
|
||||||
|
# print u"found serial {0}".format(serial)
|
||||||
|
serials.append(serial)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# print u"cannot get serials from device."
|
||||||
|
device_path = u""
|
||||||
|
self.kobodir = u""
|
||||||
|
kobodb = u""
|
||||||
|
|
||||||
|
if (self.kobodir == u""):
|
||||||
|
# step 4. we haven't found a device with serials, so try desktop apps
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
import _winreg as winreg
|
||||||
|
if sys.getwindowsversion().major > 5:
|
||||||
|
if 'LOCALAPPDATA' in os.environ.keys():
|
||||||
|
# Python 2.x does not return unicode env. Use Python 3.x
|
||||||
|
self.kobodir = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
|
||||||
|
if (self.kobodir == u""):
|
||||||
|
if 'USERPROFILE' in os.environ.keys():
|
||||||
|
# Python 2.x does not return unicode env. Use Python 3.x
|
||||||
|
self.kobodir = os.path.join(winreg.ExpandEnvironmentStrings(u"%USERPROFILE%"), u"Local Settings", u"Application Data")
|
||||||
|
self.kobodir = os.path.join(self.kobodir, u"Kobo", u"Kobo Desktop Edition")
|
||||||
|
elif sys.platform.startswith('darwin'):
|
||||||
|
self.kobodir = os.path.join(os.environ['HOME'], u"Library", u"Application Support", u"Kobo", u"Kobo Desktop Edition")
|
||||||
|
elif linux_path != None:
|
||||||
|
# Probably Linux, let's get the wine prefix and path to Kobo.
|
||||||
|
self.kobodir = os.path.join(linux_path, u"Local Settings", u"Application Data", u"Kobo", u"Kobo Desktop Edition")
|
||||||
|
# desktop versions use Kobo.sqlite
|
||||||
|
kobodb = os.path.join(self.kobodir, u"Kobo.sqlite")
|
||||||
|
# check for existence of file
|
||||||
|
if (not(os.path.isfile(kobodb))):
|
||||||
|
# give up here, we haven't found anything useful
|
||||||
|
self.kobodir = u""
|
||||||
|
kobodb = u""
|
||||||
|
|
||||||
|
|
||||||
|
if (self.kobodir != u""):
|
||||||
|
self.bookdir = os.path.join(self.kobodir, u"kepub")
|
||||||
|
self.__sqlite = sqlite3.connect(kobodb)
|
||||||
|
self.__cursor = self.__sqlite.cursor()
|
||||||
|
self._userkeys = []
|
||||||
|
self._books = []
|
||||||
|
self._volumeID = []
|
||||||
|
self._serials = serials
|
||||||
|
|
||||||
def close (self):
|
def close (self):
|
||||||
"""Closes the database used by the library."""
|
"""Closes the database used by the library."""
|
||||||
@@ -249,9 +374,8 @@ class KoboLibrary(object):
|
|||||||
"""
|
"""
|
||||||
if len(self._userkeys) != 0:
|
if len(self._userkeys) != 0:
|
||||||
return self._userkeys
|
return self._userkeys
|
||||||
userid = self.__getuserid()
|
|
||||||
for macaddr in self.__getmacaddrs():
|
for macaddr in self.__getmacaddrs():
|
||||||
self._userkeys.append(self.__getuserkey(macaddr, userid))
|
self._userkeys.extend(self.__getuserkeys(macaddr))
|
||||||
return self._userkeys
|
return self._userkeys
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -277,7 +401,7 @@ class KoboLibrary(object):
|
|||||||
|
|
||||||
def __bookfile (self, volumeid):
|
def __bookfile (self, volumeid):
|
||||||
"""The filename needed to open a given book."""
|
"""The filename needed to open a given book."""
|
||||||
return os.path.join(self.kobodir, 'kepub', volumeid)
|
return os.path.join(self.kobodir, u"kepub", volumeid)
|
||||||
|
|
||||||
def __getmacaddrs (self):
|
def __getmacaddrs (self):
|
||||||
"""The list of all MAC addresses on this machine."""
|
"""The list of all MAC addresses on this machine."""
|
||||||
@@ -293,17 +417,44 @@ class KoboLibrary(object):
|
|||||||
output = subprocess.check_output('/sbin/ifconfig -a', shell=True)
|
output = subprocess.check_output('/sbin/ifconfig -a', shell=True)
|
||||||
matches = c.findall(output)
|
matches = c.findall(output)
|
||||||
for m in matches:
|
for m in matches:
|
||||||
# print "m:",m[0]
|
# print u"m:{0}".format(m[0])
|
||||||
macaddrs.append(m[0].upper())
|
macaddrs.append(m[0].upper())
|
||||||
|
else:
|
||||||
|
# probably linux, let's try ipconfig under wine
|
||||||
|
c = re.compile('\s(' + '[0-9a-f]{2}-' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
||||||
|
for line in os.popen('ipconfig /all'):
|
||||||
|
m = c.search(line)
|
||||||
|
if m:
|
||||||
|
macaddrs.append(re.sub("-", ":", m.group(1)).upper())
|
||||||
|
|
||||||
|
# extend the list of macaddrs in any case with the serials
|
||||||
|
# cannot hurt ;-)
|
||||||
|
macaddrs.extend(self._serials)
|
||||||
|
|
||||||
return macaddrs
|
return macaddrs
|
||||||
|
|
||||||
def __getuserid (self):
|
def __getuserids (self):
|
||||||
return self.__cursor.execute('SELECT UserID FROM user WHERE HasMadePurchase = "true"').fetchone()[0]
|
userids = []
|
||||||
|
cursor = self.__cursor.execute('SELECT UserID FROM user')
|
||||||
def __getuserkey (self, macaddr, userid):
|
row = cursor.fetchone()
|
||||||
deviceid = hashlib.sha256('NoCanLook' + macaddr).hexdigest()
|
while row is not None:
|
||||||
userkey = hashlib.sha256(deviceid + userid).hexdigest()
|
try:
|
||||||
return binascii.a2b_hex(userkey[32:])
|
userid = row[0]
|
||||||
|
userids.append(userid)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
row = cursor.fetchone()
|
||||||
|
return userids
|
||||||
|
|
||||||
|
def __getuserkeys (self, macaddr):
|
||||||
|
userids = self.__getuserids()
|
||||||
|
userkeys = []
|
||||||
|
for hash in KOBO_HASH_KEYS:
|
||||||
|
deviceid = hashlib.sha256(hash + macaddr).hexdigest()
|
||||||
|
for userid in userids:
|
||||||
|
userkey = hashlib.sha256(deviceid + userid).hexdigest()
|
||||||
|
userkeys.append(binascii.a2b_hex(userkey[32:]))
|
||||||
|
return userkeys
|
||||||
|
|
||||||
class KoboBook(object):
|
class KoboBook(object):
|
||||||
"""A Kobo book.
|
"""A Kobo book.
|
||||||
@@ -410,13 +561,13 @@ class KoboFile(object):
|
|||||||
if contents[:5]=="<?xml":
|
if contents[:5]=="<?xml":
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
print "Bad XML: ",contents[:5]
|
print u"Bad XML: {0}".format(contents[:5])
|
||||||
raise ValueError
|
raise ValueError
|
||||||
if self.mimetype == 'image/jpeg':
|
if self.mimetype == 'image/jpeg':
|
||||||
if contents[:3] == '\xff\xd8\xff':
|
if contents[:3] == '\xff\xd8\xff':
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
print "Bad JPEG: ", contents[:3].encode('hex')
|
print u"Bad JPEG: {0}".format(contents[:3].encode('hex'))
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -438,36 +589,44 @@ class KoboFile(object):
|
|||||||
contents = contents[:-padding]
|
contents = contents[:-padding]
|
||||||
return contents
|
return contents
|
||||||
|
|
||||||
if __name__ == '__main__':
|
def cli_main():
|
||||||
|
description = __about__
|
||||||
|
epilog = u"Parsing of arguments failed."
|
||||||
|
parser = argparse.ArgumentParser(prog=sys.argv[0], description=description, epilog=epilog)
|
||||||
|
parser.add_argument('--devicedir', default='/media/KOBOeReader', help="directory of connected Kobo device")
|
||||||
|
args = vars(parser.parse_args())
|
||||||
|
serials = []
|
||||||
|
devicedir = u""
|
||||||
|
if args['devicedir']:
|
||||||
|
devicedir = args['devicedir']
|
||||||
|
|
||||||
lib = KoboLibrary()
|
lib = KoboLibrary(serials, devicedir)
|
||||||
|
|
||||||
for i, book in enumerate(lib.books):
|
for i, book in enumerate(lib.books):
|
||||||
print ('%d: %s' % (i + 1, book.title)).encode('ascii', 'ignore')
|
print u"{0}: {1}".format(i + 1, book.title)
|
||||||
|
|
||||||
num_string = raw_input("Convert book number... ")
|
num_string = raw_input(u"Convert book number... ")
|
||||||
try:
|
try:
|
||||||
num = int(num_string)
|
num = int(num_string)
|
||||||
book = lib.books[num - 1]
|
book = lib.books[num - 1]
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
print "Converting", book.title
|
print u"Converting {0}".format(book.title)
|
||||||
|
|
||||||
zin = zipfile.ZipFile(book.filename, "r")
|
zin = zipfile.ZipFile(book.filename, "r")
|
||||||
# make filename out of Unicode alphanumeric and whitespace equivalents from title
|
# make filename out of Unicode alphanumeric and whitespace equivalents from title
|
||||||
outname = "%s.epub" % (re.sub('[^\s\w]', '', book.title, 0, re.UNICODE))
|
outname = u"{0}.epub".format(re.sub('[^\s\w]', '_', book.title, 0, re.UNICODE))
|
||||||
|
|
||||||
if (book.type == 'drm-free'):
|
if (book.type == 'drm-free'):
|
||||||
print "DRM-free book, conversion is not needed"
|
print u"DRM-free book, conversion is not needed"
|
||||||
shutil.copyfile(book.filename, outname)
|
shutil.copyfile(book.filename, outname)
|
||||||
print "Book saved as", os.path.join(os.getcwd(), outname)
|
print u"Book saved as {0}".format(os.path.join(os.getcwd(), outname))
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
result = 1
|
result = 1
|
||||||
for userkey in lib.userkeys:
|
for userkey in lib.userkeys:
|
||||||
# print "Trying key: ",userkey.encode('hex_codec')
|
print u"Trying key: {0}".format(userkey.encode('hex_codec'))
|
||||||
confirmedGood = False
|
|
||||||
try:
|
try:
|
||||||
zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
|
zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
|
||||||
for filename in zin.namelist():
|
for filename in zin.namelist():
|
||||||
@@ -476,18 +635,25 @@ if __name__ == '__main__':
|
|||||||
file = book.encryptedfiles[filename]
|
file = book.encryptedfiles[filename]
|
||||||
contents = file.decrypt(userkey, contents)
|
contents = file.decrypt(userkey, contents)
|
||||||
# Parse failures mean the key is probably wrong.
|
# Parse failures mean the key is probably wrong.
|
||||||
if not confirmedGood:
|
file.check(contents)
|
||||||
confirmedGood = file.check(contents)
|
|
||||||
zout.writestr(filename, contents)
|
zout.writestr(filename, contents)
|
||||||
zout.close()
|
zout.close()
|
||||||
print "Book saved as", os.path.join(os.getcwd(), outname)
|
print u"Decryption succeeded."
|
||||||
|
print u"Book saved as {0}".format(os.path.join(os.getcwd(), outname))
|
||||||
result = 0
|
result = 0
|
||||||
break
|
break
|
||||||
except ValueError:
|
except ValueError:
|
||||||
print "Decryption failed, trying next key"
|
print u"Decryption failed."
|
||||||
zout.close()
|
zout.close()
|
||||||
os.remove(outname)
|
os.remove(outname)
|
||||||
|
|
||||||
zin.close()
|
zin.close()
|
||||||
lib.close()
|
lib.close()
|
||||||
exit(result)
|
if result != 0:
|
||||||
|
print u"Could not decrypt book with any of the keys found."
|
||||||
|
return result
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||||
|
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||||
|
sys.exit(cli_main())
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
obok_plugin.zip
|
obok_plugin.zip
|
||||||
================
|
================
|
||||||
|
|
||||||
This plugin will remove the DRM from Kobo ebooks download on Mac or Windows using the Kobo desktop application.
|
This plugin will remove the DRM from Kobo ebooks download on Mac or Windows using the Kobo desktop application, or from Kobo ebooks on an attached E-Ink Kobo reader (but not a Kobo Arc or Kobo Vox). If both are available, ebooks will be read from the attached E-Ink Kobo reader. To import from the desktop application, unplug the Kobo reader.
|
||||||
|
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
@@ -11,7 +11,7 @@ Do NOT select "Get plugins to enhance calibre" as this is reserved for 'official
|
|||||||
|
|
||||||
Customization
|
Customization
|
||||||
-------------
|
-------------
|
||||||
No customization is required, except choosing which menus will show the plugin.
|
No customization is required, except choosing which menus will show the plugin. Altough the ability to enter a device serial number is given, this should not need to be filled in, as the serial number should be picked up automatically from the attached Kobo reader.
|
||||||
|
|
||||||
|
|
||||||
Using the plugin
|
Using the plugin
|
||||||
@@ -47,6 +47,7 @@ Credits
|
|||||||
-------
|
-------
|
||||||
The original obok script was by Physisticated
|
The original obok script was by Physisticated
|
||||||
The plugin conversion was done anonymously.
|
The plugin conversion was done anonymously.
|
||||||
|
The Kobo reader support was added by norbusan
|
||||||
|
|
||||||
Improvements to the script and the plugin adaption have been by numerous people since.
|
Additional improvements to the script and the plugin adaption by numerous anonymous people.
|
||||||
|
|
||||||
|
|||||||
@@ -16,16 +16,18 @@ from __future__ import with_statement
|
|||||||
# - and added in unicode command line support
|
# - and added in unicode command line support
|
||||||
# 1.3 - added in TkInter interface, output to a file
|
# 1.3 - added in TkInter interface, output to a file
|
||||||
# 1.4 - Fix some problems identified by Aldo Bleeker
|
# 1.4 - Fix some problems identified by Aldo Bleeker
|
||||||
|
# 1.5 - Fix another problem identified by Aldo Bleeker
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Retrieve Kindle for Android Serial Number.
|
Retrieve Kindle for Android Serial Number.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = '1.4'
|
__version__ = '1.5'
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import traceback
|
||||||
import getopt
|
import getopt
|
||||||
import tempfile
|
import tempfile
|
||||||
import zlib
|
import zlib
|
||||||
@@ -220,20 +222,30 @@ def get_serials2(path=STORAGE2):
|
|||||||
userdata_keys = cursor.fetchall()
|
userdata_keys = cursor.fetchall()
|
||||||
dsns = []
|
dsns = []
|
||||||
for userdata_row in userdata_keys:
|
for userdata_row in userdata_keys:
|
||||||
if userdata_row:
|
try:
|
||||||
userdata_utf8 = userdata_row[0].encode('utf8')
|
if userdata_row and userdata_row[0]:
|
||||||
if len(userdata_utf8) > 0:
|
userdata_utf8 = userdata_row[0].encode('utf8')
|
||||||
dsns.append(userdata_utf8)
|
if len(userdata_utf8) > 0:
|
||||||
|
dsns.append(userdata_utf8)
|
||||||
|
except:
|
||||||
|
print "Error getting one of the device serial name keys"
|
||||||
|
traceback.print_exc()
|
||||||
|
pass
|
||||||
dsns = list(set(dsns))
|
dsns = list(set(dsns))
|
||||||
|
|
||||||
cursor.execute('''select userdata_value from userdata where userdata_key like '%/%kindle.account.tokens%' ''')
|
cursor.execute('''select userdata_value from userdata where userdata_key like '%/%kindle.account.tokens%' ''')
|
||||||
userdata_keys = cursor.fetchall()
|
userdata_keys = cursor.fetchall()
|
||||||
tokens = []
|
tokens = []
|
||||||
for userdata_row in userdata_keys:
|
for userdata_row in userdata_keys:
|
||||||
if userdata_row:
|
try:
|
||||||
userdata_utf8 = userdata_row[0].encode('utf8')
|
if userdata_row and userdata_row[0]:
|
||||||
if len(userdata_utf8) > 0:
|
userdata_utf8 = userdata_row[0].encode('utf8')
|
||||||
tokens.append(userdata_utf8)
|
if len(userdata_utf8) > 0:
|
||||||
|
tokens.append(userdata_utf8)
|
||||||
|
except:
|
||||||
|
print "Error getting one of the account token keys"
|
||||||
|
traceback.print_exc()
|
||||||
|
pass
|
||||||
tokens = list(set(tokens))
|
tokens = list(set(tokens))
|
||||||
|
|
||||||
serials = []
|
serials = []
|
||||||
@@ -377,7 +389,6 @@ def gui_main():
|
|||||||
import Tkconstants
|
import Tkconstants
|
||||||
import tkMessageBox
|
import tkMessageBox
|
||||||
import tkFileDialog
|
import tkFileDialog
|
||||||
import traceback
|
|
||||||
except:
|
except:
|
||||||
print "Tkinter not installed"
|
print "Tkinter not installed"
|
||||||
return cli_main()
|
return cli_main()
|
||||||
|
|||||||
@@ -1,6 +1,33 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Version 6.3.5 January 2016
|
||||||
|
# Update for latest version of Windows Desktop app.
|
||||||
|
# Support Kobo devices in the command line version.
|
||||||
|
#
|
||||||
|
# Version 3.1.9 November 2015
|
||||||
|
# Handle Kobo Desktop under wine on Linux
|
||||||
|
#
|
||||||
|
# Version 3.1.8 November 2015
|
||||||
|
# Handle the case of Kobo Arc or Vox device (i.e. don't crash).
|
||||||
|
#
|
||||||
|
# Version 3.1.7 October 2015
|
||||||
|
# Handle the case of no device or database more gracefully.
|
||||||
|
#
|
||||||
|
# Version 3.1.6 September 2015
|
||||||
|
# Enable support for Kobo devices
|
||||||
|
# More character encoding fixes (unicode strings)
|
||||||
|
#
|
||||||
|
# Version 3.1.5 September 2015
|
||||||
|
# Removed requirement that a purchase has been made.
|
||||||
|
# Also add in character encoding fixes
|
||||||
|
#
|
||||||
|
# Version 3.1.4 September 2015
|
||||||
|
# Updated for version 3.17 of the Windows Desktop app.
|
||||||
|
#
|
||||||
|
# Version 3.1.3 August 2015
|
||||||
|
# Add translations for Portuguese and Arabic
|
||||||
|
#
|
||||||
# Version 3.1.2 January 2015
|
# Version 3.1.2 January 2015
|
||||||
# Add coding, version number and version announcement
|
# Add coding, version number and version announcement
|
||||||
#
|
#
|
||||||
@@ -109,7 +136,8 @@
|
|||||||
#
|
#
|
||||||
"""Manage all Kobo books, either encrypted or DRM-free."""
|
"""Manage all Kobo books, either encrypted or DRM-free."""
|
||||||
|
|
||||||
__version__ = '3.1.1'
|
__version__ = '3.1.9'
|
||||||
|
__about__ = u"Obok v{0}\nCopyright © 2012-2015 Physisticated et al.".format(__version__)
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@@ -123,6 +151,18 @@ import hashlib
|
|||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
import string
|
import string
|
||||||
import shutil
|
import shutil
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
can_parse_xml = True
|
||||||
|
try:
|
||||||
|
from xml.etree import ElementTree as ET
|
||||||
|
# print u"using xml.etree for xml parsing"
|
||||||
|
except ImportError:
|
||||||
|
can_parse_xml = False
|
||||||
|
# print u"Cannot find xml.etree, disabling extraction of serial numbers"
|
||||||
|
|
||||||
|
# List of all known hash keys
|
||||||
|
KOBO_HASH_KEYS = ['88b3a2e13', 'XzUhGYdFp', 'NoCanLook']
|
||||||
|
|
||||||
class ENCRYPTIONError(Exception):
|
class ENCRYPTIONError(Exception):
|
||||||
pass
|
pass
|
||||||
@@ -209,6 +249,24 @@ def _load_crypto():
|
|||||||
|
|
||||||
AES = _load_crypto()
|
AES = _load_crypto()
|
||||||
|
|
||||||
|
# Wrap a stream so that output gets flushed immediately
|
||||||
|
# and also make sure that any unicode strings get
|
||||||
|
# encoded using "replace" before writing them.
|
||||||
|
class SafeUnbuffered:
|
||||||
|
def __init__(self, stream):
|
||||||
|
self.stream = stream
|
||||||
|
self.encoding = stream.encoding
|
||||||
|
if self.encoding == None:
|
||||||
|
self.encoding = "utf-8"
|
||||||
|
def write(self, data):
|
||||||
|
if isinstance(data,unicode):
|
||||||
|
data = data.encode(self.encoding,"replace")
|
||||||
|
self.stream.write(data)
|
||||||
|
self.stream.flush()
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
|
|
||||||
class KoboLibrary(object):
|
class KoboLibrary(object):
|
||||||
"""The Kobo library.
|
"""The Kobo library.
|
||||||
|
|
||||||
@@ -216,23 +274,93 @@ class KoboLibrary(object):
|
|||||||
written by the Kobo Desktop Edition application, including the list
|
written by the Kobo Desktop Edition application, including the list
|
||||||
of books, their titles, and the user's encryption key(s)."""
|
of books, their titles, and the user's encryption key(s)."""
|
||||||
|
|
||||||
def __init__ (self):
|
def __init__ (self, serials = [], device_path = None):
|
||||||
print u"Obok v{0}\nCopyright © 2012-2014 Physisticated et al.".format(__version__)
|
print __about__
|
||||||
if sys.platform.startswith('win'):
|
self.kobodir = u""
|
||||||
if sys.getwindowsversion().major > 5:
|
kobodb = u""
|
||||||
self.kobodir = os.environ['LOCALAPPDATA']
|
|
||||||
else:
|
# Order of checks
|
||||||
self.kobodir = os.path.join(os.environ['USERPROFILE'], 'Local Settings', 'Application Data')
|
# 1. first check if a device_path has been passed in, and whether
|
||||||
self.kobodir = os.path.join(self.kobodir, 'Kobo', 'Kobo Desktop Edition')
|
# we can find the sqlite db in the respective place
|
||||||
elif sys.platform.startswith('darwin'):
|
# 2. if 1., and we got some serials passed in (from saved
|
||||||
self.kobodir = os.path.join(os.environ['HOME'], 'Library', 'Application Support', 'Kobo', 'Kobo Desktop Edition')
|
# settings in calibre), just use it
|
||||||
self.bookdir = os.path.join(self.kobodir, 'kepub')
|
# 3. if 1. worked, but we didn't get serials, try to parse them
|
||||||
kobodb = os.path.join(self.kobodir, 'Kobo.sqlite')
|
# from the device, if this didn't work, unset everything
|
||||||
self.__sqlite = sqlite3.connect(kobodb)
|
# 4. if by now we don't have kobodir set, give up on device and
|
||||||
self.__cursor = self.__sqlite.cursor()
|
# try to use the Desktop app.
|
||||||
self._userkeys = []
|
|
||||||
self._books = []
|
# step 1. check whether this looks like a real device
|
||||||
self._volumeID = []
|
if (device_path):
|
||||||
|
# we got a device path
|
||||||
|
self.kobodir = os.path.join(device_path, u".kobo")
|
||||||
|
# devices use KoboReader.sqlite
|
||||||
|
kobodb = os.path.join(self.kobodir, u"KoboReader.sqlite")
|
||||||
|
if (not(os.path.isfile(kobodb))):
|
||||||
|
# device path seems to be wrong, unset it
|
||||||
|
device_path = u""
|
||||||
|
self.kobodir = u""
|
||||||
|
kobodb = u""
|
||||||
|
|
||||||
|
if (self.kobodir):
|
||||||
|
# step 3. we found a device but didn't get serials, try to get them
|
||||||
|
if (len(serials) == 0):
|
||||||
|
# we got a device path but no saved serial
|
||||||
|
# try to get the serial from the device
|
||||||
|
# print u"get_device_settings - device_path = {0}".format(device_path)
|
||||||
|
# get serial from device_path/.adobe-digital-editions/device.xml
|
||||||
|
if can_parse_xml:
|
||||||
|
devicexml = os.path.join(device_path, '.adobe-digital-editions', 'device.xml')
|
||||||
|
# print u"trying to load {0}".format(devicexml)
|
||||||
|
if (os.path.exists(devicexml)):
|
||||||
|
# print u"trying to parse {0}".format(devicexml)
|
||||||
|
xmltree = ET.parse(devicexml)
|
||||||
|
for node in xmltree.iter():
|
||||||
|
if "deviceSerial" in node.tag:
|
||||||
|
serial = node.text
|
||||||
|
# print u"found serial {0}".format(serial)
|
||||||
|
serials.append(serial)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# print u"cannot get serials from device."
|
||||||
|
device_path = u""
|
||||||
|
self.kobodir = u""
|
||||||
|
kobodb = u""
|
||||||
|
|
||||||
|
if (self.kobodir == u""):
|
||||||
|
# step 4. we haven't found a device with serials, so try desktop apps
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
import _winreg as winreg
|
||||||
|
if sys.getwindowsversion().major > 5:
|
||||||
|
if 'LOCALAPPDATA' in os.environ.keys():
|
||||||
|
# Python 2.x does not return unicode env. Use Python 3.x
|
||||||
|
self.kobodir = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
|
||||||
|
if (self.kobodir == u""):
|
||||||
|
if 'USERPROFILE' in os.environ.keys():
|
||||||
|
# Python 2.x does not return unicode env. Use Python 3.x
|
||||||
|
self.kobodir = os.path.join(winreg.ExpandEnvironmentStrings(u"%USERPROFILE%"), u"Local Settings", u"Application Data")
|
||||||
|
self.kobodir = os.path.join(self.kobodir, u"Kobo", u"Kobo Desktop Edition")
|
||||||
|
elif sys.platform.startswith('darwin'):
|
||||||
|
self.kobodir = os.path.join(os.environ['HOME'], u"Library", u"Application Support", u"Kobo", u"Kobo Desktop Edition")
|
||||||
|
elif linux_path != None:
|
||||||
|
# Probably Linux, let's get the wine prefix and path to Kobo.
|
||||||
|
self.kobodir = os.path.join(linux_path, u"Local Settings", u"Application Data", u"Kobo", u"Kobo Desktop Edition")
|
||||||
|
# desktop versions use Kobo.sqlite
|
||||||
|
kobodb = os.path.join(self.kobodir, u"Kobo.sqlite")
|
||||||
|
# check for existence of file
|
||||||
|
if (not(os.path.isfile(kobodb))):
|
||||||
|
# give up here, we haven't found anything useful
|
||||||
|
self.kobodir = u""
|
||||||
|
kobodb = u""
|
||||||
|
|
||||||
|
|
||||||
|
if (self.kobodir != u""):
|
||||||
|
self.bookdir = os.path.join(self.kobodir, u"kepub")
|
||||||
|
self.__sqlite = sqlite3.connect(kobodb)
|
||||||
|
self.__cursor = self.__sqlite.cursor()
|
||||||
|
self._userkeys = []
|
||||||
|
self._books = []
|
||||||
|
self._volumeID = []
|
||||||
|
self._serials = serials
|
||||||
|
|
||||||
def close (self):
|
def close (self):
|
||||||
"""Closes the database used by the library."""
|
"""Closes the database used by the library."""
|
||||||
@@ -246,9 +374,8 @@ class KoboLibrary(object):
|
|||||||
"""
|
"""
|
||||||
if len(self._userkeys) != 0:
|
if len(self._userkeys) != 0:
|
||||||
return self._userkeys
|
return self._userkeys
|
||||||
userid = self.__getuserid()
|
|
||||||
for macaddr in self.__getmacaddrs():
|
for macaddr in self.__getmacaddrs():
|
||||||
self._userkeys.append(self.__getuserkey(macaddr, userid))
|
self._userkeys.extend(self.__getuserkeys(macaddr))
|
||||||
return self._userkeys
|
return self._userkeys
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -274,7 +401,7 @@ class KoboLibrary(object):
|
|||||||
|
|
||||||
def __bookfile (self, volumeid):
|
def __bookfile (self, volumeid):
|
||||||
"""The filename needed to open a given book."""
|
"""The filename needed to open a given book."""
|
||||||
return os.path.join(self.kobodir, 'kepub', volumeid)
|
return os.path.join(self.kobodir, u"kepub", volumeid)
|
||||||
|
|
||||||
def __getmacaddrs (self):
|
def __getmacaddrs (self):
|
||||||
"""The list of all MAC addresses on this machine."""
|
"""The list of all MAC addresses on this machine."""
|
||||||
@@ -290,17 +417,44 @@ class KoboLibrary(object):
|
|||||||
output = subprocess.check_output('/sbin/ifconfig -a', shell=True)
|
output = subprocess.check_output('/sbin/ifconfig -a', shell=True)
|
||||||
matches = c.findall(output)
|
matches = c.findall(output)
|
||||||
for m in matches:
|
for m in matches:
|
||||||
# print "m:",m[0]
|
# print u"m:{0}".format(m[0])
|
||||||
macaddrs.append(m[0].upper())
|
macaddrs.append(m[0].upper())
|
||||||
|
else:
|
||||||
|
# probably linux, let's try ipconfig under wine
|
||||||
|
c = re.compile('\s(' + '[0-9a-f]{2}-' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
||||||
|
for line in os.popen('ipconfig /all'):
|
||||||
|
m = c.search(line)
|
||||||
|
if m:
|
||||||
|
macaddrs.append(re.sub("-", ":", m.group(1)).upper())
|
||||||
|
|
||||||
|
# extend the list of macaddrs in any case with the serials
|
||||||
|
# cannot hurt ;-)
|
||||||
|
macaddrs.extend(self._serials)
|
||||||
|
|
||||||
return macaddrs
|
return macaddrs
|
||||||
|
|
||||||
def __getuserid (self):
|
def __getuserids (self):
|
||||||
return self.__cursor.execute('SELECT UserID FROM user WHERE HasMadePurchase = "true"').fetchone()[0]
|
userids = []
|
||||||
|
cursor = self.__cursor.execute('SELECT UserID FROM user')
|
||||||
def __getuserkey (self, macaddr, userid):
|
row = cursor.fetchone()
|
||||||
deviceid = hashlib.sha256('NoCanLook' + macaddr).hexdigest()
|
while row is not None:
|
||||||
userkey = hashlib.sha256(deviceid + userid).hexdigest()
|
try:
|
||||||
return binascii.a2b_hex(userkey[32:])
|
userid = row[0]
|
||||||
|
userids.append(userid)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
row = cursor.fetchone()
|
||||||
|
return userids
|
||||||
|
|
||||||
|
def __getuserkeys (self, macaddr):
|
||||||
|
userids = self.__getuserids()
|
||||||
|
userkeys = []
|
||||||
|
for hash in KOBO_HASH_KEYS:
|
||||||
|
deviceid = hashlib.sha256(hash + macaddr).hexdigest()
|
||||||
|
for userid in userids:
|
||||||
|
userkey = hashlib.sha256(deviceid + userid).hexdigest()
|
||||||
|
userkeys.append(binascii.a2b_hex(userkey[32:]))
|
||||||
|
return userkeys
|
||||||
|
|
||||||
class KoboBook(object):
|
class KoboBook(object):
|
||||||
"""A Kobo book.
|
"""A Kobo book.
|
||||||
@@ -407,13 +561,13 @@ class KoboFile(object):
|
|||||||
if contents[:5]=="<?xml":
|
if contents[:5]=="<?xml":
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
print "Bad XML: ",contents[:5]
|
print u"Bad XML: {0}".format(contents[:5])
|
||||||
raise ValueError
|
raise ValueError
|
||||||
if self.mimetype == 'image/jpeg':
|
if self.mimetype == 'image/jpeg':
|
||||||
if contents[:3] == '\xff\xd8\xff':
|
if contents[:3] == '\xff\xd8\xff':
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
print "Bad JPEG: ", contents[:3].encode('hex')
|
print u"Bad JPEG: {0}".format(contents[:3].encode('hex'))
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -435,36 +589,44 @@ class KoboFile(object):
|
|||||||
contents = contents[:-padding]
|
contents = contents[:-padding]
|
||||||
return contents
|
return contents
|
||||||
|
|
||||||
if __name__ == '__main__':
|
def cli_main():
|
||||||
|
description = __about__
|
||||||
|
epilog = u"Parsing of arguments failed."
|
||||||
|
parser = argparse.ArgumentParser(prog=sys.argv[0], description=description, epilog=epilog)
|
||||||
|
parser.add_argument('--devicedir', default='/media/KOBOeReader', help="directory of connected Kobo device")
|
||||||
|
args = vars(parser.parse_args())
|
||||||
|
serials = []
|
||||||
|
devicedir = u""
|
||||||
|
if args['devicedir']:
|
||||||
|
devicedir = args['devicedir']
|
||||||
|
|
||||||
lib = KoboLibrary()
|
lib = KoboLibrary(serials, devicedir)
|
||||||
|
|
||||||
for i, book in enumerate(lib.books):
|
for i, book in enumerate(lib.books):
|
||||||
print ('%d: %s' % (i + 1, book.title)).encode('ascii', 'ignore')
|
print u"{0}: {1}".format(i + 1, book.title)
|
||||||
|
|
||||||
num_string = raw_input("Convert book number... ")
|
num_string = raw_input(u"Convert book number... ")
|
||||||
try:
|
try:
|
||||||
num = int(num_string)
|
num = int(num_string)
|
||||||
book = lib.books[num - 1]
|
book = lib.books[num - 1]
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
print "Converting", book.title
|
print u"Converting {0}".format(book.title)
|
||||||
|
|
||||||
zin = zipfile.ZipFile(book.filename, "r")
|
zin = zipfile.ZipFile(book.filename, "r")
|
||||||
# make filename out of Unicode alphanumeric and whitespace equivalents from title
|
# make filename out of Unicode alphanumeric and whitespace equivalents from title
|
||||||
outname = "%s.epub" % (re.sub('[^\s\w]', '', book.title, 0, re.UNICODE))
|
outname = u"{0}.epub".format(re.sub('[^\s\w]', '_', book.title, 0, re.UNICODE))
|
||||||
|
|
||||||
if (book.type == 'drm-free'):
|
if (book.type == 'drm-free'):
|
||||||
print "DRM-free book, conversion is not needed"
|
print u"DRM-free book, conversion is not needed"
|
||||||
shutil.copyfile(book.filename, outname)
|
shutil.copyfile(book.filename, outname)
|
||||||
print "Book saved as", os.path.join(os.getcwd(), outname)
|
print u"Book saved as {0}".format(os.path.join(os.getcwd(), outname))
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
result = 1
|
result = 1
|
||||||
for userkey in lib.userkeys:
|
for userkey in lib.userkeys:
|
||||||
# print "Trying key: ",userkey.encode('hex_codec')
|
print u"Trying key: {0}".format(userkey.encode('hex_codec'))
|
||||||
confirmedGood = False
|
|
||||||
try:
|
try:
|
||||||
zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
|
zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
|
||||||
for filename in zin.namelist():
|
for filename in zin.namelist():
|
||||||
@@ -473,18 +635,25 @@ if __name__ == '__main__':
|
|||||||
file = book.encryptedfiles[filename]
|
file = book.encryptedfiles[filename]
|
||||||
contents = file.decrypt(userkey, contents)
|
contents = file.decrypt(userkey, contents)
|
||||||
# Parse failures mean the key is probably wrong.
|
# Parse failures mean the key is probably wrong.
|
||||||
if not confirmedGood:
|
file.check(contents)
|
||||||
confirmedGood = file.check(contents)
|
|
||||||
zout.writestr(filename, contents)
|
zout.writestr(filename, contents)
|
||||||
zout.close()
|
zout.close()
|
||||||
print "Book saved as", os.path.join(os.getcwd(), outname)
|
print u"Decryption succeeded."
|
||||||
|
print u"Book saved as {0}".format(os.path.join(os.getcwd(), outname))
|
||||||
result = 0
|
result = 0
|
||||||
break
|
break
|
||||||
except ValueError:
|
except ValueError:
|
||||||
print "Decryption failed, trying next key"
|
print u"Decryption failed."
|
||||||
zout.close()
|
zout.close()
|
||||||
os.remove(outname)
|
os.remove(outname)
|
||||||
|
|
||||||
zin.close()
|
zin.close()
|
||||||
lib.close()
|
lib.close()
|
||||||
exit(result)
|
if result != 0:
|
||||||
|
print u"Could not decrypt book with any of the keys found."
|
||||||
|
return result
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||||
|
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||||
|
sys.exit(cli_main())
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,10 +1,4 @@
|
|||||||
How-to:
|
The latest Scuolabook tool can be found at Hex's own blog:
|
||||||
1) Make sure you can read all PDF files on Scuolabook Reader.
|
https://thisishex.wordpress.com/scuolabook-drm-remover/
|
||||||
2) Run Scuolabook DRM Remover.
|
|
||||||
3) Get your free books from the directory where you started the program.
|
|
||||||
|
|
||||||
Note:
|
Harper.
|
||||||
It is recommended to use Scuolabook version 2.0.1 and refuse all updates
|
|
||||||
because the encryption algorithm may change making this tool useless.
|
|
||||||
|
|
||||||
Hex
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
Welcome to the tools!
|
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.3.1 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.3.4 archive from Apprentice Alf's Blog: http://apprenticealf.wordpress.com/
|
||||||
|
|
||||||
The is archive includes tools to remove DRM from:
|
The is archive includes tools to remove DRM from:
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ The is archive includes tools to remove DRM from:
|
|||||||
- Adobe Digital Editions PDFs
|
- Adobe Digital Editions PDFs
|
||||||
- Mobipocket ebooks
|
- Mobipocket ebooks
|
||||||
- eReader PDB books
|
- eReader PDB books
|
||||||
- Scuolabooks (Windows only solution by Hex)
|
- Scuolabooks (Link to solution by Hex)
|
||||||
|
|
||||||
These tools do NOT work with Apple's iBooks FairPlay DRM (see end of this file.)
|
These tools do NOT work with Apple's iBooks FairPlay DRM (see end of this file.)
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ Rocket_ebooks
|
|||||||
Information about the now-obsolete Rocket ebook format and DRM, along with source for a tool to remove the DRM.
|
Information about the now-obsolete Rocket ebook format and DRM, along with source for a tool to remove the DRM.
|
||||||
|
|
||||||
Scuolabook_DRM
|
Scuolabook_DRM
|
||||||
A windows-only application (including source code) for removing DRM from ScuolaBooks PDFs, created by "Hex" and included with permission.
|
A link to the tool for removing DRM from ScuolaBooks PDFs, created by "Hex".
|
||||||
|
|
||||||
|
|
||||||
Windows and Python
|
Windows and Python
|
||||||
|
|||||||
Reference in New Issue
Block a user