mirror of
https://github.com/apprenticeharper/DeDRM_tools
synced 2024-12-27 09:58:59 +01:00
a7974f0f14
integer division, and version
657 lines
34 KiB
Python
657 lines
34 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# __init__.py for DeDRM_plugin
|
|
# Copyright © 2008-2020 Apprentice Harper et al.
|
|
|
|
__license__ = 'GPL v3'
|
|
__version__ = '7.0.3'
|
|
__docformat__ = 'restructuredtext en'
|
|
|
|
|
|
# Released under the terms of the GNU General Public Licence, version 3
|
|
# <http://www.gnu.org/licenses/>
|
|
#
|
|
# All credit given to i♥cabbages and The Dark Reverser for the original standalone scripts.
|
|
# We had the much easier job of converting them to a calibre plugin.
|
|
#
|
|
# This plugin is meant to decrypt eReader PDBs, Adobe Adept ePubs, Barnes & Noble ePubs,
|
|
# Adobe Adept PDFs, Amazon Kindle and Mobipocket files without having
|
|
# to install any dependencies... other than having calibre installed, of course.
|
|
#
|
|
# Configuration:
|
|
# Check out the plugin's configuration settings by clicking the "Customize plugin"
|
|
# button when you have the "DeDRM" plugin highlighted (under Preferences->
|
|
# Plugins->File type plugins). Once you have the configuration dialog open, you'll
|
|
# see a Help link on the top right-hand side.
|
|
#
|
|
# Revision history:
|
|
# 6.0.0 - Initial release
|
|
# 6.0.1 - Bug Fixes for Windows App, Kindle for Mac and Windows Adobe Digital Editions
|
|
# 6.0.2 - Restored call to Wine to get Kindle for PC keys, added for ADE
|
|
# 6.0.3 - Fixes for Kindle for Mac and Windows non-ascii user names
|
|
# 6.0.4 - Fixes for stand-alone scripts and applications
|
|
# and pdb files in plugin and initial conversion of prefs.
|
|
# 6.0.5 - Fix a key issue
|
|
# 6.0.6 - Fix up an incorrect function call
|
|
# 6.0.7 - Error handling for incomplete PDF metadata
|
|
# 6.0.8 - Fixes a Wine key issue and topaz support
|
|
# 6.0.9 - Ported to work with newer versions of Calibre (moved to Qt5). Still supports older Qt4 versions.
|
|
# 6.1.0 - Fixed multiple books import problem and PDF import with no key problem
|
|
# 6.2.0 - Support for getting B&N key from nook Study log. Fix for UTF-8 filenames in Adobe ePubs.
|
|
# Fix for not copying needed files. Fix for getting default Adobe key for PDFs
|
|
# 6.2.1 - Fix for non-ascii Windows user names
|
|
# 6.2.2 - Added URL method for B&N/nook books
|
|
# 6.3.0 - Added in Kindle for Android serial number solution
|
|
# 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
|
|
# 6.4.0 - Updated for new Kindle for PC encryption
|
|
# 6.4.1 - Fix for some new tags in Topaz ebooks.
|
|
# 6.4.2 - Fix for more new tags in Topaz ebooks and very small Topaz ebooks
|
|
# 6.4.3 - Fix for error that only appears when not in debug mode
|
|
# Also includes fix for Macs with bonded ethernet ports
|
|
# 6.5.0 - Big update to Macintosh app
|
|
# Fix for some more 'new' tags in Topaz ebooks.
|
|
# Fix an error in wineutils.py
|
|
# 6.5.1 - Updated version number, added PDF check for DRM-free documents
|
|
# 6.5.2 - Another Topaz fix
|
|
# 6.5.3 - Warn about KFX files explicitly
|
|
# 6.5.4 - Mac App Fix, improve PDF decryption, handle latest tcl changes in ActivePython
|
|
# 6.5.5 - Finally a fix for the Windows non-ASCII user names.
|
|
# 6.6.0 - Add kfx and kfx-zip as supported file types (also invoke this plugin if the original
|
|
# imported format was azw8 since that may be converted to kfx)
|
|
# 6.6.1 - Thanks to wzyboy for a fix for stand-alone tools, and the new folder structure.
|
|
# 6.6.2 - revamp of folders to get Mac OS X app working. Updated to 64-bit app. Various fixes.
|
|
# 6.6.3 - More cleanup of kindle book names and start of support for .kinf2018
|
|
# 6.7.0 - Handle new library in calibre.
|
|
# 6.8.0 - Full support for .kinf2018 and new KFX encryption (Kindle for PC/Mac 2.5+)
|
|
# 6.8.1 - Kindle key fix for Mac OS X Big Sur
|
|
# 7.0.0 - Switched to Python 3 for calibre 5.0. Thanks to all who contributed
|
|
# 7.0.1 - More Python 3 changes. Adobe PDF decryption should now work in some cases
|
|
# 7.0.2 - More Python 3 changes. Adobe PDF decryption should now work on PC too.
|
|
# 7.0.3 - More Python 3 changes. Integer division in ineptpdf.py
|
|
|
|
"""
|
|
Decrypt DRMed ebooks.
|
|
"""
|
|
|
|
PLUGIN_NAME = "DeDRM"
|
|
PLUGIN_VERSION_TUPLE = tuple([int(x) for x in __version__.split(".")])
|
|
PLUGIN_VERSION = ".".join([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'
|
|
|
|
import codecs
|
|
import sys, os, re
|
|
import time
|
|
import zipfile
|
|
import traceback
|
|
from zipfile import ZipFile
|
|
|
|
class DeDRMError(Exception):
|
|
pass
|
|
|
|
from calibre.customize import FileTypePlugin
|
|
from calibre.constants import iswindows, isosx
|
|
from calibre.gui2 import is_ok_to_use_qt
|
|
from calibre.utils.config import config_dir
|
|
|
|
|
|
# Wrap a stream so that output gets flushed immediately
|
|
# and also make sure that any unicode strings get safely
|
|
# 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,str):
|
|
data = data.encode(self.encoding,"replace")
|
|
try:
|
|
self.stream.buffer.write(data)
|
|
self.stream.buffer.flush()
|
|
except:
|
|
# We can do nothing if a write fails
|
|
pass
|
|
def __getattr__(self, attr):
|
|
return getattr(self.stream, attr)
|
|
|
|
class DeDRM(FileTypePlugin):
|
|
name = PLUGIN_NAME
|
|
description = "Removes DRM from Amazon Kindle, Adobe Adept (including Kobo), Barnes & Noble, Mobipocket and eReader ebooks. Credit given to i♥cabbages and The Dark Reverser for the original stand-alone scripts."
|
|
supported_platforms = ['linux', 'osx', 'windows']
|
|
author = "Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
|
|
version = PLUGIN_VERSION_TUPLE
|
|
minimum_calibre_version = (5, 0, 0) # Python 3.
|
|
file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','azw8','tpz','kfx','kfx-zip'])
|
|
on_import = True
|
|
on_preprocess = True
|
|
priority = 600
|
|
|
|
|
|
def initialize(self):
|
|
"""
|
|
Dynamic modules can't be imported/loaded from a zipfile.
|
|
So this routine will extract the appropriate
|
|
library for the target OS and copy it to the 'alfcrypto' subdirectory of
|
|
calibre's configuration directory. That 'alfcrypto' directory is then
|
|
inserted into the syspath (as the very first entry) in the run function
|
|
so the CDLL stuff will work in the alfcrypto.py script.
|
|
|
|
The extraction only happens once per version of the plugin
|
|
Also perform upgrade of preferences once per version
|
|
"""
|
|
try:
|
|
self.pluginsdir = os.path.join(config_dir,"plugins")
|
|
if not os.path.exists(self.pluginsdir):
|
|
os.mkdir(self.pluginsdir)
|
|
self.maindir = os.path.join(self.pluginsdir,"DeDRM")
|
|
if not os.path.exists(self.maindir):
|
|
os.mkdir(self.maindir)
|
|
self.helpdir = os.path.join(self.maindir,"help")
|
|
if not os.path.exists(self.helpdir):
|
|
os.mkdir(self.helpdir)
|
|
self.alfdir = os.path.join(self.maindir,"libraryfiles")
|
|
if not os.path.exists(self.alfdir):
|
|
os.mkdir(self.alfdir)
|
|
# only continue if we've never run this version of the plugin before
|
|
self.verdir = os.path.join(self.maindir,PLUGIN_VERSION)
|
|
if not os.path.exists(self.verdir):
|
|
if iswindows:
|
|
names = ["alfcrypto.dll","alfcrypto64.dll"]
|
|
elif isosx:
|
|
names = ["libalfcrypto.dylib"]
|
|
else:
|
|
names = ["libalfcrypto32.so","libalfcrypto64.so","kindlekey.py","adobekey.py","subasyncio.py"]
|
|
lib_dict = self.load_resources(names)
|
|
print("{0} v{1}: Copying needed library files from plugin's zip".format(PLUGIN_NAME, PLUGIN_VERSION))
|
|
|
|
for entry, data in lib_dict.items():
|
|
file_path = os.path.join(self.alfdir, entry)
|
|
try:
|
|
os.remove(file_path)
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
open(file_path,'wb').write(data)
|
|
except:
|
|
print("{0} v{1}: Exception when copying needed library files".format(PLUGIN_NAME, PLUGIN_VERSION))
|
|
traceback.print_exc()
|
|
pass
|
|
|
|
# convert old preferences, if necessary.
|
|
from calibre_plugins.dedrm.prefs import convertprefs
|
|
convertprefs()
|
|
|
|
# mark that this version has been initialized
|
|
os.mkdir(self.verdir)
|
|
except Exception as e:
|
|
traceback.print_exc()
|
|
raise
|
|
|
|
def ePubDecrypt(self,path_to_ebook):
|
|
# Create a TemporaryPersistent file to work with.
|
|
# Check original epub archive for zip errors.
|
|
import calibre_plugins.dedrm.zipfix
|
|
|
|
inf = self.temporary_file(".epub")
|
|
try:
|
|
print("{0} v{1}: Verifying zip archive integrity".format(PLUGIN_NAME, PLUGIN_VERSION))
|
|
fr = zipfix.fixZip(path_to_ebook, inf.name)
|
|
fr.fix()
|
|
except Exception as e:
|
|
print("{0} v{1}: Error \'{2}\' when checking zip archive".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0]))
|
|
raise Exception(e)
|
|
|
|
# import the decryption keys
|
|
import calibre_plugins.dedrm.prefs as prefs
|
|
dedrmprefs = prefs.DeDRM_Prefs()
|
|
|
|
# import the Barnes & Noble ePub handler
|
|
import calibre_plugins.dedrm.ignobleepub as ignobleepub
|
|
|
|
|
|
#check the book
|
|
if ignobleepub.ignobleBook(inf.name):
|
|
print("{0} v{1}: “{2}” is a secure Barnes & Noble ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)))
|
|
|
|
# Attempt to decrypt epub with each encryption key (generated or provided).
|
|
for keyname, userkey in dedrmprefs['bandnkeys'].items():
|
|
keyname_masked = "".join(("X" if (x.isdigit()) else x) for x in keyname)
|
|
print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked))
|
|
of = self.temporary_file(".epub")
|
|
|
|
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
|
try:
|
|
result = ignobleepub.decryptBook(userkey, inf.name, of.name)
|
|
except:
|
|
print("{0} v{1}: Exception when trying to decrypt after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
|
traceback.print_exc()
|
|
result = 1
|
|
|
|
of.close()
|
|
|
|
if result == 0:
|
|
# Decryption was successful.
|
|
# Return the modified PersistentTemporary file to calibre.
|
|
return of.name
|
|
|
|
print("{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime))
|
|
|
|
# perhaps we should see if we can get a key from a log file
|
|
print("{0} v{1}: Looking for new NOOK Study Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
|
|
|
# get the default NOOK Study keys
|
|
defaultkeys = []
|
|
|
|
try:
|
|
if iswindows or isosx:
|
|
from calibre_plugins.dedrm.ignoblekey import nookkeys
|
|
|
|
defaultkeys = nookkeys()
|
|
else: # linux
|
|
from .wineutils import WineGetKeys
|
|
|
|
scriptpath = os.path.join(self.alfdir,"ignoblekey.py")
|
|
defaultkeys = WineGetKeys(scriptpath, ".b64",dedrmprefs['adobewineprefix'])
|
|
|
|
except:
|
|
print("{0} v{1}: Exception when getting default NOOK Study Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
|
traceback.print_exc()
|
|
|
|
newkeys = []
|
|
for keyvalue in defaultkeys:
|
|
if keyvalue not in dedrmprefs['bandnkeys'].values():
|
|
newkeys.append(keyvalue)
|
|
|
|
if len(newkeys) > 0:
|
|
try:
|
|
for i,userkey in enumerate(newkeys):
|
|
print("{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
|
|
|
of = self.temporary_file(".epub")
|
|
|
|
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
|
try:
|
|
result = ignobleepub.decryptBook(userkey, inf.name, of.name)
|
|
except:
|
|
print("{0} v{1}: Exception when trying to decrypt after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
|
traceback.print_exc()
|
|
result = 1
|
|
|
|
of.close()
|
|
|
|
if result == 0:
|
|
# Decryption was a success
|
|
# Store the new successful key in the defaults
|
|
print("{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
|
try:
|
|
dedrmprefs.addnamedvaluetoprefs('bandnkeys','nook_Study_key',keyvalue)
|
|
dedrmprefs.writeprefs()
|
|
print("{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
|
except:
|
|
print("{0} v{1}: Exception saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
|
traceback.print_exc()
|
|
# Return the modified PersistentTemporary file to calibre.
|
|
return of.name
|
|
|
|
print("{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
|
except Exception as e:
|
|
pass
|
|
|
|
print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
|
raise DeDRMError("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
|
|
|
# import the Adobe Adept ePub handler
|
|
import calibre_plugins.dedrm.ineptepub as ineptepub
|
|
|
|
if ineptepub.adeptBook(inf.name):
|
|
print("{0} v{1}: {2} is a secure Adobe Adept ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)))
|
|
|
|
# Attempt to decrypt epub with each encryption key (generated or provided).
|
|
for keyname, userkeyhex in dedrmprefs['adeptkeys'].items():
|
|
userkey = codecs.decode(userkeyhex, 'hex')
|
|
print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname))
|
|
of = self.temporary_file(".epub")
|
|
|
|
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
|
try:
|
|
result = ineptepub.decryptBook(userkey, inf.name, of.name)
|
|
except:
|
|
print("{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
|
traceback.print_exc()
|
|
result = 1
|
|
|
|
try:
|
|
of.close()
|
|
except:
|
|
print("{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("{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("{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime))
|
|
|
|
# perhaps we need to get a new default ADE key
|
|
print("{0} v{1}: Looking for new default Adobe Digital Editions Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
|
|
|
# get the default Adobe keys
|
|
defaultkeys = []
|
|
|
|
try:
|
|
if iswindows or isosx:
|
|
from calibre_plugins.dedrm.adobekey import adeptkeys
|
|
|
|
defaultkeys = adeptkeys()
|
|
else: # linux
|
|
from .wineutils import WineGetKeys
|
|
|
|
scriptpath = os.path.join(self.alfdir,"adobekey.py")
|
|
defaultkeys = WineGetKeys(scriptpath, ".der",dedrmprefs['adobewineprefix'])
|
|
|
|
self.default_key = defaultkeys[0]
|
|
except:
|
|
print("{0} v{1}: Exception when getting default Adobe Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
|
traceback.print_exc()
|
|
self.default_key = ""
|
|
|
|
newkeys = []
|
|
for keyvalue in defaultkeys:
|
|
if codecs.encode(keyvalue, 'hex').decode('ascii') not in dedrmprefs['adeptkeys'].values():
|
|
newkeys.append(keyvalue)
|
|
|
|
if len(newkeys) > 0:
|
|
try:
|
|
for i,userkey in enumerate(newkeys):
|
|
print("{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
|
of = self.temporary_file(".epub")
|
|
|
|
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
|
try:
|
|
result = ineptepub.decryptBook(userkey, inf.name, of.name)
|
|
except:
|
|
print("{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
|
traceback.print_exc()
|
|
result = 1
|
|
|
|
of.close()
|
|
|
|
if result == 0:
|
|
# Decryption was a success
|
|
# Store the new successful key in the defaults
|
|
print("{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
|
try:
|
|
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',codecs.encode(keyvalue, 'hex').decode('ascii'))
|
|
dedrmprefs.writeprefs()
|
|
print("{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
|
except:
|
|
print("{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("{0} v{1}: Decrypted with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
|
# Return the modified PersistentTemporary file to calibre.
|
|
return of.name
|
|
|
|
print("{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
|
except Exception as e:
|
|
print("{0} v{1}: Unexpected Exception trying a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
|
traceback.print_exc()
|
|
pass
|
|
|
|
# Something went wrong with decryption.
|
|
print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
|
raise DeDRMError("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
|
|
|
# Not a Barnes & Noble nor an Adobe Adept
|
|
# Import the fixed epub.
|
|
print("{0} v{1}: “{2}” is neither an Adobe Adept nor a Barnes & Noble encrypted ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)))
|
|
raise DeDRMError("{0} v{1}: Couldn't decrypt after {2:.1f} seconds. DRM free perhaps?".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
|
|
|
def PDFDecrypt(self,path_to_ebook):
|
|
import calibre_plugins.dedrm.prefs as prefs
|
|
import calibre_plugins.dedrm.ineptpdf
|
|
|
|
dedrmprefs = prefs.DeDRM_Prefs()
|
|
# Attempt to decrypt epub with each encryption key (generated or provided).
|
|
print("{0} v{1}: {2} is a PDF ebook".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)))
|
|
for keyname, userkeyhex in dedrmprefs['adeptkeys'].items():
|
|
userkey = codecs.decode(userkeyhex,'hex')
|
|
print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname))
|
|
of = self.temporary_file(".pdf")
|
|
|
|
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
|
try:
|
|
result = ineptpdf.decryptBook(userkey, path_to_ebook, of.name)
|
|
except:
|
|
print("{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
|
traceback.print_exc()
|
|
result = 1
|
|
|
|
of.close()
|
|
|
|
if result == 0:
|
|
# Decryption was successful.
|
|
# Return the modified PersistentTemporary file to calibre.
|
|
return of.name
|
|
|
|
print("{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime))
|
|
|
|
# perhaps we need to get a new default ADE key
|
|
print("{0} v{1}: Looking for new default Adobe Digital Editions Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
|
|
|
# get the default Adobe keys
|
|
defaultkeys = []
|
|
|
|
try:
|
|
if iswindows or isosx:
|
|
from calibre_plugins.dedrm.adobekey import adeptkeys
|
|
|
|
defaultkeys = adeptkeys()
|
|
else: # linux
|
|
from .wineutils import WineGetKeys
|
|
|
|
scriptpath = os.path.join(self.alfdir,"adobekey.py")
|
|
defaultkeys = WineGetKeys(scriptpath, ".der",dedrmprefs['adobewineprefix'])
|
|
|
|
self.default_key = defaultkeys[0]
|
|
except:
|
|
print("{0} v{1}: Exception when getting default Adobe Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
|
traceback.print_exc()
|
|
self.default_key = ""
|
|
|
|
newkeys = []
|
|
for keyvalue in defaultkeys:
|
|
if codecs.encode(keyvalue,'hex') not in dedrmprefs['adeptkeys'].values():
|
|
newkeys.append(keyvalue)
|
|
|
|
if len(newkeys) > 0:
|
|
try:
|
|
for i,userkey in enumerate(newkeys):
|
|
print("{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
|
of = self.temporary_file(".pdf")
|
|
|
|
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
|
try:
|
|
result = ineptpdf.decryptBook(userkey, path_to_ebook, of.name)
|
|
except:
|
|
print("{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
|
traceback.print_exc()
|
|
result = 1
|
|
|
|
of.close()
|
|
|
|
if result == 0:
|
|
# Decryption was a success
|
|
# Store the new successful key in the defaults
|
|
print("{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
|
try:
|
|
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',codecs.encode(keyvalue,'hex'))
|
|
dedrmprefs.writeprefs()
|
|
print("{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
|
except:
|
|
print("{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()
|
|
# Return the modified PersistentTemporary file to calibre.
|
|
return of.name
|
|
|
|
print("{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
|
except Exception as e:
|
|
pass
|
|
|
|
# Something went wrong with decryption.
|
|
print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
|
raise DeDRMError("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
|
|
|
|
|
def KindleMobiDecrypt(self,path_to_ebook):
|
|
|
|
# add the alfcrypto directory to sys.path so alfcrypto.py
|
|
# will be able to locate the custom lib(s) for CDLL import.
|
|
sys.path.insert(0, self.alfdir)
|
|
# Had to move this import here so the custom libs can be
|
|
# extracted to the appropriate places beforehand these routines
|
|
# look for them.
|
|
import calibre_plugins.dedrm.prefs as prefs
|
|
import calibre_plugins.dedrm.k4mobidedrm
|
|
|
|
dedrmprefs = prefs.DeDRM_Prefs()
|
|
pids = dedrmprefs['pids']
|
|
serials = dedrmprefs['serials']
|
|
for android_serials_list in dedrmprefs['androidkeys'].values():
|
|
#print android_serials_list
|
|
serials.extend(android_serials_list)
|
|
#print serials
|
|
androidFiles = []
|
|
kindleDatabases = list(dedrmprefs['kindlekeys'].items())
|
|
|
|
try:
|
|
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,kindleDatabases,androidFiles,serials,pids,self.starttime)
|
|
except Exception as e:
|
|
decoded = False
|
|
# perhaps we need to get a new default Kindle for Mac/PC key
|
|
defaultkeys = []
|
|
print("{0} v{1}: Failed to decrypt with error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION,e.args[0]))
|
|
print("{0} v{1}: Looking for new default Kindle Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
|
|
|
try:
|
|
if iswindows or isosx:
|
|
from calibre_plugins.dedrm.kindlekey import kindlekeys
|
|
|
|
defaultkeys = kindlekeys()
|
|
else: # linux
|
|
from .wineutils import WineGetKeys
|
|
|
|
scriptpath = os.path.join(self.alfdir,"kindlekey.py")
|
|
defaultkeys = WineGetKeys(scriptpath, ".k4i",dedrmprefs['kindlewineprefix'])
|
|
except:
|
|
print("{0} v{1}: Exception when getting default Kindle Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
|
traceback.print_exc()
|
|
pass
|
|
|
|
newkeys = {}
|
|
for i,keyvalue in enumerate(defaultkeys):
|
|
keyname = "default_key_{0:d}".format(i+1)
|
|
if keyvalue not in dedrmprefs['kindlekeys'].values():
|
|
newkeys[keyname] = keyvalue
|
|
if len(newkeys) > 0:
|
|
print("{0} v{1}: Found {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), "key" if len(newkeys)==1 else "keys"))
|
|
try:
|
|
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,list(newkeys.items()),[],[],[],self.starttime)
|
|
decoded = True
|
|
# store the new successful keys in the defaults
|
|
print("{0} v{1}: Saving {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), "key" if len(newkeys)==1 else "keys"))
|
|
for keyvalue in newkeys.values():
|
|
dedrmprefs.addnamedvaluetoprefs('kindlekeys','default_key',keyvalue)
|
|
dedrmprefs.writeprefs()
|
|
except Exception as e:
|
|
pass
|
|
if not decoded:
|
|
#if you reached here then no luck raise and exception
|
|
print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
|
raise DeDRMError("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
|
|
|
of = self.temporary_file(book.getBookExtension())
|
|
book.getFile(of.name)
|
|
of.close()
|
|
book.cleanup()
|
|
return of.name
|
|
|
|
|
|
def eReaderDecrypt(self,path_to_ebook):
|
|
|
|
import calibre_plugins.dedrm.prefs as prefs
|
|
import calibre_plugins.dedrm.erdr2pml
|
|
|
|
dedrmprefs = prefs.DeDRM_Prefs()
|
|
# Attempt to decrypt epub with each encryption key (generated or provided).
|
|
for keyname, userkey in dedrmprefs['ereaderkeys'].items():
|
|
keyname_masked = "".join(("X" if (x.isdigit()) else x) for x in keyname)
|
|
print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked))
|
|
of = self.temporary_file(".pmlz")
|
|
|
|
# Give the userkey, ebook and TemporaryPersistent file to the decryption function.
|
|
result = erdr2pml.decryptBook(path_to_ebook, of.name, True, codecs.decode(userkey,'hex'))
|
|
|
|
of.close()
|
|
|
|
# Decryption was successful return the modified PersistentTemporary
|
|
# file to Calibre's import process.
|
|
if result == 0:
|
|
print("{0} v{1}: Successfully decrypted with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime))
|
|
return of.name
|
|
|
|
print("{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime))
|
|
|
|
print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
|
raise DeDRMError("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
|
|
|
|
|
def run(self, path_to_ebook):
|
|
|
|
# make sure any unicode output gets converted safely with 'replace'
|
|
sys.stdout=SafeUnbuffered(sys.stdout)
|
|
sys.stderr=SafeUnbuffered(sys.stderr)
|
|
|
|
print("{0} v{1}: Trying to decrypt {2}".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)))
|
|
self.starttime = time.time()
|
|
|
|
booktype = os.path.splitext(path_to_ebook)[1].lower()[1:]
|
|
if booktype in ['prc','mobi','pobi','azw','azw1','azw3','azw4','tpz','kfx-zip']:
|
|
# Kindle/Mobipocket
|
|
decrypted_ebook = self.KindleMobiDecrypt(path_to_ebook)
|
|
elif booktype == 'pdb':
|
|
# eReader
|
|
decrypted_ebook = self.eReaderDecrypt(path_to_ebook)
|
|
pass
|
|
elif booktype == 'pdf':
|
|
# Adobe Adept PDF (hopefully)
|
|
decrypted_ebook = self.PDFDecrypt(path_to_ebook)
|
|
pass
|
|
elif booktype == 'epub':
|
|
# Adobe Adept or B&N ePub
|
|
decrypted_ebook = self.ePubDecrypt(path_to_ebook)
|
|
else:
|
|
print("Unknown booktype {0}. Passing back to calibre unchanged".format(booktype))
|
|
return path_to_ebook
|
|
print("{0} v{1}: Finished after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
|
return decrypted_ebook
|
|
|
|
def is_customizable(self):
|
|
# return true to allow customization via the Plugin->Preferences.
|
|
return True
|
|
|
|
def config_widget(self):
|
|
import calibre_plugins.dedrm.config as config
|
|
return config.ConfigWidget(self.plugin_path, self.alfdir)
|
|
|
|
def save_settings(self, config_widget):
|
|
config_widget.save_settings()
|