From a930a99c8000b610eacce8ba65cce668cf5fe355 Mon Sep 17 00:00:00 2001 From: Florian Bach Date: Sun, 26 Sep 2021 12:56:53 +0200 Subject: [PATCH] v0.0.4: Make DeDRM work without modifications --- .github/workflows/main.yml | 1 - README.md | 13 +++----- calibre-plugin/__init__.py | 46 +++++++++++++++----------- calibre-plugin/config.py | 68 ++++++++++++++++---------------------- 4 files changed, 60 insertions(+), 68 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 552b87f..6037533 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -20,5 +20,4 @@ jobs: name: linux path: | calibre-plugin.zip - libgourou_bundle_raw.tar.xz \ No newline at end of file diff --git a/README.md b/README.md index 274e655..c946497 100644 --- a/README.md +++ b/README.md @@ -10,21 +10,17 @@ It is a full Python reimplementation of libgourou by Grégory Soutadé (http://i 3. Click the "Link to ADE account" button 4. Enter your AdobeID and password, then wait a couple seconds for the success message. 5. The settings window should now say "Authorized with ADE ID X on device Y". -6. Click the "Export account activation data" and "Export account encryption key" buttons to export / backup your keys. Do not skip this step. The first file (ZIP) can be used to re-authorize Calibre after a reset / reinstall without using up one of your Adobe authorizations. The second file (DER) can be imported into Alf. -7. Download an EPUB ACSM file from Adobe's test library and see if you can import it into Calibre: https://www.adobe.com/de/solutions/ebook/digital-editions/sample-ebook-library.html +6. Click the "Export account activation data" and "Export account encryption key" buttons to export / backup your keys. Do not skip this step. The first file (ZIP) can be used to re-authorize Calibre after a reset / reinstall without using up one of your Adobe authorizations. The second file (DER) can be imported into DeDRM. +7. If needed (new AdobeID), import the DER file into the DeDRM plugin. +8. Download an EPUB ACSM file from Adobe's test library and see if you can import it into Calibre: https://www.adobe.com/de/solutions/ebook/digital-editions/sample-ebook-library.html IMPORTANT: - I would suggest creating a new dummy AdobeID to use for Calibre so just in case Adobe detects this and bans you, you don't lose your main AdobeID. -- Combined with that I suggest importing the DER file into the Alf plugin to make sure that losing your AdobeID doesn't also mean you'll lose access to all your eBooks. See the section "Combining with Alf" below. +- Combined with that I suggest importing the DER file into the DeDRM plugin to make sure that losing your AdobeID doesn't also mean you'll lose access to all your eBooks. - This plugin doesn't yet work with PDFs. Importing an ACSM file for a PDF book will just result in the ACSM file being imported, it won't be converted into a PDF. - This software is not approved by Adobe. I am not responsible if Adobe detects that you're using nonstandard software and bans your account. Do not complain to me if Adobe bans your main ADE account - you have been warned. -## Combining with Alf - -In order to combine this plugin with Alf, you'll need to go to Alf's settings and import the DER file you've just exported from this plugin. - -Also, there's a small code change needed to Alf's plugin to make it work together with this one. Open up the `__init__.py` file in Alf's ZIP file and search for "file_types". You'll find a list of supported file types. Add "acsm" to that list, save the file, put it back into the ZIP file, then re-import the Alf plugin into Calibre. This is needed due to a bug in Calibre - even though this plugin converts the ACSM file into an EPUB on import, Alf still "thinks" it's an ACSM file and doesn't do anything with it. By forcing Alf to accept ACSM files it will work correctly (since it'll then notice that it's actually an EPUB). ## To-Do list for the future? @@ -34,4 +30,5 @@ There's a bunch of features that could still be added, but most of them aren't i - Support for un-authorizing a machine - Support to re-import a backed-up authorization after a reinstallation (right now you can only do that manually) - Support for PDFs +- Support for returning loan books - ... diff --git a/calibre-plugin/__init__.py b/calibre-plugin/__init__.py index 914baa2..b2ac598 100644 --- a/calibre-plugin/__init__.py +++ b/calibre-plugin/__init__.py @@ -8,10 +8,11 @@ # v0.0.1: First version. # v0.0.2: Allow key extraction without extra binary call (unreleased test version) # v0.0.3: Standalone Calibre plugin for Linux, Windows, MacOS without the need for libgourou. +# v0.0.4: Manually execute DeDRM (if installed) after converting ACSM to EPUB. from calibre.customize import FileTypePlugin # type: ignore -__version__ = '0.0.3' +__version__ = '0.0.4' PLUGIN_NAME = "DeACSM" PLUGIN_VERSION_TUPLE = tuple([int(x) for x in __version__.split(".")]) @@ -24,7 +25,6 @@ import os, shutil, traceback, sys import zipfile from lxml import etree - class DeACSM(FileTypePlugin): name = PLUGIN_NAME description = "Takes an Adobe ACSM file and converts that into a useable EPUB file. Python reimplementation of libgourou by Grégory Soutadé" @@ -101,9 +101,8 @@ class DeACSM(FileTypePlugin): from libadobe import VAR_HOBBES_VERSION, createDeviceKeyFile, update_account_path from libadobeAccount import createDeviceFile, createUser, signIn, activateDevice except: - print("error Account") - raise - raise + print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) + traceback.print_exc() # Fulfill: try: @@ -114,11 +113,8 @@ class DeACSM(FileTypePlugin): from libadobe import sendHTTPRequest from libadobeFulfill import buildRights, fulfill except: - print("error Fulfill") - raise - raise - - + print("{0} v{1}: Error while importing Fulfillment stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) + traceback.print_exc() import calibre_plugins.deacsm.prefs as prefs # type: ignore deacsmprefs = prefs.DeACSM_Prefs() @@ -127,9 +123,6 @@ class DeACSM(FileTypePlugin): except Exception as e: traceback.print_exc() raise - - - def is_customizable(self): @@ -178,9 +171,8 @@ class DeACSM(FileTypePlugin): from libadobe import sendHTTPRequest from libadobeFulfill import buildRights, fulfill except: - print("error Fulfill") - raise - raise + print("{0} v{1}: Error while importing Fulfillment stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) + traceback.print_exc() adobe_fulfill_response = etree.fromstring(replyData) @@ -264,9 +256,8 @@ class DeACSM(FileTypePlugin): from libadobe import sendHTTPRequest from libadobeFulfill import buildRights, fulfill except: - print("error Fulfill") - raise - raise + print("{0} v{1}: Error while importing Fulfillment stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) + traceback.print_exc() success, replyData = fulfill(path_to_ebook) @@ -276,6 +267,23 @@ class DeACSM(FileTypePlugin): print("{0} v{1}: Downloading book ...".format(PLUGIN_NAME, PLUGIN_VERSION)) rpl = self.download(replyData) if (rpl is not None): + # Got a file + + # Because Calibre still thinks this is an ACSM file (not an EPUB) + # it will not run other plugins like Alf / DeDRM. + # So we have to manually check if it's installed, + # and if it is, run it to remove DRM. + try: + from calibre.customize.ui import _initialized_plugins + for plugin in _initialized_plugins: + if (plugin.name == "DeDRM"): + print("{0} v{1}: Executing DeDRM plugin ...".format(PLUGIN_NAME, PLUGIN_VERSION)) + return plugin.run(rpl) + except: + print("{0} v{1}: Error while checking for DeDRM plugin.".format(PLUGIN_NAME, PLUGIN_VERSION)) + pass + + # Looks like DeDRM is not installed, return book with DRM. return rpl diff --git a/calibre-plugin/config.py b/calibre-plugin/config.py index c10f6ba..449c5d2 100644 --- a/calibre-plugin/config.py +++ b/calibre-plugin/config.py @@ -3,7 +3,7 @@ # pyright: reportUndefinedVariable=false -import os, base64 +import os, base64, traceback from lxml import etree @@ -79,9 +79,9 @@ class ConfigWidget(QWidget): from libadobe import VAR_HOBBES_VERSION, createDeviceKeyFile, update_account_path from libadobeAccount import createDeviceFile, createUser, signIn, activateDevice except: - print("error Account") - raise - raise + print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) + traceback.print_exc() + update_account_path(self.deacsmprefs["path_to_account_data"]) @@ -126,7 +126,7 @@ class ConfigWidget(QWidget): if (filename is None): return - print("would export to " + filename) + print("{0} v{1}: Exporting activation data to {2}".format(PLUGIN_NAME, PLUGIN_VERSION, filename)) try: with ZipFile(filename, 'w') as zipfile: @@ -148,9 +148,8 @@ class ConfigWidget(QWidget): from libadobe import VAR_HOBBES_VERSION, createDeviceKeyFile, update_account_path from libadobeAccount import createDeviceFile, createUser, signIn, activateDevice except: - print("error Account") - raise - raise + print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) + traceback.print_exc() update_account_path(self.deacsmprefs["path_to_account_data"]) @@ -195,9 +194,20 @@ class ConfigWidget(QWidget): def export_key(self): - pluginsdir = os.path.join(config_dir,"plugins") - maindir = os.path.join(pluginsdir,"DeACSM") - verdir = os.path.join(maindir,PLUGIN_VERSION) + + try: + from calibre_plugins.deacsm.libadobe import update_account_path + from calibre_plugins.deacsm.libadobeAccount import exportAccountEncryptionKeyDER + except: + try: + from libadobe import update_account_path + from libadobeAccount import exportAccountEncryptionKeyDER + except: + print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION)) + traceback.print_exc() + + + update_account_path(self.deacsmprefs["path_to_account_data"]) filters = [("DER Files", ["der"])] @@ -206,38 +216,16 @@ class ConfigWidget(QWidget): if (filename is None): return - print("would export to " + filename) + print("{0} v{1}: Exporting encryption key to {2}".format(PLUGIN_NAME, PLUGIN_VERSION, filename)) - import calibre_plugins.deacsm.prefs as prefs # type: ignore - deacsmprefs = prefs.DeACSM_Prefs() + ret = exportAccountEncryptionKeyDER(filename) + + if ret: + return info_dialog(None, "Done", "Key successfully exported", show=True, show_copy_button=False) + else: + return error_dialog(None, "Export failed", "Export failed", show=True, show_copy_button=False) - activation_xml_path = os.path.join(self.deacsmprefs["path_to_account_data"], "activation.xml") - - container = None - try: - container = etree.parse(activation_xml_path) - except (FileNotFoundError, OSError) as e: - return error_dialog(None, "Export failed", "Export failed - Can't open activation.xml", show=True, show_copy_button=False) - - key_binary = None - try: - adeptNS = lambda tag: '{%s}%s' % ('http://ns.adobe.com/adept', tag) - usernameXML = container.find(adeptNS("credentials")).find(adeptNS("privateLicenseKey")) - key_base64 = usernameXML.text - key_binary = base64.decodebytes(key_base64.encode())[26:] - except: - return error_dialog(None, "Export failed", "Export failed - Can't read key from activation.xml", show=True, show_copy_button=False) - - try: - output_file = open(filename, "wb") - output_file.write(key_binary) - output_file.close() - except: - return error_dialog(None, "Export failed", "Export failed - Can't write key to file", show=True, show_copy_button=False) - - - info_dialog(None, "Done", "Key successfully exported", show=True, show_copy_button=False) def save_settings(self): #self.deacsmprefs.set('path_to_account_data', self.txtboxUA.text())