Compare commits

...

29 Commits

Author SHA1 Message Date
apprenticeharper
10963f6011 Update to DeDRM to try to fix Linux python problem, and improve Adobe logging 2016-01-11 06:44:44 +00:00
apprenticeharper
72968d2124 Update of obok and change Scuolabook to a link 2016-01-11 06:43:22 +00:00
apprenticeharper
3e95168972 Merge fixes for new Kobo version and Linux support, update version number 2016-01-11 06:40:15 +00:00
apprenticeharper
ecf1d76d90 Update to Scuolabooks tool 2016-01-07 06:20:00 +00:00
Apprentice Harper
e59f0f346d Merge pull request #51 from norbusan/master
update obok.py with new hashes - support obok cmd line usage
2016-01-06 17:45:13 +00:00
Norbert Preining
b6046d3f4b move serial detection into obok.py, cater for cmd usage 2015-12-28 09:53:13 +09:00
Norbert Preining
a863a4856a update obok.py with new hashes and code from minmax 2015-12-26 09:13:16 +09:00
apprenticeharper
a13d08c3bc Fix for crash when Arc or Vox is connected. 2015-10-29 07:47:49 +00:00
apprenticeharper
9434751a72 updated version number and script copy for obok changes 2015-10-13 08:05:34 +01:00
Apprentice Harper
fc156852a4 Merge pull request #41 from norbusan/fixes
obok: make sure that file exists before opening the db
2015-10-13 07:36:54 +01:00
Norbert Preining
0c67fd43a2 obok: make sure that file exists before opening the db 2015-10-08 10:59:34 +09:00
Apprentice Harper
00a5c4e1d1 Updated obok plugin readme 2015-10-05 07:49:48 +01:00
apprenticeharper
4ea0d81144 Change to unicode strings to fix stand-alone character encoding problems 2015-09-30 07:39:29 +01:00
Apprentice Harper
b1cccf4b25 Merge pull request #39 from norbusan/obok_linux
improvements in the obok device handling
2015-09-21 07:40:54 +01:00
Norbert Preining
fe6074949b obok.py: first try device, and only if that fails fall back to Desktop progs 2015-09-18 09:10:06 +09:00
Norbert Preining
2db7ee8894 obok_plugin:action.py - get serial from device if possible 2015-09-18 08:58:01 +09:00
Norbert Preining
93d8758462 get device path from calibre, and allow device usage on all platforms 2015-09-16 23:01:29 +09:00
Norbert Preining
f97bc078db add support for linux via device serials and reading from device 2015-09-14 14:12:22 +09:00
apprenticeharper
2e96db6cdc More changes to the obok cli interface for character encodings 2015-09-08 07:52:06 +01:00
apprenticeharper
0d530c0c46 Add encryption fixes from other scripts (SafeUnbuffered). 2015-09-07 08:12:45 +01:00
apprenticeharper
488924d443 Fix for kobo users who haven't yet bought a book. 2015-09-07 07:52:23 +01:00
apprenticeharper
07485be2c0 Add transparency to the obok logo 2015-09-03 08:06:33 +01:00
apprenticeharper
e5e269fbae Fixes for android key extraction 2015-09-03 07:51:10 +01:00
apprenticeharper
d54dc38c2d Fix for Kobo Desktop 3.17 2015-09-03 07:49:09 +01:00
apprenticeharper
3c322f3695 Second and last step to fix capitalisation issue 2015-09-01 07:08:46 +01:00
apprenticeharper
c112c28f58 First step to fix capitalisation issue 2015-09-01 07:06:13 +01:00
apprenticeharper
b8606cd182 Merge pull request #32 from eli-schwartz/master
Linux: Allow using ~ when specifying a wineprefix.
2015-09-01 06:57:48 +01:00
Eli Schwartz
f2190a6755 Linux: Allow using ~ when specifying a wineprefix. 2015-08-12 17:39:50 -04:00
apprenticeharper
33d8a63f61 Fix for plugin bug introduced in 6.3.1 for Kindle for PC 2015-08-12 18:35:21 +01:00
32 changed files with 928 additions and 204 deletions

View File

@@ -24,7 +24,7 @@
<key>CFBundleExecutable</key>
<string>droplet</string>
<key>CFBundleGetInfoString</key>
<string>DeDRM AppleScript 6.3.2 Written 20102015 by Apprentice Alf et al.</string>
<string>DeDRM AppleScript 6.3.5 Written 20102016 by Apprentice Alf et al.</string>
<key>CFBundleIconFile</key>
<string>DeDRM</string>
<key>CFBundleIdentifier</key>
@@ -36,7 +36,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>6.3.2</string>
<string>6.3.5</string>
<key>CFBundleSignature</key>
<string>dplt</string>
<key>LSRequiresCarbon</key>

View File

@@ -44,6 +44,9 @@ __docformat__ = 'restructuredtext en'
# 6.3.0 - Added in Kindle for Android serial number solution
# 6.3.1 - Version number bump for clarity
# 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_VERSION_TUPLE = (6, 3, 2)
PLUGIN_VERSION_TUPLE = (6, 3, 5)
PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE])
# Include an html helpfile in the plugin's zipfile with the following name.
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
@@ -94,7 +97,7 @@ class DeDRM(FileTypePlugin):
supported_platforms = ['linux', 'osx', 'windows']
author = u"Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
version = PLUGIN_VERSION_TUPLE
minimum_calibre_version = (0, 7, 55) # Compiled python libraries cannot be imported in earlier versions.
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'])
on_import = True
priority = 600
@@ -147,7 +150,7 @@ class DeDRM(FileTypePlugin):
try:
open(file_path,'wb').write(data)
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()
pass
@@ -294,11 +297,15 @@ class DeDRM(FileTypePlugin):
traceback.print_exc()
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:
# Decryption was successful.
# 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
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:
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()
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 of.name
@@ -523,7 +531,7 @@ class DeDRM(FileTypePlugin):
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")
try:
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],self.starttime)
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],[],self.starttime)
decoded = True
# 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")

View File

@@ -16,16 +16,18 @@ from __future__ import with_statement
# - and added in unicode command line support
# 1.3 - added in TkInter interface, output to a file
# 1.4 - Fix some problems identified by Aldo Bleeker
# 1.5 - Fix another problem identified by Aldo Bleeker
"""
Retrieve Kindle for Android Serial Number.
"""
__license__ = 'GPL v3'
__version__ = '1.4'
__version__ = '1.5'
import os
import sys
import traceback
import getopt
import tempfile
import zlib
@@ -220,20 +222,30 @@ def get_serials2(path=STORAGE2):
userdata_keys = cursor.fetchall()
dsns = []
for userdata_row in userdata_keys:
if userdata_row:
userdata_utf8 = userdata_row[0].encode('utf8')
if len(userdata_utf8) > 0:
dsns.append(userdata_utf8)
try:
if userdata_row and userdata_row[0]:
userdata_utf8 = userdata_row[0].encode('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))
cursor.execute('''select userdata_value from userdata where userdata_key like '%/%kindle.account.tokens%' ''')
userdata_keys = cursor.fetchall()
tokens = []
for userdata_row in userdata_keys:
if userdata_row:
userdata_utf8 = userdata_row[0].encode('utf8')
if len(userdata_utf8) > 0:
tokens.append(userdata_utf8)
try:
if userdata_row and userdata_row[0]:
userdata_utf8 = userdata_row[0].encode('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))
serials = []
@@ -377,7 +389,6 @@ def gui_main():
import Tkconstants
import tkMessageBox
import tkFileDialog
import traceback
except:
print "Tkinter not installed"
return cli_main()

View File

@@ -3,18 +3,19 @@
from __future__ import with_statement
# ineptepub.pyw, version 6.1
# ineptepub.pyw, version 6.3
# Copyright © 2009-2010 by i♥cabbages
# Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/>
# Modified 20102013 by some_updates, DiapDealer and Apprentice Alf
# Modified 20152016 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
# <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.
#
# 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.1 - Work if TkInter is missing
# 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.
"""
__license__ = 'GPL v3'
__version__ = "6.2"
__version__ = "6.3"
import sys
import os
@@ -169,9 +171,14 @@ def _load_crypto_libcrypto():
def __init__(self, der):
buf = create_string_buffer(der)
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:
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_):
rsa = self._rsa
@@ -313,6 +320,12 @@ def _load_crypto_pycrypto():
key = [key.getChild(x).value for x in xrange(1, 4)]
key = [self.bytesToNumber(v) for v in 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):
total = 0L

View File

@@ -3,18 +3,19 @@
from __future__ import with_statement
# ineptpdf.pyw, version 7.11
# ineptpdf.pyw, version 8.0.2
# Copyright © 2009-2010 by i♥cabbages
# Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/>
# Modified 20102012 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
# <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.
#
# 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
# 8.0 - Work if TkInter is missing
# 8.0.1 - Broken Metadata fix.
# 8.0.2 - Add additional check on DER file sanity
"""
Decrypts Adobe ADEPT-encrypted PDF files.
"""
__license__ = 'GPL v3'
__version__ = "8.0.1"
__version__ = "8.0.2"
import sys
import os
@@ -198,6 +201,11 @@ def _load_crypto_libcrypto():
rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
if rsa is None:
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_):
rsa = self._rsa
@@ -383,6 +391,11 @@ def _load_crypto_pycrypto():
key = [key.getChild(x).value for x in xrange(1, 4)]
key = [self.bytesToNumber(v) for v in 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):
total = 0L

View File

@@ -26,6 +26,7 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
if not os.path.exists(outdirpath):
os.makedirs(outdirpath)
wineprefix = os.path.abspath(os.path.expanduser(os.path.expandvars(wineprefix)))
if wineprefix != "" and os.path.exists(wineprefix):
cmdline = u"WINEPREFIX=\"{2}\" wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
else:
@@ -38,8 +39,20 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
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])
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 = []
# get any files with extension in the output dir
files = [f for f in os.listdir(outdirpath) if f.endswith(extension)]

View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# DeDRM.pyw
# Copyright 2010-2015 some_updates, Apprentice Alf and Apprentice Harper
# Copyright 2010-2016 some_updates, Apprentice Alf and Apprentice Harper
# Revision history:
# 6.0.0 - Release along with unified plugin
@@ -17,8 +17,11 @@
# 6.3.0 - Add in Android support
# 6.3.1 - Version bump for clarity
# 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 os, os.path

View File

@@ -44,6 +44,9 @@ __docformat__ = 'restructuredtext en'
# 6.3.0 - Added in Kindle for Android serial number solution
# 6.3.1 - Version number bump for clarity
# 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_VERSION_TUPLE = (6, 3, 2)
PLUGIN_VERSION_TUPLE = (6, 3, 5)
PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE])
# Include an html helpfile in the plugin's zipfile with the following name.
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
@@ -94,7 +97,7 @@ class DeDRM(FileTypePlugin):
supported_platforms = ['linux', 'osx', 'windows']
author = u"Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
version = PLUGIN_VERSION_TUPLE
minimum_calibre_version = (0, 7, 55) # Compiled python libraries cannot be imported in earlier versions.
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'])
on_import = True
priority = 600
@@ -147,7 +150,7 @@ class DeDRM(FileTypePlugin):
try:
open(file_path,'wb').write(data)
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()
pass
@@ -294,11 +297,15 @@ class DeDRM(FileTypePlugin):
traceback.print_exc()
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:
# Decryption was successful.
# 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
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:
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()
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 of.name
@@ -523,7 +531,7 @@ class DeDRM(FileTypePlugin):
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")
try:
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],self.starttime)
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],[],self.starttime)
decoded = True
# 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")

View File

@@ -16,16 +16,18 @@ from __future__ import with_statement
# - and added in unicode command line support
# 1.3 - added in TkInter interface, output to a file
# 1.4 - Fix some problems identified by Aldo Bleeker
# 1.5 - Fix another problem identified by Aldo Bleeker
"""
Retrieve Kindle for Android Serial Number.
"""
__license__ = 'GPL v3'
__version__ = '1.4'
__version__ = '1.5'
import os
import sys
import traceback
import getopt
import tempfile
import zlib
@@ -220,20 +222,30 @@ def get_serials2(path=STORAGE2):
userdata_keys = cursor.fetchall()
dsns = []
for userdata_row in userdata_keys:
if userdata_row:
userdata_utf8 = userdata_row[0].encode('utf8')
if len(userdata_utf8) > 0:
dsns.append(userdata_utf8)
try:
if userdata_row and userdata_row[0]:
userdata_utf8 = userdata_row[0].encode('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))
cursor.execute('''select userdata_value from userdata where userdata_key like '%/%kindle.account.tokens%' ''')
userdata_keys = cursor.fetchall()
tokens = []
for userdata_row in userdata_keys:
if userdata_row:
userdata_utf8 = userdata_row[0].encode('utf8')
if len(userdata_utf8) > 0:
tokens.append(userdata_utf8)
try:
if userdata_row and userdata_row[0]:
userdata_utf8 = userdata_row[0].encode('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))
serials = []
@@ -377,7 +389,6 @@ def gui_main():
import Tkconstants
import tkMessageBox
import tkFileDialog
import traceback
except:
print "Tkinter not installed"
return cli_main()

View File

@@ -3,18 +3,19 @@
from __future__ import with_statement
# ineptepub.pyw, version 6.1
# ineptepub.pyw, version 6.3
# Copyright © 2009-2010 by i♥cabbages
# Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/>
# Modified 20102013 by some_updates, DiapDealer and Apprentice Alf
# Modified 20152016 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
# <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.
#
# 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.1 - Work if TkInter is missing
# 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.
"""
__license__ = 'GPL v3'
__version__ = "6.2"
__version__ = "6.3"
import sys
import os
@@ -169,9 +171,14 @@ def _load_crypto_libcrypto():
def __init__(self, der):
buf = create_string_buffer(der)
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:
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_):
rsa = self._rsa
@@ -313,6 +320,12 @@ def _load_crypto_pycrypto():
key = [key.getChild(x).value for x in xrange(1, 4)]
key = [self.bytesToNumber(v) for v in 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):
total = 0L

View File

@@ -3,18 +3,19 @@
from __future__ import with_statement
# ineptpdf.pyw, version 7.11
# ineptpdf.pyw, version 8.0.2
# Copyright © 2009-2010 by i♥cabbages
# Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/>
# Modified 20102012 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
# <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.
#
# 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
# 8.0 - Work if TkInter is missing
# 8.0.1 - Broken Metadata fix.
# 8.0.2 - Add additional check on DER file sanity
"""
Decrypts Adobe ADEPT-encrypted PDF files.
"""
__license__ = 'GPL v3'
__version__ = "8.0.1"
__version__ = "8.0.2"
import sys
import os
@@ -198,6 +201,11 @@ def _load_crypto_libcrypto():
rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
if rsa is None:
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_):
rsa = self._rsa
@@ -383,6 +391,11 @@ def _load_crypto_pycrypto():
key = [key.getChild(x).value for x in xrange(1, 4)]
key = [self.bytesToNumber(v) for v in 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):
total = 0L

View File

@@ -26,6 +26,7 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
if not os.path.exists(outdirpath):
os.makedirs(outdirpath)
wineprefix = os.path.abspath(os.path.expanduser(os.path.expandvars(wineprefix)))
if wineprefix != "" and os.path.exists(wineprefix):
cmdline = u"WINEPREFIX=\"{2}\" wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
else:
@@ -38,8 +39,20 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
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])
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 = []
# get any files with extension in the output dir
files = [f for f in os.listdir(outdirpath) if f.endswith(extension)]

View File

@@ -44,6 +44,9 @@ __docformat__ = 'restructuredtext en'
# 6.3.0 - Added in Kindle for Android serial number solution
# 6.3.1 - Version number bump for clarity
# 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_VERSION_TUPLE = (6, 3, 2)
PLUGIN_VERSION_TUPLE = (6, 3, 5)
PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE])
# Include an html helpfile in the plugin's zipfile with the following name.
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
@@ -94,7 +97,7 @@ class DeDRM(FileTypePlugin):
supported_platforms = ['linux', 'osx', 'windows']
author = u"Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
version = PLUGIN_VERSION_TUPLE
minimum_calibre_version = (0, 7, 55) # Compiled python libraries cannot be imported in earlier versions.
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'])
on_import = True
priority = 600
@@ -147,7 +150,7 @@ class DeDRM(FileTypePlugin):
try:
open(file_path,'wb').write(data)
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()
pass
@@ -294,11 +297,15 @@ class DeDRM(FileTypePlugin):
traceback.print_exc()
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:
# Decryption was successful.
# 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
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:
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()
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 of.name
@@ -523,7 +531,7 @@ class DeDRM(FileTypePlugin):
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")
try:
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],self.starttime)
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],[],self.starttime)
decoded = True
# 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")

View File

@@ -16,16 +16,18 @@ from __future__ import with_statement
# - and added in unicode command line support
# 1.3 - added in TkInter interface, output to a file
# 1.4 - Fix some problems identified by Aldo Bleeker
# 1.5 - Fix another problem identified by Aldo Bleeker
"""
Retrieve Kindle for Android Serial Number.
"""
__license__ = 'GPL v3'
__version__ = '1.4'
__version__ = '1.5'
import os
import sys
import traceback
import getopt
import tempfile
import zlib
@@ -220,20 +222,30 @@ def get_serials2(path=STORAGE2):
userdata_keys = cursor.fetchall()
dsns = []
for userdata_row in userdata_keys:
if userdata_row:
userdata_utf8 = userdata_row[0].encode('utf8')
if len(userdata_utf8) > 0:
dsns.append(userdata_utf8)
try:
if userdata_row and userdata_row[0]:
userdata_utf8 = userdata_row[0].encode('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))
cursor.execute('''select userdata_value from userdata where userdata_key like '%/%kindle.account.tokens%' ''')
userdata_keys = cursor.fetchall()
tokens = []
for userdata_row in userdata_keys:
if userdata_row:
userdata_utf8 = userdata_row[0].encode('utf8')
if len(userdata_utf8) > 0:
tokens.append(userdata_utf8)
try:
if userdata_row and userdata_row[0]:
userdata_utf8 = userdata_row[0].encode('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))
serials = []
@@ -377,7 +389,6 @@ def gui_main():
import Tkconstants
import tkMessageBox
import tkFileDialog
import traceback
except:
print "Tkinter not installed"
return cli_main()

View File

@@ -3,18 +3,19 @@
from __future__ import with_statement
# ineptepub.pyw, version 6.1
# ineptepub.pyw, version 6.3
# Copyright © 2009-2010 by i♥cabbages
# Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/>
# Modified 20102013 by some_updates, DiapDealer and Apprentice Alf
# Modified 20152016 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
# <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.
#
# 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.1 - Work if TkInter is missing
# 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.
"""
__license__ = 'GPL v3'
__version__ = "6.2"
__version__ = "6.3"
import sys
import os
@@ -169,9 +171,14 @@ def _load_crypto_libcrypto():
def __init__(self, der):
buf = create_string_buffer(der)
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:
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_):
rsa = self._rsa
@@ -313,6 +320,12 @@ def _load_crypto_pycrypto():
key = [key.getChild(x).value for x in xrange(1, 4)]
key = [self.bytesToNumber(v) for v in 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):
total = 0L

View File

@@ -3,18 +3,19 @@
from __future__ import with_statement
# ineptpdf.pyw, version 7.11
# ineptpdf.pyw, version 8.0.2
# Copyright © 2009-2010 by i♥cabbages
# Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/>
# Modified 20102012 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
# <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.
#
# 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
# 8.0 - Work if TkInter is missing
# 8.0.1 - Broken Metadata fix.
# 8.0.2 - Add additional check on DER file sanity
"""
Decrypts Adobe ADEPT-encrypted PDF files.
"""
__license__ = 'GPL v3'
__version__ = "8.0.1"
__version__ = "8.0.2"
import sys
import os
@@ -198,6 +201,11 @@ def _load_crypto_libcrypto():
rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
if rsa is None:
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_):
rsa = self._rsa
@@ -383,6 +391,11 @@ def _load_crypto_pycrypto():
key = [key.getChild(x).value for x in xrange(1, 4)]
key = [self.bytesToNumber(v) for v in 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):
total = 0L

View File

@@ -26,6 +26,7 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
if not os.path.exists(outdirpath):
os.makedirs(outdirpath)
wineprefix = os.path.abspath(os.path.expanduser(os.path.expandvars(wineprefix)))
if wineprefix != "" and os.path.exists(wineprefix):
cmdline = u"WINEPREFIX=\"{2}\" wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
else:
@@ -38,8 +39,20 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
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])
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 = []
# get any files with extension in the output dir
files = [f for f in os.listdir(outdirpath) if f.endswith(extension)]

Binary file not shown.

View File

@@ -19,7 +19,7 @@ except NameError:
PLUGIN_NAME = 'Obok DeDRM'
PLUGIN_SAFE_NAME = PLUGIN_NAME.strip().lower().replace(' ', '_')
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])
HELPFILE_NAME = PLUGIN_SAFE_NAME + '_Help.htm'
PLUGIN_AUTHORS = 'Anon'
@@ -29,7 +29,7 @@ class ObokDeDRMAction(InterfaceActionBase):
name = PLUGIN_NAME
description = PLUGIN_DESCRIPTION
supported_platforms = ['windows', 'osx']
supported_platforms = ['windows', 'osx', 'linux' ]
author = PLUGIN_AUTHORS
version = PLUGIN_VERSION_TUPLE
minimum_calibre_version = (1, 0, 0)

View File

@@ -6,7 +6,7 @@ __license__ = 'GPL v3'
__docformat__ = 'restructuredtext en'
import os, zipfile
import os, traceback, zipfile
try:
from PyQt5.Qt import QToolButton, QUrl
@@ -78,8 +78,30 @@ class InterfacePluginAction(InterfaceAction):
self.current_idx = self.gui.library_view.currentIndex()
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)
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
books = self.build_book_list()
@@ -98,6 +120,7 @@ class InterfacePluginAction(InterfaceAction):
candidate_keys = self.library.userkeys
except:
print (_('Trouble retrieving keys with newer obok method.'))
traceback.print_exc()
else:
if len(candidate_keys):
self.userkeys.extend(candidate_keys)

View File

@@ -3,15 +3,23 @@ from __future__ import (unicode_literals, division, absolute_import,
print_function)
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:
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
plugin_prefs = JSONConfig('plugins/obok_dedrm_prefs')
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)
try:
debug_print("obok::config.py - loading translations")
@@ -26,7 +34,10 @@ class ConfigWidget(QWidget):
self.plugin_action = plugin_action
layout = QVBoxLayout(self)
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)
layout.addWidget(combo_label)
self.find_homes = QComboBox()
@@ -35,6 +46,178 @@ class ConfigWidget(QWidget):
self.find_homes.addItems([_('Ask'), _('Always'), _('Never')])
index = self.find_homes.findText(plugin_prefs['finding_homes_for_formats'])
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):
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

View File

@@ -1,6 +1,30 @@
#!/usr/bin/env python
# -*- 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
#
@@ -112,7 +136,8 @@
#
"""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 os
@@ -126,6 +151,18 @@ import hashlib
import xml.etree.ElementTree as ET
import string
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):
pass
@@ -212,6 +249,24 @@ def _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):
"""The Kobo library.
@@ -219,23 +274,93 @@ class KoboLibrary(object):
written by the Kobo Desktop Edition application, including the list
of books, their titles, and the user's encryption key(s)."""
def __init__ (self):
print u"Obok v{0}\nCopyright © 2012-2014 Physisticated et al.".format(__version__)
if sys.platform.startswith('win'):
if sys.getwindowsversion().major > 5:
self.kobodir = os.environ['LOCALAPPDATA']
else:
self.kobodir = os.path.join(os.environ['USERPROFILE'], 'Local Settings', 'Application Data')
self.kobodir = os.path.join(self.kobodir, 'Kobo', 'Kobo Desktop Edition')
elif sys.platform.startswith('darwin'):
self.kobodir = os.path.join(os.environ['HOME'], 'Library', 'Application Support', 'Kobo', 'Kobo Desktop Edition')
self.bookdir = os.path.join(self.kobodir, 'kepub')
kobodb = os.path.join(self.kobodir, 'Kobo.sqlite')
self.__sqlite = sqlite3.connect(kobodb)
self.__cursor = self.__sqlite.cursor()
self._userkeys = []
self._books = []
self._volumeID = []
def __init__ (self, serials = [], device_path = None):
print __about__
self.kobodir = u""
kobodb = u""
# Order of checks
# 1. first check if a device_path has been passed in, and whether
# we can find the sqlite db in the respective place
# 2. if 1., and we got some serials passed in (from saved
# settings in calibre), just use it
# 3. if 1. worked, but we didn't get serials, try to parse them
# from the device, if this didn't work, unset everything
# 4. if by now we don't have kobodir set, give up on device and
# try to use the Desktop app.
# step 1. check whether this looks like a real device
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):
"""Closes the database used by the library."""
@@ -249,9 +374,8 @@ class KoboLibrary(object):
"""
if len(self._userkeys) != 0:
return self._userkeys
userid = self.__getuserid()
for macaddr in self.__getmacaddrs():
self._userkeys.append(self.__getuserkey(macaddr, userid))
self._userkeys.extend(self.__getuserkeys(macaddr))
return self._userkeys
@property
@@ -277,7 +401,7 @@ class KoboLibrary(object):
def __bookfile (self, volumeid):
"""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):
"""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)
matches = c.findall(output)
for m in matches:
# print "m:",m[0]
# print u"m:{0}".format(m[0])
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
def __getuserid (self):
return self.__cursor.execute('SELECT UserID FROM user WHERE HasMadePurchase = "true"').fetchone()[0]
def __getuserkey (self, macaddr, userid):
deviceid = hashlib.sha256('NoCanLook' + macaddr).hexdigest()
userkey = hashlib.sha256(deviceid + userid).hexdigest()
return binascii.a2b_hex(userkey[32:])
def __getuserids (self):
userids = []
cursor = self.__cursor.execute('SELECT UserID FROM user')
row = cursor.fetchone()
while row is not None:
try:
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):
"""A Kobo book.
@@ -410,13 +561,13 @@ class KoboFile(object):
if contents[:5]=="<?xml":
return True
else:
print "Bad XML: ",contents[:5]
print u"Bad XML: {0}".format(contents[:5])
raise ValueError
if self.mimetype == 'image/jpeg':
if contents[:3] == '\xff\xd8\xff':
return True
else:
print "Bad JPEG: ", contents[:3].encode('hex')
print u"Bad JPEG: {0}".format(contents[:3].encode('hex'))
raise ValueError()
return False
@@ -438,36 +589,44 @@ class KoboFile(object):
contents = contents[:-padding]
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):
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:
num = int(num_string)
book = lib.books[num - 1]
except (ValueError, IndexError):
exit()
print "Converting", book.title
print u"Converting {0}".format(book.title)
zin = zipfile.ZipFile(book.filename, "r")
# 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'):
print "DRM-free book, conversion is not needed"
print u"DRM-free book, conversion is not needed"
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)
result = 1
for userkey in lib.userkeys:
# print "Trying key: ",userkey.encode('hex_codec')
confirmedGood = False
print u"Trying key: {0}".format(userkey.encode('hex_codec'))
try:
zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
for filename in zin.namelist():
@@ -476,18 +635,25 @@ if __name__ == '__main__':
file = book.encryptedfiles[filename]
contents = file.decrypt(userkey, contents)
# Parse failures mean the key is probably wrong.
if not confirmedGood:
confirmedGood = file.check(contents)
file.check(contents)
zout.writestr(filename, contents)
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
break
except ValueError:
print "Decryption failed, trying next key"
print u"Decryption failed."
zout.close()
os.remove(outname)
zin.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())

View File

@@ -1,7 +1,7 @@
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
@@ -11,7 +11,7 @@ Do NOT select "Get plugins to enhance calibre" as this is reserved for 'official
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
@@ -47,6 +47,7 @@ Credits
-------
The original obok script was by Physisticated
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.

View File

@@ -16,16 +16,18 @@ from __future__ import with_statement
# - and added in unicode command line support
# 1.3 - added in TkInter interface, output to a file
# 1.4 - Fix some problems identified by Aldo Bleeker
# 1.5 - Fix another problem identified by Aldo Bleeker
"""
Retrieve Kindle for Android Serial Number.
"""
__license__ = 'GPL v3'
__version__ = '1.4'
__version__ = '1.5'
import os
import sys
import traceback
import getopt
import tempfile
import zlib
@@ -220,20 +222,30 @@ def get_serials2(path=STORAGE2):
userdata_keys = cursor.fetchall()
dsns = []
for userdata_row in userdata_keys:
if userdata_row:
userdata_utf8 = userdata_row[0].encode('utf8')
if len(userdata_utf8) > 0:
dsns.append(userdata_utf8)
try:
if userdata_row and userdata_row[0]:
userdata_utf8 = userdata_row[0].encode('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))
cursor.execute('''select userdata_value from userdata where userdata_key like '%/%kindle.account.tokens%' ''')
userdata_keys = cursor.fetchall()
tokens = []
for userdata_row in userdata_keys:
if userdata_row:
userdata_utf8 = userdata_row[0].encode('utf8')
if len(userdata_utf8) > 0:
tokens.append(userdata_utf8)
try:
if userdata_row and userdata_row[0]:
userdata_utf8 = userdata_row[0].encode('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))
serials = []
@@ -377,7 +389,6 @@ def gui_main():
import Tkconstants
import tkMessageBox
import tkFileDialog
import traceback
except:
print "Tkinter not installed"
return cli_main()

View File

@@ -1,6 +1,33 @@
#!/usr/bin/env python
# -*- 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
# Add coding, version number and version announcement
#
@@ -109,7 +136,8 @@
#
"""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 os
@@ -123,6 +151,18 @@ import hashlib
import xml.etree.ElementTree as ET
import string
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):
pass
@@ -209,6 +249,24 @@ def _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):
"""The Kobo library.
@@ -216,23 +274,93 @@ class KoboLibrary(object):
written by the Kobo Desktop Edition application, including the list
of books, their titles, and the user's encryption key(s)."""
def __init__ (self):
print u"Obok v{0}\nCopyright © 2012-2014 Physisticated et al.".format(__version__)
if sys.platform.startswith('win'):
if sys.getwindowsversion().major > 5:
self.kobodir = os.environ['LOCALAPPDATA']
else:
self.kobodir = os.path.join(os.environ['USERPROFILE'], 'Local Settings', 'Application Data')
self.kobodir = os.path.join(self.kobodir, 'Kobo', 'Kobo Desktop Edition')
elif sys.platform.startswith('darwin'):
self.kobodir = os.path.join(os.environ['HOME'], 'Library', 'Application Support', 'Kobo', 'Kobo Desktop Edition')
self.bookdir = os.path.join(self.kobodir, 'kepub')
kobodb = os.path.join(self.kobodir, 'Kobo.sqlite')
self.__sqlite = sqlite3.connect(kobodb)
self.__cursor = self.__sqlite.cursor()
self._userkeys = []
self._books = []
self._volumeID = []
def __init__ (self, serials = [], device_path = None):
print __about__
self.kobodir = u""
kobodb = u""
# Order of checks
# 1. first check if a device_path has been passed in, and whether
# we can find the sqlite db in the respective place
# 2. if 1., and we got some serials passed in (from saved
# settings in calibre), just use it
# 3. if 1. worked, but we didn't get serials, try to parse them
# from the device, if this didn't work, unset everything
# 4. if by now we don't have kobodir set, give up on device and
# try to use the Desktop app.
# step 1. check whether this looks like a real device
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):
"""Closes the database used by the library."""
@@ -246,9 +374,8 @@ class KoboLibrary(object):
"""
if len(self._userkeys) != 0:
return self._userkeys
userid = self.__getuserid()
for macaddr in self.__getmacaddrs():
self._userkeys.append(self.__getuserkey(macaddr, userid))
self._userkeys.extend(self.__getuserkeys(macaddr))
return self._userkeys
@property
@@ -274,7 +401,7 @@ class KoboLibrary(object):
def __bookfile (self, volumeid):
"""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):
"""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)
matches = c.findall(output)
for m in matches:
# print "m:",m[0]
# print u"m:{0}".format(m[0])
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
def __getuserid (self):
return self.__cursor.execute('SELECT UserID FROM user WHERE HasMadePurchase = "true"').fetchone()[0]
def __getuserkey (self, macaddr, userid):
deviceid = hashlib.sha256('NoCanLook' + macaddr).hexdigest()
userkey = hashlib.sha256(deviceid + userid).hexdigest()
return binascii.a2b_hex(userkey[32:])
def __getuserids (self):
userids = []
cursor = self.__cursor.execute('SELECT UserID FROM user')
row = cursor.fetchone()
while row is not None:
try:
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):
"""A Kobo book.
@@ -407,13 +561,13 @@ class KoboFile(object):
if contents[:5]=="<?xml":
return True
else:
print "Bad XML: ",contents[:5]
print u"Bad XML: {0}".format(contents[:5])
raise ValueError
if self.mimetype == 'image/jpeg':
if contents[:3] == '\xff\xd8\xff':
return True
else:
print "Bad JPEG: ", contents[:3].encode('hex')
print u"Bad JPEG: {0}".format(contents[:3].encode('hex'))
raise ValueError()
return False
@@ -435,36 +589,44 @@ class KoboFile(object):
contents = contents[:-padding]
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):
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:
num = int(num_string)
book = lib.books[num - 1]
except (ValueError, IndexError):
exit()
print "Converting", book.title
print u"Converting {0}".format(book.title)
zin = zipfile.ZipFile(book.filename, "r")
# 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'):
print "DRM-free book, conversion is not needed"
print u"DRM-free book, conversion is not needed"
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)
result = 1
for userkey in lib.userkeys:
# print "Trying key: ",userkey.encode('hex_codec')
confirmedGood = False
print u"Trying key: {0}".format(userkey.encode('hex_codec'))
try:
zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
for filename in zin.namelist():
@@ -473,18 +635,25 @@ if __name__ == '__main__':
file = book.encryptedfiles[filename]
contents = file.decrypt(userkey, contents)
# Parse failures mean the key is probably wrong.
if not confirmedGood:
confirmedGood = file.check(contents)
file.check(contents)
zout.writestr(filename, contents)
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
break
except ValueError:
print "Decryption failed, trying next key"
print u"Decryption failed."
zout.close()
os.remove(outname)
zin.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())

View File

@@ -1,10 +1,4 @@
How-to:
1) Make sure you can read all PDF files on Scuolabook Reader.
2) Run Scuolabook DRM Remover.
3) Get your free books from the directory where you started the program.
The latest Scuolabook tool can be found at Hex's own blog:
https://thisishex.wordpress.com/scuolabook-drm-remover/
Note:
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
Harper.

View File

@@ -1,7 +1,7 @@
Welcome to the tools!
=====================
This ReadMe_First.txt is meant to give users a quick overview of what is available and how to get started. This document is part of the Tools v6.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:
@@ -12,7 +12,7 @@ The is archive includes tools to remove DRM from:
- Adobe Digital Editions PDFs
- Mobipocket ebooks
- 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.)
@@ -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.
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