Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfc13db6c5 | ||
|
|
8aa2157d55 | ||
|
|
81b08dcf05 | ||
|
|
ca42e028a7 | ||
|
|
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 |
Binary file not shown.
@@ -24,7 +24,7 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>droplet</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>DeDRM AppleScript 6.3.3 Written 2010–2015 by Apprentice Alf et al.</string>
|
||||
<string>DeDRM AppleScript 6.3.6 Written 2010–2016 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.3</string>
|
||||
<string>6.3.6</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>dplt</string>
|
||||
<key>LSRequiresCarbon</key>
|
||||
|
||||
@@ -45,6 +45,9 @@ __docformat__ = 'restructuredtext en'
|
||||
# 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
|
||||
# 6.3.6 - Fixes for ADE ePub and PDF introduced in 6.3.5
|
||||
|
||||
|
||||
"""
|
||||
@@ -52,7 +55,7 @@ Decrypt DRMed ebooks.
|
||||
"""
|
||||
|
||||
PLUGIN_NAME = u"DeDRM"
|
||||
PLUGIN_VERSION_TUPLE = (6, 3, 3)
|
||||
PLUGIN_VERSION_TUPLE = (6, 3, 6)
|
||||
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'
|
||||
@@ -95,7 +98,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
|
||||
@@ -148,7 +151,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
|
||||
|
||||
@@ -295,11 +298,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)
|
||||
@@ -359,6 +366,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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ineptepub.pyw, version 6.1
|
||||
# ineptepub.pyw, version 6.5
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# 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
|
||||
# <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,16 @@ 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
|
||||
# 6.4 - Remove erroneous check on DER file sanity
|
||||
# 6.5 - Completely remove erroneous check on DER file sanity
|
||||
|
||||
"""
|
||||
Decrypt Adobe Digital Editions encrypted ePub books.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "6.2"
|
||||
__version__ = "6.5"
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ineptpdf.pyw, version 7.11
|
||||
# ineptpdf.pyw, version 8.0.4
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# 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
|
||||
# <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,17 @@ 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
|
||||
# 8.0.3 - Remove erroneous check on DER file sanity
|
||||
# 8.0.4 - Completely remove erroneous check on DER file sanity
|
||||
|
||||
|
||||
"""
|
||||
Decrypts Adobe ADEPT-encrypted PDF files.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "8.0.1"
|
||||
__version__ = "8.0.4"
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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
|
||||
@@ -18,8 +18,11 @@
|
||||
# 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
|
||||
# 6.3.6 - Version bump to match plugin
|
||||
|
||||
__version__ = '6.3.3'
|
||||
__version__ = '6.3.6'
|
||||
|
||||
import sys
|
||||
import os, os.path
|
||||
@@ -45,6 +45,9 @@ __docformat__ = 'restructuredtext en'
|
||||
# 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
|
||||
# 6.3.6 - Fixes for ADE ePub and PDF introduced in 6.3.5
|
||||
|
||||
|
||||
"""
|
||||
@@ -52,7 +55,7 @@ Decrypt DRMed ebooks.
|
||||
"""
|
||||
|
||||
PLUGIN_NAME = u"DeDRM"
|
||||
PLUGIN_VERSION_TUPLE = (6, 3, 3)
|
||||
PLUGIN_VERSION_TUPLE = (6, 3, 6)
|
||||
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'
|
||||
@@ -95,7 +98,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
|
||||
@@ -148,7 +151,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
|
||||
|
||||
@@ -295,11 +298,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)
|
||||
@@ -359,6 +366,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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ineptepub.pyw, version 6.1
|
||||
# ineptepub.pyw, version 6.5
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# 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
|
||||
# <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,16 @@ 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
|
||||
# 6.4 - Remove erroneous check on DER file sanity
|
||||
# 6.5 - Completely remove erroneous check on DER file sanity
|
||||
|
||||
"""
|
||||
Decrypt Adobe Digital Editions encrypted ePub books.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "6.2"
|
||||
__version__ = "6.5"
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ineptpdf.pyw, version 7.11
|
||||
# ineptpdf.pyw, version 8.0.4
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# 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
|
||||
# <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,17 @@ 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
|
||||
# 8.0.3 - Remove erroneous check on DER file sanity
|
||||
# 8.0.4 - Completely remove erroneous check on DER file sanity
|
||||
|
||||
|
||||
"""
|
||||
Decrypts Adobe ADEPT-encrypted PDF files.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "8.0.1"
|
||||
__version__ = "8.0.4"
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
@@ -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.
@@ -45,6 +45,9 @@ __docformat__ = 'restructuredtext en'
|
||||
# 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
|
||||
# 6.3.6 - Fixes for ADE ePub and PDF introduced in 6.3.5
|
||||
|
||||
|
||||
"""
|
||||
@@ -52,7 +55,7 @@ Decrypt DRMed ebooks.
|
||||
"""
|
||||
|
||||
PLUGIN_NAME = u"DeDRM"
|
||||
PLUGIN_VERSION_TUPLE = (6, 3, 3)
|
||||
PLUGIN_VERSION_TUPLE = (6, 3, 6)
|
||||
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'
|
||||
@@ -95,7 +98,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
|
||||
@@ -148,7 +151,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
|
||||
|
||||
@@ -295,11 +298,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)
|
||||
@@ -359,6 +366,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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ineptepub.pyw, version 6.1
|
||||
# ineptepub.pyw, version 6.5
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# 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
|
||||
# <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,16 @@ 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
|
||||
# 6.4 - Remove erroneous check on DER file sanity
|
||||
# 6.5 - Completely remove erroneous check on DER file sanity
|
||||
|
||||
"""
|
||||
Decrypt Adobe Digital Editions encrypted ePub books.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "6.2"
|
||||
__version__ = "6.5"
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ineptpdf.pyw, version 7.11
|
||||
# ineptpdf.pyw, version 8.0.4
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# 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
|
||||
# <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,17 @@ 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
|
||||
# 8.0.3 - Remove erroneous check on DER file sanity
|
||||
# 8.0.4 - Completely remove erroneous check on DER file sanity
|
||||
|
||||
|
||||
"""
|
||||
Decrypts Adobe ADEPT-encrypted PDF files.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "8.0.1"
|
||||
__version__ = "8.0.4"
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
@@ -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.
@@ -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, 6)
|
||||
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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 |
@@ -1,6 +1,30 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Version 3.2.0 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())
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,6 +1,33 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Version 3.2.0 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())
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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.
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user