From 851ba90431ba711f1ae3db7da7a6fc72c043763c Mon Sep 17 00:00:00 2001 From: Benton Edmondson Date: Sat, 4 Jun 2022 23:14:43 -0400 Subject: [PATCH] switched from python to cpp --- .gitignore | 4 +- demo.png | Bin 48070 -> 0 bytes flake.lock | 95 +++++++++++++++-------- flake.nix | 190 +++++++++++++++++++++++++++++++++------------ lib/handle_aax.py | 51 ------------ lib/handle_acsm.py | 77 ------------------ lib/utils.py | 45 ----------- readme.md | 73 +++++++---------- src/knock.cpp | 104 +++++++++++++++++++++++++ src/knock.py | 46 ----------- 10 files changed, 335 insertions(+), 350 deletions(-) delete mode 100644 demo.png delete mode 100644 lib/handle_aax.py delete mode 100644 lib/handle_acsm.py delete mode 100644 lib/utils.py create mode 100644 src/knock.cpp delete mode 100755 src/knock.py diff --git a/.gitignore b/.gitignore index e2f5dd2..f4d6c00 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -result \ No newline at end of file +result +.vscode +todo.md \ No newline at end of file diff --git a/demo.png b/demo.png deleted file mode 100644 index 217e924fea6b74aa44e8be4f0de790cb116aadcb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48070 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 NRNiPPmcKR&{XceTfN1~# diff --git a/flake.lock b/flake.lock index 52ed2b5..39dfaa9 100644 --- a/flake.lock +++ b/flake.lock @@ -1,32 +1,45 @@ { "nodes": { - "benpkgs": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, + "base64-src": { + "flake": false, "locked": { - "lastModified": 1638237664, - "narHash": "sha256-9SkZPl1EZ1ezWZoMdhYOzPm2HK0Ca5hkfphjlPNk/FY=", - "owner": "BentonEdmondson", - "repo": "benpkgs", - "rev": "da71a3a488de3ae7ab8daff2c2a9354d2f0464bf", - "type": "github" + "lastModified": 1468170709, + "narHash": "sha256-dt6i1j0rqH7lA+2XXp9KTEhj2GvYueyGrHh9VXBEsbw=", + "ref": "master", + "rev": "7d5a89229a525452e37504976a73c35fbaf2fe4d", + "revCount": 1, + "type": "git", + "url": "https://gist.github.com/f0fd86b6c73063283afe550bc5d77594.git" }, "original": { - "owner": "BentonEdmondson", - "repo": "benpkgs", - "type": "github" + "type": "git", + "url": "https://gist.github.com/f0fd86b6c73063283afe550bc5d77594.git" + } + }, + "gourou-src": { + "flake": false, + "locked": { + "lastModified": 1650729079, + "narHash": "sha256-2ZnuO/fIjSzTK0srFzDAhGNh1hA4lZf3lK4VuFxWXmo=", + "ref": "master", + "rev": "7b6b1471fefb27e79e06e5d686cb8842c539cd0c", + "revCount": 79, + "type": "git", + "url": "git://soutade.fr/libgourou.git" + }, + "original": { + "ref": "master", + "type": "git", + "url": "git://soutade.fr/libgourou.git" } }, "nixpkgs": { "locked": { - "lastModified": 1638198142, - "narHash": "sha256-plU9b8r4St6q4U7VHtG9V7oF8k9fIpfXl/KDaZLuY9k=", + "lastModified": 1654230545, + "narHash": "sha256-8Vlwf0x8ow6pPOK2a04bT+pxIeRnM1+O0Xv9/CuDzRs=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8a308775674e178495767df90c419425474582a1", + "rev": "236cc2971ac72acd90f0ae3a797f9f83098b17ec", "type": "github" }, "original": { @@ -36,31 +49,47 @@ "type": "github" } }, - "rmdrm": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, + "pugixml-src": { + "flake": false, "locked": { - "lastModified": 1638240151, - "narHash": "sha256-2FHEb4xaskyLdS5M/eILue74Idplve9g8FRxg9D1Z/4=", - "owner": "BentonEdmondson", - "repo": "rmdrm", - "rev": "c60eaa0c4338be5ee6165f59f2a1816566aba8a0", + "lastModified": 1644379750, + "narHash": "sha256-FLemG9T17n6l7vgb01OmO22BK59jv5uozVHeUnILEEQ=", + "owner": "zeux", + "repo": "pugixml", + "rev": "314baf6605143f1e837209008f490e8559529e1c", "type": "github" }, "original": { - "owner": "BentonEdmondson", - "repo": "rmdrm", + "owner": "zeux", + "ref": "latest", + "repo": "pugixml", "type": "github" } }, "root": { "inputs": { - "benpkgs": "benpkgs", + "base64-src": "base64-src", + "gourou-src": "gourou-src", "nixpkgs": "nixpkgs", - "rmdrm": "rmdrm" + "pugixml-src": "pugixml-src", + "updfparser-src": "updfparser-src" + } + }, + "updfparser-src": { + "flake": false, + "locked": { + "lastModified": 1647424063, + "narHash": "sha256-9dvibKiUbbI4CrmuAaJzlpntT0XdLvdGeC2/WzjlA5U=", + "ref": "master", + "rev": "9d56c1d0b1ce81aae4c8db9d99a8b5d1f7967bcf", + "revCount": 25, + "type": "git", + "url": "git://soutade.fr/updfparser.git" + }, + "original": { + "ref": "master", + "type": "git", + "url": "git://soutade.fr/updfparser.git" } } }, diff --git a/flake.nix b/flake.nix index f499354..519aa3e 100644 --- a/flake.nix +++ b/flake.nix @@ -1,60 +1,150 @@ { inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - - rmdrm.url = "github:BentonEdmondson/rmdrm"; - rmdrm.inputs.nixpkgs.follows = "nixpkgs"; - - benpkgs.url = "github:BentonEdmondson/benpkgs"; - benpkgs.inputs.nixpkgs.follows = "nixpkgs"; + gourou-src = { + url = "git://soutade.fr/libgourou.git"; + type = "git"; + ref = "master"; + flake = false; + }; + updfparser-src = { + url = "git://soutade.fr/updfparser.git"; + type = "git"; + ref = "master"; + flake = false; + }; + base64-src = { + url = "git+https://gist.github.com/f0fd86b6c73063283afe550bc5d77594.git"; + flake = false; + }; + pugixml-src = { + url = "github:zeux/pugixml/latest"; + flake = false; + }; }; outputs = flakes: let - nixpkgs = flakes.nixpkgs.legacyPackages.x86_64-linux; - libgourou-utils = flakes.libgourou-utils.defaultPackage.x86_64-linux; - rmdrm = flakes.rmdrm.defaultPackage.x86_64-linux; - benpkgs = flakes.benpkgs.packages.x86_64-linux; - in { - defaultPackage.x86_64-linux = nixpkgs.python3Packages.buildPythonApplication rec { - pname = "knock"; - version = "1.1.0-alpha"; - src = ./.; - - nativeBuildInputs = [ nixpkgs.makeWrapper ]; - - buildInputs = [ - rmdrm - benpkgs.libgourou - nixpkgs.ffmpeg + self = flakes.self.packages.x86_64-linux; + nixpkgs = flakes.nixpkgs.legacyPackages.x86_64-linux.pkgsStatic; + gourou-src = flakes.gourou-src; + updfparser-src = flakes.updfparser-src; + base64-src = flakes.base64-src; + pugixml-src = flakes.pugixml-src; + cxx = "${nixpkgs.stdenv.cc}/bin/x86_64-unknown-linux-musl-g++"; + ar = "${nixpkgs.stdenv.cc.bintools.bintools_bin}/bin/x86_64-unknown-linux-musl-ar"; + obj-flags = "-O2 -static"; + in rec { + packages.x86_64-linux.libzip-static = nixpkgs.libzip.overrideAttrs (prev: { + cmakeFlags = (prev.cmakeFlags or []) ++ [ + "-DBUILD_SHARED_LIBS=OFF" + "-DBUILD_EXAMPLES=OFF" + "-DBUILD_DOC=OFF" + "-DBUILD_TOOLS=OFF" + "-DBUILD_REGRESS=OFF" ]; - - propagatedBuildInputs = [ - benpkgs.Audible - nixpkgs.python3Packages.python_magic - nixpkgs.python3Packages.xdg - nixpkgs.python3Packages.click - ]; - - format = "other"; - - installPhase = '' - mkdir -p $out/bin $out/${nixpkgs.python3.sitePackages} - cp lib/*.py $out/${nixpkgs.python3.sitePackages} - cp src/knock.py $out/bin/knock - wrapProgram $out/bin/knock --prefix PATH : ${nixpkgs.lib.makeBinPath buildInputs} - #''; - - meta = { - description = "A CLI tool to convert ACSM files to DRM-free EPUB/PDF files"; - homepage = "https://github.com/BentonEdmondson/knock"; - license = [ nixpkgs.lib.licenses.gpl3Only ]; - maintainers = [{ - name = "Benton Edmondson"; - email = "bentonedmondson@gmail.com"; - }]; - # potentially others, but I'm only listed those tested - platforms = [ "x86_64-linux" ]; - }; + outputs = ["out"]; + }); + packages.x86_64-linux.base64 = derivation { + name = "updfparser"; + system = "x86_64-linux"; + builder = "${nixpkgs.bash}/bin/bash"; + PATH = "${nixpkgs.coreutils}/bin"; + args = ["-c" '' + mkdir -p $out/include/base64 + cp ${base64-src}/Base64.h $out/include/base64/Base64.h + '']; }; + packages.x86_64-linux.updfparser = derivation { + name = "updfparser"; + system = "x86_64-linux"; + builder = "${nixpkgs.bash}/bin/bash"; + PATH = "${nixpkgs.coreutils}/bin"; + args = [ "-c" '' + ${cxx} \ + -c ${updfparser-src}/src/*.cpp \ + -I ${updfparser-src}/include \ + ${obj-flags} + mkdir -p $out/lib + ${ar} crs $out/lib/libupdfparser.a *.o + '' ]; + }; + packages.x86_64-linux.gourou = derivation { + name = "gourou"; + system = "x86_64-linux"; + builder = "${nixpkgs.bash}/bin/bash"; + PATH = "${nixpkgs.coreutils}/bin"; + args = [ "-c" '' + ${cxx} \ + -c \ + ${gourou-src}/src/*.cpp \ + ${pugixml-src}/src/pugixml.cpp \ + -I ${self.base64}/include \ + -I ${gourou-src}/include \ + -I ${pugixml-src}/src \ + -I ${updfparser-src}/include \ + ${obj-flags} + mkdir -p $out/lib $out/debug + ${ar} crs $out/lib/libgourou.a *.o + cp *.o $out/debug + '' ]; + }; + packages.x86_64-linux.utils-common = derivation { + name = "utils-common"; + system = "x86_64-linux"; + builder = "${nixpkgs.bash}/bin/bash"; + PATH = "${nixpkgs.coreutils}/bin"; + args = [ "-c" '' + ${cxx} \ + -c ${gourou-src}/utils/drmprocessorclientimpl.cpp \ + ${gourou-src}/utils/utils_common.cpp \ + -I ${gourou-src}/utils \ + -I ${gourou-src}/include \ + -I ${pugixml-src}/src \ + -I ${nixpkgs.openssl.dev}/include \ + -I ${nixpkgs.curl.dev}/include \ + -I ${nixpkgs.zlib.dev}/include \ + -I ${self.libzip-static}/include \ + ${obj-flags} + mkdir -p $out/lib + ${ar} crs $out/lib/libutils-common.a *.o + '' ]; + }; + packages.x86_64-linux.knock = derivation { + name = "knock"; + system = "x86_64-linux"; + builder = "${nixpkgs.bash}/bin/bash"; + PATH = "${nixpkgs.coreutils}/bin"; + args = [ "-c" '' + mkdir -p $out/bin + ${cxx} \ + -o $out/bin/knock \ + ${./src/knock.cpp} \ + -Wl,--as-needed -static \ + ${self.utils-common}/lib/libutils-common.a \ + ${self.gourou}/lib/libgourou.a \ + ${self.updfparser}/lib/libupdfparser.a \ + -Wl,--start-group \ + ${self.libzip-static}/lib/libzip.a \ + ${nixpkgs.libnghttp2}/lib/libnghttp2.a \ + ${nixpkgs.libidn2.out}/lib/libidn2.a \ + ${nixpkgs.libunistring}/lib/libunistring.a \ + ${nixpkgs.libssh2}/lib/libssh2.a \ + ${nixpkgs.zstd.out}/lib/libzstd.a \ + ${nixpkgs.zlib}/lib/libz.a \ + ${nixpkgs.openssl.out}/lib/libcrypto.a \ + ${nixpkgs.curl.out}/lib/libcurl.a \ + ${nixpkgs.openssl.out}/lib/libssl.a \ + -static-libgcc -static-libstdc++ \ + -Wl,--end-group \ + -I ${gourou-src}/utils \ + -I ${gourou-src}/include \ + -I ${pugixml-src}/src \ + -I ${nixpkgs.openssl.dev}/include \ + -I ${nixpkgs.curl.dev}/include \ + -I ${nixpkgs.zlib.dev}/include \ + -I ${self.libzip-static}/include + '' ]; + }; + defaultPackage.x86_64-linux = self.knock; }; } \ No newline at end of file diff --git a/lib/handle_aax.py b/lib/handle_aax.py deleted file mode 100644 index fe91e5f..0000000 --- a/lib/handle_aax.py +++ /dev/null @@ -1,51 +0,0 @@ -from xdg import xdg_config_home -from utils import open_fake_terminal, close_fake_terminal, run -import audible, click, sys - -def handle_aax(aax_path): - authcode_path = xdg_config_home().joinpath('knock', 'aax', 'authcode') - - # make the config dir if it doesn't exist - authcode_path.parent.mkdir(parents=True, exist_ok=True) - - m4b_path = aax_path.with_suffix('.m4b') - - if m4b_path.exists(): - click.echo(f"Error: {m4b_path} must be moved out of the way or deleted.", err=True) - sys.exit(1) - - if not authcode_path.exists(): - click.echo('This device does not have an Audible decryption key.') - - email = click.prompt("Enter your Audible account's email address") - password = click.prompt("Enter your Audible account's password", hide_input=True) - locale = click.prompt("Enter your locale (e.g. 'us', 'ca', 'jp', etc)") - - open_fake_terminal(f'audible.auth.Authenticator.from_login("{email}", "{locale}").get_activation_bytes()') - - try: - authcode = audible.auth.Authenticator.from_login( - username=email, - password=password, - locale=locale - ).get_activation_bytes() - authcode_path.write_text(authcode) - except Exception as error: - click.echo(error, err=True) - close_fake_terminal(1) - - close_fake_terminal(0) - - click.echo('Decrypting the file...') - - authcode = authcode_path.read_text() - - run([ - 'ffmpeg', - '-activation_bytes', authcode, - '-i', str(aax_path), - '-c', 'copy', str(m4b_path), - '-loglevel', 'error' - ]) - - click.secho(f'DRM-free M4B file created:\n{m4b_path}', fg='green') diff --git a/lib/handle_acsm.py b/lib/handle_acsm.py deleted file mode 100644 index defc68b..0000000 --- a/lib/handle_acsm.py +++ /dev/null @@ -1,77 +0,0 @@ -from xdg import xdg_config_home -import click, sys, shutil, subprocess, magic -from utils import run - -def handle_acsm(acsm_path): - drm_path = acsm_path.with_suffix('.drm') - adobe_dir = xdg_config_home().joinpath('knock', 'acsm') - adobe_dir.mkdir(parents=True, exist_ok=True) - - if drm_path.exists(): - click.echo(f"Error: {drm_path} must be moved out of the way or deleted.", err=True) - sys.exit(1) - - if ( - not adobe_dir.joinpath('device.xml').exists() - or not adobe_dir.joinpath('activation.xml').exists() - or not adobe_dir.joinpath('devicesalt').exists() - ): - shutil.rmtree(str(adobe_dir)) - click.echo('This device is not registered with Adobe.') - email = click.prompt("Enter your Adobe account's email address") - password = click.prompt("Enter your Adobe account's password", hide_input=True) - click.echo('Registering this device with Adobe...') - - run( - [ - 'adept_activate', - '-u', email, - '-O', str(adobe_dir) - ], - stdin=password+'\n', - cleanser=lambda:shutil.rmtree(str(adobe_dir)) - ) - - click.echo('Downloading the book from Adobe...') - - run([ - 'acsmdownloader', - '-d', str(adobe_dir.joinpath('device.xml')), - '-a', str(adobe_dir.joinpath('activation.xml')), - '-k', str(adobe_dir.joinpath('devicesalt')), - '-o', str(drm_path), - '-f', str(acsm_path) - ]) - - drm_path_type = magic.from_file(str(drm_path), mime=True) - if drm_path_type == 'application/epub+zip': - file_type = 'epub' - elif drm_path_type == 'application/pdf': - file_type = 'pdf' - else: - click.echo(f'Error: Received file of media type {drm_path_type} from Adobe\' servers.', err=True) - click.echo('Only the following ACSM conversions are currently supported:', err=True) - click.echo(' * ACSM -> EPUB', err=True) - click.echo(' * ACSM -> PDF', err=True) - click.echo('Please open a feature request at:', err=True) - click.echo(f' https://github.com/BentonEdmondson/knock/issues/new?title=Support%20{drm_path_type}%20Files&labels=enhancement', err=True) - sys.exit(1) - - output_path = acsm_path.with_suffix('.' + file_type) - if output_path.exists(): - drm_path.unlink() - click.echo(f"Error: {output_path} must be moved out of the way or deleted.", err=True) - sys.exit(1) - - click.echo('Decrypting the file...') - - run([ - 'rmdrm-' + file_type, - str(adobe_dir.joinpath('activation.xml')), - str(drm_path), - str(output_path) - ]) - - drm_path.unlink() - - click.secho(f'DRM-free {file_type.upper()} file created:\n{output_path}', fg='green') \ No newline at end of file diff --git a/lib/utils.py b/lib/utils.py deleted file mode 100644 index 9551d43..0000000 --- a/lib/utils.py +++ /dev/null @@ -1,45 +0,0 @@ -import click, subprocess, sys - -# run a command and display output in a styled terminal -# cleanser is called if the command returns a >0 exit code -def run(command: [str], stdin: str = '', cleanser = lambda: None) -> int: - - open_fake_terminal(' '.join(command)) - - result = subprocess.run( - command, - stderr=subprocess.STDOUT, - input=stdin.encode(), - check=False # don't throw Python error if returncode isn't 0 - ) - - close_fake_terminal(result.returncode, cleanser) - - return result.returncode - - -def open_fake_terminal(command: str): - click.secho('', fg='white', bg='black', bold=True, reset=False) - - # show command - click.echo(f'knock> {command}') - - # remove bold - click.secho('', fg='white', bg='black', bold=False, reset=False) - - -def close_fake_terminal(exit_code: int, cleanser = lambda: None): - click.secho(f'\nknock[{exit_code}]>', bold=True) - - # newline - click.echo('') - - if exit_code > 0: - cleanser() - click.echo(f'Error: Command returned error code {exit_code}.', err=True) - sys.exit(1) - -def verify_absence_of(file_path): - if m4b_path.exists(): - click.echo(f"Error: {file_path} must be moved out of the way or deleted.", err=True) - sys.exit(1) \ No newline at end of file diff --git a/readme.md b/readme.md index e1a2073..8f1b8b1 100644 --- a/readme.md +++ b/readme.md @@ -1,54 +1,20 @@ # Knock -Perform the following conversions with one command: -* ACSM → EPUB -* ACSM → PDF -* (Soon: AAX → M4B) - -![CLI demonstration](demo.png) +Convert ACSM files to PDF/EPUBs with one command on Linux ([and MacOS very soon](https://github.com/BentonEdmondson/knock/issues/58)). *This software does not utilize Adobe Digital Editions nor Wine. It is completely free and open-source software written natively for Linux.* -## Setup and Installation +## Installation -* For NixOS users, include this flake in your system `flake.nix`. Then run `knock ~/path/to/my-book.acsm` to use. - ```nix - { - inputs.knock.url = "github:BentonEdmondson/knock"; - outputs = { self, knock }: { /* knock.defaultPackage.x86_64-linux is the package */ }; - } - ``` -* For non-NixOS, use the latest [release](https://github.com/BentonEdmondson/knock/releases). It is large because it includes all dependencies, allowing it to run on any system with an x86_64 Linux kernel. It was built using [`nix bundle`](https://nixos.org/manual/nix/unstable/command-ref/new-cli/nix3-bundle.html). Use it by doing the following: - 1. Download `knock-version-x86_64-linux` and open a terminal - 1. Navigate to the folder within which `knock-version-x86_64-linux` resides (e.g. `cd ~/Downloads`) - 1. Run `mv knock-version-x86_64-linux knock` to rename it to `knock` - 1. Run `chmod +x knock` to make it executable - 1. Run `./knock ~/path/to/my-book.acsm` to convert the ebook - - If you receive an error that says something like `./nix/store/...: not found` or `./nix/store/...: No such file or directory` then you might not have user namespaces enabled. Try running the following to fix it: - - ``` - echo "kernel.unprivileged_userns_clone=1" >> /etc/sysctl.conf - sudo reboot - ``` - If you receive an error that says something like `E_AUTH_FAILED http://adeactivate.adobe.com/adept/SignInDirect xxxx@xxxxxxxx.com CUS05051` then you might have over (at least) 10 digit password for Adobe. Try changing it to 10 digit password and try the command again. - - 1. Optionally move the executable to `~/bin` (for your user) or `/usr/local/bin/` (for all users) to allow it to run from anywhere (might not work on some distributions) - -## Recommended Workflows - -Before buying your ebook/audiobook, check if it is available for free on [Project Gutenberg](https://gutenberg.org/) (ebooks) or [LibriVox](https://librivox.org/) (audiobooks). - -If you're looking for an ebook reader or audiobook player, I recommend [Foliate](https://johnfactotum.github.io/foliate/) for the former and [Cozy](https://cozy.sh/) for the latter. +* Download the latest [release](https://github.com/BentonEdmondson/knock/releases). Make sure it is the correct version for your architecture (run `uname -m` to check). +* Rename the binary and make it executable. +* Run `knock /path/to/book.acsm` to perform the conversion. ## Verified Book Sources -Knock should work on any ACSM file, but it has been specifically verified to work on ACSM files from the following: +Knock should work on any ACSM file, but it has been specifically verified to work on ACSM files purchased [eBooks.com](https://www.ebooks.com/en-us/) and [Kobo](https://www.kobo.com/us/en), among others. -* [eBooks.com](https://www.ebooks.com/en-us/) -* [Rakuten Kobo](https://www.kobo.com/us/en) -* [Google Books](https://books.google.com/) -* [Hugendubel.de](https://www.hugendubel.de/de/) (German) +Before buying your ebook, check if it is available for free on [Project Gutenberg](https://gutenberg.org/). ## The Name @@ -65,13 +31,26 @@ The name comes from the [D&D 5e spell](https://roll20.net/compendium/dnd5e/Knock ## Dependencies -* [`libgourou`](http://indefero.soutade.fr/p/libgourou/) for using the ACSM file to download the corresponding encrypted EPUB/PDF file from Adobe's servers -* [`rmdrm`](https://github.com/BentonEdmondson/rmdrm/) for decrypting the Adobe ADEPT-encrypted EPUB/PDF files -* [`Audible`](https://github.com/mkb79/Audible) for fetching the Audible decryption key used to decrypt AAX files -* [`ffmpeg`](https://www.ffmpeg.org/) for converting AAX files to M4B files using the Audible decryption key +There are no userspace runtime dependencies. -These are already included in all releases and in the Nix flake of course. +## Building & Contributing + +Install [Nix](https://github.com/NixOS/nix) if you don't have it. [Enable flakes](https://nixos.wiki/wiki/Flakes) if you haven't. Run + +``` +nix build +``` + +to build and + +``` +nix flake update +``` + +to update libraries. + +Test books can be found [here](https://www.adobe.com/solutions/ebook/digital-editions/sample-ebook-library.html). ## License -This software is licensed under GPLv3. +This software is licensed under GPLv3. The linked libraries have various licenses. diff --git a/src/knock.cpp b/src/knock.cpp new file mode 100644 index 0000000..5017c9e --- /dev/null +++ b/src/knock.cpp @@ -0,0 +1,104 @@ +#include +#include "drmprocessorclientimpl.h" +#include "libgourou_common.h" +#include "libgourou.h" + +std::string get_data_dir(); +void verify_absence(std::string file); +void verify_presence(std::string file); + +int main(int argc, char** argv) try { + + if (argc != 2) { + throw std::invalid_argument("the ACSM file must be passed as an argument"); + } + + const std::string acsm_file = argv[1]; + verify_presence(acsm_file); + const std::string acsm_stem = acsm_file.substr(0, acsm_file.find_last_of(".")); + const std::string drm_file = acsm_stem + ".drm"; + const std::string out_file = acsm_stem + ".out"; + verify_absence(drm_file); + verify_absence(out_file); + const std::string knock_data = get_data_dir(); + + DRMProcessorClientImpl client; + gourou::DRMProcessor* processor = gourou::DRMProcessor::createDRMProcessor( + &client, + false, // don't "always generate a new device" (default) + knock_data + ); + + processor->signIn("anonymous", ""); + processor->activateDevice(); + + std::cout << "downloading the file from Adobe..." << std::endl; + gourou::FulfillmentItem* item = processor->fulfill(acsm_file); + gourou::DRMProcessor::ITEM_TYPE type = processor->download(item, drm_file); + + std::cout << "removing DRM from the file..." << std::endl; + std::string ext_file; + std::string file_type; + switch (type) { + case gourou::DRMProcessor::ITEM_TYPE::PDF: { + // for pdfs the function moves the pdf while removing drm + processor->removeDRM(drm_file, out_file, type, nullptr, 0); + std::filesystem::remove(drm_file); + ext_file = acsm_stem + ".pdf"; + file_type = "PDF"; + break; + } + case gourou::DRMProcessor::ITEM_TYPE::EPUB: { + // for epubs the drm is removed in-place so in == out + processor->removeDRM(drm_file, drm_file, type, nullptr, 0); + std::filesystem::rename(drm_file, out_file); + ext_file = acsm_stem + ".epub"; + file_type = "EPUB"; + break; + } + } + + if (std::filesystem::exists(ext_file)) { + std::cerr + << "warning: failed to update file extension; " + ext_file + " already exists" + << std::endl; + ext_file = out_file; + } else { + std::filesystem::rename(out_file, ext_file); + } + + std::cout << "DRM-free " + file_type + " file generated at " + ext_file << std::endl; + + return 0; + +} catch (const gourou::Exception& e) { + std::cerr << "error:\n" << e.what(); + return EXIT_FAILURE; +} catch (const std::exception& e) { + std::cerr << "error: " << e.what() << std::endl; + return EXIT_FAILURE; +} + +std::string get_data_dir() { + char* xdg_data_home = std::getenv("XDG_DATA_HOME"); + std::string knock_data; + if (xdg_data_home != nullptr) { + knock_data = xdg_data_home; + } else { + knock_data = std::string(std::getenv("HOME")) + "/.local/share"; + } + knock_data += "/knock/acsm"; + return knock_data; +} + +void verify_absence(std::string file) { + if (std::filesystem::exists(file)) { + throw std::runtime_error("file " + file + " must be moved out of the way or deleted"); + } +} + +void verify_presence(std::string file) { + if (!std::filesystem::exists(file)) { + throw std::runtime_error("file " + file + " does not exist"); + } +} \ No newline at end of file diff --git a/src/knock.py b/src/knock.py deleted file mode 100755 index 274149a..0000000 --- a/src/knock.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python3 - -import subprocess, shutil, click -from pathlib import Path -from getpass import getpass -from xdg import xdg_config_home - -from handle_acsm import handle_acsm -from handle_aax import handle_aax - -@click.command() -@click.argument( - "path", - type=click.Path( - exists=True, - file_okay=True, - dir_okay=False, - readable=True, - resolve_path=True - ) -) -def main(path): - path = Path(path) - - # make the config dir if it doesn't exist - xdg_config_home().joinpath('knock').mkdir(parents=True, exist_ok=True) - - path_type = path.suffix[1:].upper() - - if path_type == 'ACSM': - click.echo('Received an ACSM (Adobe) file...') - handle_acsm(path) - #elif path_type == 'AAX': - # click.echo('Received an AAX (Audible) file...') - # handle_aax(path) - else: - click.echo(f'Error: Files of type {path_type} are not supported.\n', err=True) - click.echo('Only the following file types are currently supported:', err=True) - click.echo(' * ACSM (Adobe)\n', err=True) - #click.echo(' * AAX (Audible)\n', err=True) - click.echo('Please open a feature request at:', err=True) - click.echo(f' https://github.com/BentonEdmondson/knock/issues/new?title=Support%20{path_type}%20Files&labels=enhancement', err=True) - sys.exit(1) - -if __name__ == "__main__": - main() \ No newline at end of file