mirror of
https://github.com/noDRM/DeDRM_tools.git
synced 2026-03-20 13:08:55 +00:00
Compare commits
107 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed412bee35 | ||
|
|
6cee615f26 | ||
|
|
c4581b4d72 | ||
|
|
f6a568bcc1 | ||
|
|
bf6170e613 | ||
|
|
afcd79c0cc | ||
|
|
fdf0389936 | ||
|
|
5599c1694b | ||
|
|
dff90fae6f | ||
|
|
d33f679eae | ||
|
|
225e74a334 | ||
|
|
13e9a14907 | ||
|
|
92ea0a2f24 | ||
|
|
a1059650f6 | ||
|
|
a3cc221932 | ||
|
|
6732be1434 | ||
|
|
ad5cb056f0 | ||
|
|
d3c7388327 | ||
|
|
8e436ad920 | ||
|
|
ae806f734e | ||
|
|
ccfa454226 | ||
|
|
6716db1f62 | ||
|
|
0e0d7d8b14 | ||
|
|
981aadc497 | ||
|
|
26eb5d676c | ||
|
|
036f9007fd | ||
|
|
bdd1c2e474 | ||
|
|
54a58d05a5 | ||
|
|
f9d9b6016f | ||
|
|
131cea1215 | ||
|
|
731eeac087 | ||
|
|
b8b324956c | ||
|
|
1955b34883 | ||
|
|
dbc5c2b4de | ||
|
|
856fef55be | ||
|
|
f2fa0426b7 | ||
|
|
c3376cc492 | ||
|
|
dc72c368a5 | ||
|
|
77033e1602 | ||
|
|
15cd372ad9 | ||
|
|
c52e4db3df | ||
|
|
45038cc77b | ||
|
|
5ec9c98a0b | ||
|
|
66bab7bd7d | ||
|
|
e0c7d7d382 | ||
|
|
f12a4f3856 | ||
|
|
87881659c4 | ||
|
|
dbc7f26097 | ||
|
|
c58e82d97f | ||
|
|
74bcf33591 | ||
|
|
a1703e15d4 | ||
|
|
591448d1f5 | ||
|
|
a74f37c79e | ||
|
|
7f4e6698ef | ||
|
|
e2e19fb50f | ||
|
|
4a319a3522 | ||
|
|
f1ef1b8ecd | ||
|
|
af0acf31a3 | ||
|
|
6dd022e6a0 | ||
|
|
ef59e112c1 | ||
|
|
019abecd05 | ||
|
|
7b3bbbd008 | ||
|
|
32968b1328 | ||
|
|
e0ec691dd6 | ||
|
|
0add3646d9 | ||
|
|
16024ee972 | ||
|
|
9cfe09e507 | ||
|
|
4a58d6f7dc | ||
|
|
c4c20eb07e | ||
|
|
cc33f40ecc | ||
|
|
939cdbb0c9 | ||
|
|
dc27c36761 | ||
|
|
7262264b95 | ||
|
|
4b160132a5 | ||
|
|
85fb4ff729 | ||
|
|
608bd400ee | ||
|
|
781268e17e | ||
|
|
41d3da12ec | ||
|
|
83139bc590 | ||
|
|
e31752e334 | ||
|
|
2eb31c8fb5 | ||
|
|
a3c7bad67e | ||
|
|
dca0cf7d00 | ||
|
|
62e0a69089 | ||
|
|
9df1563492 | ||
|
|
971db9ae71 | ||
|
|
cf829db532 | ||
|
|
80c8bd2d24 | ||
|
|
969599ce6b | ||
|
|
f55420bbf4 | ||
|
|
7f758566d3 | ||
|
|
ff8d44492e | ||
|
|
21d4811bfe | ||
|
|
558efebbff | ||
|
|
1eaee6a0a8 | ||
|
|
3f644ddfd6 | ||
|
|
08bdacf476 | ||
|
|
109261bdc0 | ||
|
|
de50a02af9 | ||
|
|
6920f79a26 | ||
|
|
2800f7cd80 | ||
|
|
61c5096da0 | ||
|
|
9118ce77ab | ||
|
|
c3aa1b62bb | ||
|
|
afa4ac5716 | ||
|
|
c516306858 | ||
|
|
e76bb408a3 |
33
.github/ISSUE_TEMPLATE/QUESTION.md
vendored
Normal file
33
.github/ISSUE_TEMPLATE/QUESTION.md
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: Question
|
||||
about: Questions for DeDRM Project
|
||||
title: "[QUESTION] Title"
|
||||
labels: Question
|
||||
---
|
||||
|
||||
## CheckList
|
||||
<!-- Check with `[x]` -->
|
||||
- [ ] `The Title` and The `Log Title` are setted correctly.
|
||||
- [ ] Clarified about `my environment`.
|
||||
- [ ] Code block is used for `the log`.
|
||||
<!-- If you don't know the version, please specify 'Unknown'. -->
|
||||
<!-- In case of markdown To use the code block, enclose it in ```. -->
|
||||
<!-- If you don't need Log, please delete the log section. -->
|
||||
|
||||
---
|
||||
|
||||
## Title
|
||||
<!-- content -->
|
||||
|
||||
## My Environment
|
||||
### Calibre: `Version`
|
||||
### Kindle: `Version`
|
||||
### DeDRM: `Version`
|
||||
|
||||
## Log
|
||||
<details><summary>Log Title</summary>
|
||||
|
||||
```log
|
||||
PUT YOUR LOG
|
||||
```
|
||||
</details>
|
||||
39
.github/workflows/Format.yaml
vendored
Normal file
39
.github/workflows/Format.yaml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: Python code format
|
||||
on:
|
||||
push:
|
||||
branches: master
|
||||
jobs:
|
||||
Format:
|
||||
if: "contains(github.event.head_commit.message, '!format')"
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@main
|
||||
with:
|
||||
python-version: 3.x
|
||||
- uses: actions/cache@main
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-format
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-format
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install autopep8 pycodestyle
|
||||
- name: Format by autopep8 then Push
|
||||
env:
|
||||
GIT_EMAIL: github-actions[bot]@users.noreply.github.com
|
||||
GIT_ACTOR: github-actions[bot]
|
||||
run: |
|
||||
export HASH_SHORT=$(git rev-parse --short HEAD)
|
||||
git checkout -b format--${HASH_SHORT}
|
||||
git config --global user.email $GIT_EMAIL
|
||||
git config --global user.name $GIT_ACTOR
|
||||
python -m autopep8 --in-place --aggressive --aggressive --experimental -r ./
|
||||
git add -A
|
||||
git commit -m 'Format by autopep8' -m From: -m $(git rev-parse HEAD)
|
||||
git push --set-upstream origin format--${HASH_SHORT}
|
||||
26
.github/workflows/Lint.yaml
vendored
Normal file
26
.github/workflows/Lint.yaml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Python code review
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
Test:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@main
|
||||
with:
|
||||
python-version: 3.x
|
||||
- uses: actions/cache@main
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-lint
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-lint
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install flake8
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
python -m flake8 . --builtins=_,I --ignore=E501 --count --benchmark --show-source --statistics
|
||||
35
.github/workflows/Python_tests.yml
vendored
35
.github/workflows/Python_tests.yml
vendored
@@ -1,35 +0,0 @@
|
||||
name: Python_tests
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
Python_tests:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest]
|
||||
python-version: [2.7] # , 3.8]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install flake8 pytest # -r requirements.txt
|
||||
#- name: Check formatting with black
|
||||
# if: matrix.python-version == '3.8'
|
||||
# run: |
|
||||
# pip install black
|
||||
# black --check .
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
flake8 . --builtins=_,I --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||
flake8 . --builtins=_,I --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
#- name: Test with pytest
|
||||
# run: pytest
|
||||
#- name: Run doctests with pytest
|
||||
# run: pytest --doctest-modules
|
||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.DS_Store
|
||||
@@ -27,7 +27,7 @@ platforms.
|
||||
|
||||
#### Enter your keys
|
||||
- Figure out what format DeDRM wants your key in by looking in
|
||||
[the code that handles that](dedrm_src/prefs.py).
|
||||
[the code that handles that](DeDRM_plugin/prefs.py).
|
||||
- For Kindle eInk devices, DeDRM expects you to put a list of serial
|
||||
numbers in the `serials` field: `"serials": ["012345689abcdef"]` or
|
||||
`"serials": ["1111111111111111", "2222222222222222"]`.
|
||||
|
||||
@@ -38,7 +38,7 @@ li {margin-top: 0.5em}
|
||||
|
||||
<h3>Renaming Keys:</h3>
|
||||
|
||||
<p>On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a sheet of paper. Clicking this button will promt you to enter a new name for the highlighted key in the list. Enter the new name for the encryption key and click the OK button to use the new name, or Cancel to revert to the old name..</p>
|
||||
<p>On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a sheet of paper. Clicking this button will prompt you to enter a new name for the highlighted key in the list. Enter the new name for the encryption key and click the OK button to use the new name, or Cancel to revert to the old name..</p>
|
||||
|
||||
<h3>Exporting Keys:</h3>
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ li {margin-top: 0.5em}
|
||||
|
||||
<h3>Renaming Keys:</h3>
|
||||
|
||||
<p>On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a sheet of paper. Clicking this button will promt you to enter a new name for the highlighted key in the list. Enter the new name for the encryption key and click the OK button to use the new name, or Cancel to revert to the old name..</p>
|
||||
<p>On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a sheet of paper. Clicking this button will prompt you to enter a new name for the highlighted key in the list. Enter the new name for the encryption key and click the OK button to use the new name, or Cancel to revert to the old name..</p>
|
||||
|
||||
<h3>Exporting Keys:</h3>
|
||||
|
||||
@@ -56,7 +56,7 @@ li {margin-top: 0.5em}
|
||||
|
||||
<p>At the bottom-left of the plugin’s customization dialog, you will see a button labeled "Import Existing Keyfiles". Use this button to import existing ‘.b64’ key files. Key files might come from being exported from this or older plugins, or may have been generated using the original i♥cabbages script, or you may have made it by following the instructions above.</p>
|
||||
|
||||
<p>Once done creating/deleting/renaming/importing decryption keys, click Close to exit the customization dialogue. Your changes wil only be saved permanently when you click OK in the main configuration dialog.</p>
|
||||
<p>Once done creating/deleting/renaming/importing decryption keys, click Close to exit the customization dialogue. Your changes will only be saved permanently when you click OK in the main configuration dialog.</p>
|
||||
|
||||
<h3>NOOK Study</h3>
|
||||
<p>Books downloaded through NOOK Study may or may not use the key found using the above method. If a book is not decrypted successfully with any of the keys, the plugin will attempt to recover keys from the NOOK Study log file and use them.</p>
|
||||
|
||||
@@ -36,7 +36,7 @@ li {margin-top: 0.5em}
|
||||
|
||||
<p>On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a red "X". Clicking this button will delete the highlighted Kindle serial number from the list. You will be prompted once to be sure that’s what you truly mean to do. Once gone, it’s permanently gone.</p>
|
||||
|
||||
<p>Once done creating/deleting serial numbers, click Close to exit the customization dialogue. Your changes wil only be saved permanently when you click OK in the main configuration dialog.</p>
|
||||
<p>Once done creating/deleting serial numbers, click Close to exit the customization dialogue. Your changes will only be saved permanently when you click OK in the main configuration dialog.</p>
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ li {margin-top: 0.5em}
|
||||
|
||||
<h3>Renaming Keys:</h3>
|
||||
|
||||
<p>On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a sheet of paper. Clicking this button will promt you to enter a new name for the highlighted key in the list. Enter the new name for the encryption key and click the OK button to use the new name, or Cancel to revert to the old name..</p>
|
||||
<p>On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a sheet of paper. Clicking this button will prompt you to enter a new name for the highlighted key in the list. Enter the new name for the encryption key and click the OK button to use the new name, or Cancel to revert to the old name..</p>
|
||||
|
||||
<h3>Exporting Keys:</h3>
|
||||
|
||||
@@ -46,7 +46,7 @@ li {margin-top: 0.5em}
|
||||
|
||||
<h3>Linux Users: WINEPREFIX</h3>
|
||||
|
||||
<p>Under the list of keys, Linux users will see a text field labeled "WINEPREFIX". If you are use Kindle for PC under Wine, and your wine installation containing Kindle for PC isn't the default Wine installation, you may enter the full path to the correct Wine installation here. Leave blank if you are unsure.</p>
|
||||
<p>Under the list of keys, Linux users will see a text field labeled "WINEPREFIX". If you are using the Kindle for PC under Wine, and your wine installation containing Kindle for PC isn't the default Wine installation, you may enter the full path to the correct Wine installation here. Leave blank if you are unsure.</p>
|
||||
|
||||
<h3>Importing Existing Keyfiles:</h3>
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# __init__.py for DeDRM_plugin
|
||||
# Copyright © 2008-2020 Apprentice Harper et al.
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '6.8.1'
|
||||
__version__ = '7.0.2'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
@@ -71,18 +69,22 @@ __docformat__ = 'restructuredtext en'
|
||||
# 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 Syr
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Decrypt DRMed ebooks.
|
||||
"""
|
||||
|
||||
PLUGIN_NAME = u"DeDRM"
|
||||
PLUGIN_VERSION_TUPLE = (6, 8, 0)
|
||||
PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE])
|
||||
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
|
||||
@@ -108,11 +110,11 @@ class SafeUnbuffered:
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
if isinstance(data,str):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
try:
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
self.stream.buffer.write(data)
|
||||
self.stream.buffer.flush()
|
||||
except:
|
||||
# We can do nothing if a write fails
|
||||
pass
|
||||
@@ -121,13 +123,14 @@ class SafeUnbuffered:
|
||||
|
||||
class DeDRM(FileTypePlugin):
|
||||
name = PLUGIN_NAME
|
||||
description = u"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."
|
||||
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 = u"Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
|
||||
author = "Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
|
||||
version = PLUGIN_VERSION_TUPLE
|
||||
minimum_calibre_version = (1, 0, 0) # Compiled python libraries cannot be imported in earlier versions.
|
||||
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
|
||||
|
||||
|
||||
@@ -144,29 +147,29 @@ class DeDRM(FileTypePlugin):
|
||||
Also perform upgrade of preferences once per version
|
||||
"""
|
||||
try:
|
||||
self.pluginsdir = os.path.join(config_dir,u"plugins")
|
||||
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,u"DeDRM")
|
||||
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,u"help")
|
||||
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,u"libraryfiles")
|
||||
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 = [u"alfcrypto.dll",u"alfcrypto64.dll"]
|
||||
names = ["alfcrypto.dll","alfcrypto64.dll"]
|
||||
elif isosx:
|
||||
names = [u"libalfcrypto.dylib"]
|
||||
names = ["libalfcrypto.dylib"]
|
||||
else:
|
||||
names = [u"libalfcrypto32.so",u"libalfcrypto64.so",u"kindlekey.py",u"adobekey.py",u"subasyncio.py"]
|
||||
names = ["libalfcrypto32.so","libalfcrypto64.so","kindlekey.py","adobekey.py","subasyncio.py"]
|
||||
lib_dict = self.load_resources(names)
|
||||
print u"{0} v{1}: Copying needed library files from plugin's zip".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
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)
|
||||
@@ -178,7 +181,7 @@ class DeDRM(FileTypePlugin):
|
||||
try:
|
||||
open(file_path,'wb').write(data)
|
||||
except:
|
||||
print u"{0} v{1}: Exception when copying needed library files".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
print("{0} v{1}: Exception when copying needed library files".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
traceback.print_exc()
|
||||
pass
|
||||
|
||||
@@ -188,7 +191,7 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
# mark that this version has been initialized
|
||||
os.mkdir(self.verdir)
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
raise
|
||||
|
||||
@@ -197,13 +200,13 @@ class DeDRM(FileTypePlugin):
|
||||
# Check original epub archive for zip errors.
|
||||
import calibre_plugins.dedrm.zipfix
|
||||
|
||||
inf = self.temporary_file(u".epub")
|
||||
inf = self.temporary_file(".epub")
|
||||
try:
|
||||
print u"{0} v{1}: Verifying zip archive integrity".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
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, e:
|
||||
print u"{0} v{1}: Error \'{2}\' when checking zip archive".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
|
||||
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
|
||||
@@ -216,19 +219,19 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
#check the book
|
||||
if ignobleepub.ignobleBook(inf.name):
|
||||
print u"{0} v{1}: “{2}” is a secure Barnes & Noble ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
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 = u"".join((u'X' if (x.isdigit()) else x) for x in keyname)
|
||||
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked)
|
||||
of = self.temporary_file(u".epub")
|
||||
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 u"{0} v{1}: Exception when trying to decrypt after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
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
|
||||
|
||||
@@ -239,10 +242,10 @@ class DeDRM(FileTypePlugin):
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
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_masked,time.time()-self.starttime)
|
||||
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 u"{0} v{1}: Looking for new NOOK Study Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
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 = []
|
||||
@@ -253,13 +256,13 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
defaultkeys = nookkeys()
|
||||
else: # linux
|
||||
from wineutils import WineGetKeys
|
||||
from .wineutils import WineGetKeys
|
||||
|
||||
scriptpath = os.path.join(self.alfdir,u"ignoblekey.py")
|
||||
defaultkeys = WineGetKeys(scriptpath, u".b64",dedrmprefs['adobewineprefix'])
|
||||
scriptpath = os.path.join(self.alfdir,"ignoblekey.py")
|
||||
defaultkeys = WineGetKeys(scriptpath, ".b64",dedrmprefs['adobewineprefix'])
|
||||
|
||||
except:
|
||||
print u"{0} v{1}: Exception when getting default NOOK Study Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
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 = []
|
||||
@@ -270,15 +273,15 @@ class DeDRM(FileTypePlugin):
|
||||
if len(newkeys) > 0:
|
||||
try:
|
||||
for i,userkey in enumerate(newkeys):
|
||||
print u"{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
print("{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
|
||||
of = self.temporary_file(u".epub")
|
||||
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 u"{0} v{1}: Exception when trying to decrypt after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
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
|
||||
|
||||
@@ -287,59 +290,59 @@ class DeDRM(FileTypePlugin):
|
||||
if result == 0:
|
||||
# Decryption was a success
|
||||
# Store the new successful key in the defaults
|
||||
print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
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 u"{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
print("{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
||||
except:
|
||||
print u"{0} v{1}: Exception saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
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 u"{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, e:
|
||||
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 u"{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(u"{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))
|
||||
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 u"{0} v{1}: {2} is a secure Adobe Adept ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
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 = userkeyhex.decode('hex')
|
||||
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname)
|
||||
of = self.temporary_file(u".epub")
|
||||
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 u"{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
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 u"{0} v{1}: Exception closing temporary file after {2:.1f} seconds. Ignored.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
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 u"{0} v{1}: Decrypted with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)
|
||||
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 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)
|
||||
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 u"{0} v{1}: Looking for new default Adobe Digital Editions Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
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 = []
|
||||
@@ -350,33 +353,33 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
defaultkeys = adeptkeys()
|
||||
else: # linux
|
||||
from wineutils import WineGetKeys
|
||||
from .wineutils import WineGetKeys
|
||||
|
||||
scriptpath = os.path.join(self.alfdir,u"adobekey.py")
|
||||
defaultkeys = WineGetKeys(scriptpath, u".der",dedrmprefs['adobewineprefix'])
|
||||
scriptpath = os.path.join(self.alfdir,"adobekey.py")
|
||||
defaultkeys = WineGetKeys(scriptpath, ".der",dedrmprefs['adobewineprefix'])
|
||||
|
||||
self.default_key = defaultkeys[0]
|
||||
except:
|
||||
print u"{0} v{1}: Exception when getting default Adobe Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
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 = u""
|
||||
self.default_key = ""
|
||||
|
||||
newkeys = []
|
||||
for keyvalue in defaultkeys:
|
||||
if keyvalue.encode('hex') not in dedrmprefs['adeptkeys'].values():
|
||||
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 u"{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
of = self.temporary_file(u".epub")
|
||||
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 u"{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
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
|
||||
|
||||
@@ -385,32 +388,32 @@ class DeDRM(FileTypePlugin):
|
||||
if result == 0:
|
||||
# Decryption was a success
|
||||
# Store the new successful key in the defaults
|
||||
print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
print("{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
try:
|
||||
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex'))
|
||||
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',codecs.encode(keyvalue, 'hex').decode('ascii'))
|
||||
dedrmprefs.writeprefs()
|
||||
print u"{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
print("{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
||||
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)
|
||||
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 u"{0} v{1}: Decrypted with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
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 u"{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, e:
|
||||
print u"{0} v{1}: Unexpected Exception trying a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
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 u"{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(u"{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))
|
||||
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 u"{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(u"{0} v{1}: Couldn't decrypt after {2:.1f} seconds. DRM free perhaps?".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
||||
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
|
||||
@@ -418,17 +421,17 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
dedrmprefs = prefs.DeDRM_Prefs()
|
||||
# Attempt to decrypt epub with each encryption key (generated or provided).
|
||||
print u"{0} v{1}: {2} is a PDF ebook".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
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 = userkeyhex.decode('hex')
|
||||
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname)
|
||||
of = self.temporary_file(u".pdf")
|
||||
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 u"{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
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
|
||||
|
||||
@@ -439,10 +442,10 @@ class DeDRM(FileTypePlugin):
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
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)
|
||||
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 u"{0} v{1}: Looking for new default Adobe Digital Editions Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
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 = []
|
||||
@@ -453,33 +456,33 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
defaultkeys = adeptkeys()
|
||||
else: # linux
|
||||
from wineutils import WineGetKeys
|
||||
from .wineutils import WineGetKeys
|
||||
|
||||
scriptpath = os.path.join(self.alfdir,u"adobekey.py")
|
||||
defaultkeys = WineGetKeys(scriptpath, u".der",dedrmprefs['adobewineprefix'])
|
||||
scriptpath = os.path.join(self.alfdir,"adobekey.py")
|
||||
defaultkeys = WineGetKeys(scriptpath, ".der",dedrmprefs['adobewineprefix'])
|
||||
|
||||
self.default_key = defaultkeys[0]
|
||||
except:
|
||||
print u"{0} v{1}: Exception when getting default Adobe Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
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 = u""
|
||||
self.default_key = ""
|
||||
|
||||
newkeys = []
|
||||
for keyvalue in defaultkeys:
|
||||
if keyvalue.encode('hex') not in dedrmprefs['adeptkeys'].values():
|
||||
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 u"{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
of = self.temporary_file(u".pdf")
|
||||
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 u"{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
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
|
||||
|
||||
@@ -488,24 +491,24 @@ class DeDRM(FileTypePlugin):
|
||||
if result == 0:
|
||||
# Decryption was a success
|
||||
# Store the new successful key in the defaults
|
||||
print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
print("{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
try:
|
||||
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex'))
|
||||
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',codecs.encode(keyvalue,'hex'))
|
||||
dedrmprefs.writeprefs()
|
||||
print u"{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
print("{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
||||
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)
|
||||
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 u"{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, e:
|
||||
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 u"{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(u"{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))
|
||||
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):
|
||||
@@ -527,16 +530,16 @@ class DeDRM(FileTypePlugin):
|
||||
serials.extend(android_serials_list)
|
||||
#print serials
|
||||
androidFiles = []
|
||||
kindleDatabases = dedrmprefs['kindlekeys'].items()
|
||||
kindleDatabases = list(dedrmprefs['kindlekeys'].items())
|
||||
|
||||
try:
|
||||
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,kindleDatabases,androidFiles,serials,pids,self.starttime)
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
decoded = False
|
||||
# perhaps we need to get a new default Kindle for Mac/PC key
|
||||
defaultkeys = []
|
||||
print u"{0} v{1}: Failed to decrypt with error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION,e.args[0])
|
||||
print u"{0} v{1}: Looking for new default Kindle Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
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:
|
||||
@@ -544,36 +547,36 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
defaultkeys = kindlekeys()
|
||||
else: # linux
|
||||
from wineutils import WineGetKeys
|
||||
from .wineutils import WineGetKeys
|
||||
|
||||
scriptpath = os.path.join(self.alfdir,u"kindlekey.py")
|
||||
defaultkeys = WineGetKeys(scriptpath, u".k4i",dedrmprefs['kindlewineprefix'])
|
||||
scriptpath = os.path.join(self.alfdir,"kindlekey.py")
|
||||
defaultkeys = WineGetKeys(scriptpath, ".k4i",dedrmprefs['kindlewineprefix'])
|
||||
except:
|
||||
print u"{0} v{1}: Exception when getting default Kindle Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
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 = u"default_key_{0:d}".format(i+1)
|
||||
keyname = "default_key_{0:d}".format(i+1)
|
||||
if keyvalue not in dedrmprefs['kindlekeys'].values():
|
||||
newkeys[keyname] = keyvalue
|
||||
if len(newkeys) > 0:
|
||||
print u"{0} v{1}: Found {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")
|
||||
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,newkeys.items(),[],[],[],self.starttime)
|
||||
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,list(newkeys.items()),[],[],[],self.starttime)
|
||||
decoded = True
|
||||
# store the new successful keys in the defaults
|
||||
print u"{0} v{1}: Saving {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")
|
||||
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, e:
|
||||
except Exception as e:
|
||||
pass
|
||||
if not decoded:
|
||||
#if you reached here then no luck raise and exception
|
||||
print u"{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(u"{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))
|
||||
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)
|
||||
@@ -590,25 +593,25 @@ class DeDRM(FileTypePlugin):
|
||||
dedrmprefs = prefs.DeDRM_Prefs()
|
||||
# Attempt to decrypt epub with each encryption key (generated or provided).
|
||||
for keyname, userkey in dedrmprefs['ereaderkeys'].items():
|
||||
keyname_masked = u"".join((u'X' if (x.isdigit()) else x) for x in keyname)
|
||||
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked)
|
||||
of = self.temporary_file(u".pmlz")
|
||||
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, userkey.decode('hex'))
|
||||
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 u"{0} v{1}: Successfully decrypted with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime)
|
||||
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 u"{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}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime))
|
||||
|
||||
print u"{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(u"{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))
|
||||
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):
|
||||
@@ -617,7 +620,7 @@ class DeDRM(FileTypePlugin):
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
|
||||
print u"{0} v{1}: Trying to decrypt {2}".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
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:]
|
||||
@@ -636,9 +639,9 @@ class DeDRM(FileTypePlugin):
|
||||
# Adobe Adept or B&N ePub
|
||||
decrypted_ebook = self.ePubDecrypt(path_to_ebook)
|
||||
else:
|
||||
print u"Unknown booktype {0}. Passing back to calibre unchanged".format(booktype)
|
||||
print("Unknown booktype {0}. Passing back to calibre unchanged".format(booktype))
|
||||
return path_to_ebook
|
||||
print u"{0} v{1}: Finished after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
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):
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import sys
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkinter
|
||||
import tkinter.constants
|
||||
|
||||
class ActivityBar(Tkinter.Frame):
|
||||
class ActivityBar(tkinter.Frame):
|
||||
|
||||
def __init__(self, master, length=300, height=20, barwidth=15, interval=50, bg='white', fillcolor='orchid1',\
|
||||
bd=2, relief=Tkconstants.GROOVE, *args, **kw):
|
||||
Tkinter.Frame.__init__(self, master, bg=bg, width=length, height=height, *args, **kw)
|
||||
bd=2, relief=tkinter.constants.GROOVE, *args, **kw):
|
||||
tkinter.Frame.__init__(self, master, bg=bg, width=length, height=height, *args, **kw)
|
||||
self._master = master
|
||||
self._interval = interval
|
||||
self._maximum = length
|
||||
@@ -20,7 +20,7 @@ class ActivityBar(Tkinter.Frame):
|
||||
stopx = self._maximum
|
||||
# self._canv = Tkinter.Canvas(self, bg=self['bg'], width=self['width'], height=self['height'],\
|
||||
# highlightthickness=0, relief='flat', bd=0)
|
||||
self._canv = Tkinter.Canvas(self, bg=self['bg'], width=self['width'], height=self['height'],\
|
||||
self._canv = tkinter.Canvas(self, bg=self['bg'], width=self['width'], height=self['height'],\
|
||||
highlightthickness=0, relief=relief, bd=bd)
|
||||
self._canv.pack(fill='both', expand=1)
|
||||
self._rect = self._canv.create_rectangle(0, 0, self._canv.winfo_reqwidth(), self._canv.winfo_reqheight(), fill=fillcolor, width=0)
|
||||
|
||||
@@ -1,32 +1,12 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# adobekey.pyw, version 6.0
|
||||
# Copyright © 2009-2010 i♥cabbages
|
||||
# Copyright © 2009-2020 i♥cabbages, Apprentice Harper et al.
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Modified 2010–2016 by several people
|
||||
|
||||
# Windows users: Before running this program, you must first install Python.
|
||||
# We recommend ActiveState Python 2.7.X for Windows (x86) from
|
||||
# http://www.activestate.com/activepython/downloads.
|
||||
# You must also install PyCrypto from
|
||||
# http://www.voidspace.org.uk/python/modules.shtml#pycrypto
|
||||
# (make certain to install the version for Python 2.7).
|
||||
# Then save this script file as adobekey.pyw and double-click on it to run it.
|
||||
# It will create a file named adobekey_1.der in in the same directory as the script.
|
||||
# This is your Adobe Digital Editions user key.
|
||||
#
|
||||
# Mac OS X users: Save this script file as adobekey.pyw. You can run this
|
||||
# program from the command line (python adobekey.pyw) or by double-clicking
|
||||
# it when it has been associated with PythonLauncher. It will create a file
|
||||
# named adobekey_1.der in the same directory as the script.
|
||||
# This is your Adobe Digital Editions user key.
|
||||
|
||||
# Revision history:
|
||||
# 1 - Initial release, for Adobe Digital Editions 1.7
|
||||
# 2 - Better algorithm for finding pLK; improved error handling
|
||||
@@ -48,16 +28,18 @@ from __future__ import with_statement
|
||||
# 5.8 - Added getkey interface for Windows DeDRM application
|
||||
# 5.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 6.0 - Work if TkInter is missing
|
||||
# 7.0 - Python 3 for calibre 5
|
||||
|
||||
"""
|
||||
Retrieve Adobe ADEPT user key.
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '6.0'
|
||||
__version__ = '7.0'
|
||||
|
||||
import sys, os, struct, getopt
|
||||
from base64 import b64decode
|
||||
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
@@ -69,10 +51,11 @@ class SafeUnbuffered:
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
if isinstance(data, str):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
self.stream.buffer.write(data)
|
||||
self.stream.buffer.flush()
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
@@ -110,15 +93,13 @@ def unicode_argv():
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
range(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"adobekey.py"]
|
||||
return ["adobekey.py"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = "utf-8"
|
||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||
argvencoding = sys.stdin.encoding or "utf-8"
|
||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||
|
||||
class ADEPTError(Exception):
|
||||
pass
|
||||
@@ -130,7 +111,7 @@ if iswindows:
|
||||
c_long, c_ulong
|
||||
|
||||
from ctypes.wintypes import LPVOID, DWORD, BOOL
|
||||
import _winreg as winreg
|
||||
import winreg
|
||||
|
||||
def _load_crypto_libcrypto():
|
||||
from ctypes.util import find_library
|
||||
@@ -168,7 +149,7 @@ if iswindows:
|
||||
raise ADEPTError('Failed to initialize AES key')
|
||||
def decrypt(self, data):
|
||||
out = create_string_buffer(len(data))
|
||||
iv = ("\x00" * self._blocksize)
|
||||
iv = (b"\x00" * self._blocksize)
|
||||
rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
|
||||
if rv == 0:
|
||||
raise ADEPTError('AES decryption failed')
|
||||
@@ -179,7 +160,7 @@ if iswindows:
|
||||
from Crypto.Cipher import AES as _AES
|
||||
class AES(object):
|
||||
def __init__(self, key):
|
||||
self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16)
|
||||
self._aes = _AES.new(key, _AES.MODE_CBC, b'\x00'*16)
|
||||
def decrypt(self, data):
|
||||
return self._aes.decrypt(data)
|
||||
return AES
|
||||
@@ -287,44 +268,44 @@ if iswindows:
|
||||
|
||||
if struct.calcsize("P") == 4:
|
||||
CPUID0_INSNS = (
|
||||
"\x53" # push %ebx
|
||||
"\x31\xc0" # xor %eax,%eax
|
||||
"\x0f\xa2" # cpuid
|
||||
"\x8b\x44\x24\x08" # mov 0x8(%esp),%eax
|
||||
"\x89\x18" # mov %ebx,0x0(%eax)
|
||||
"\x89\x50\x04" # mov %edx,0x4(%eax)
|
||||
"\x89\x48\x08" # mov %ecx,0x8(%eax)
|
||||
"\x5b" # pop %ebx
|
||||
"\xc3" # ret
|
||||
b"\x53" # push %ebx
|
||||
b"\x31\xc0" # xor %eax,%eax
|
||||
b"\x0f\xa2" # cpuid
|
||||
b"\x8b\x44\x24\x08" # mov 0x8(%esp),%eax
|
||||
b"\x89\x18" # mov %ebx,0x0(%eax)
|
||||
b"\x89\x50\x04" # mov %edx,0x4(%eax)
|
||||
b"\x89\x48\x08" # mov %ecx,0x8(%eax)
|
||||
b"\x5b" # pop %ebx
|
||||
b"\xc3" # ret
|
||||
)
|
||||
CPUID1_INSNS = (
|
||||
"\x53" # push %ebx
|
||||
"\x31\xc0" # xor %eax,%eax
|
||||
"\x40" # inc %eax
|
||||
"\x0f\xa2" # cpuid
|
||||
"\x5b" # pop %ebx
|
||||
"\xc3" # ret
|
||||
b"\x53" # push %ebx
|
||||
b"\x31\xc0" # xor %eax,%eax
|
||||
b"\x40" # inc %eax
|
||||
b"\x0f\xa2" # cpuid
|
||||
b"\x5b" # pop %ebx
|
||||
b"\xc3" # ret
|
||||
)
|
||||
else:
|
||||
CPUID0_INSNS = (
|
||||
"\x49\x89\xd8" # mov %rbx,%r8
|
||||
"\x49\x89\xc9" # mov %rcx,%r9
|
||||
"\x48\x31\xc0" # xor %rax,%rax
|
||||
"\x0f\xa2" # cpuid
|
||||
"\x4c\x89\xc8" # mov %r9,%rax
|
||||
"\x89\x18" # mov %ebx,0x0(%rax)
|
||||
"\x89\x50\x04" # mov %edx,0x4(%rax)
|
||||
"\x89\x48\x08" # mov %ecx,0x8(%rax)
|
||||
"\x4c\x89\xc3" # mov %r8,%rbx
|
||||
"\xc3" # retq
|
||||
b"\x49\x89\xd8" # mov %rbx,%r8
|
||||
b"\x49\x89\xc9" # mov %rcx,%r9
|
||||
b"\x48\x31\xc0" # xor %rax,%rax
|
||||
b"\x0f\xa2" # cpuid
|
||||
b"\x4c\x89\xc8" # mov %r9,%rax
|
||||
b"\x89\x18" # mov %ebx,0x0(%rax)
|
||||
b"\x89\x50\x04" # mov %edx,0x4(%rax)
|
||||
b"\x89\x48\x08" # mov %ecx,0x8(%rax)
|
||||
b"\x4c\x89\xc3" # mov %r8,%rbx
|
||||
b"\xc3" # retq
|
||||
)
|
||||
CPUID1_INSNS = (
|
||||
"\x53" # push %rbx
|
||||
"\x48\x31\xc0" # xor %rax,%rax
|
||||
"\x48\xff\xc0" # inc %rax
|
||||
"\x0f\xa2" # cpuid
|
||||
"\x5b" # pop %rbx
|
||||
"\xc3" # retq
|
||||
b"\x53" # push %rbx
|
||||
b"\x48\x31\xc0" # xor %rax,%rax
|
||||
b"\x48\xff\xc0" # inc %rax
|
||||
b"\x0f\xa2" # cpuid
|
||||
b"\x5b" # pop %rbx
|
||||
b"\xc3" # retq
|
||||
)
|
||||
|
||||
def cpuid0():
|
||||
@@ -383,7 +364,7 @@ if iswindows:
|
||||
plkroot = winreg.OpenKey(cuser, PRIVATE_LICENCE_KEY_PATH)
|
||||
except WindowsError:
|
||||
raise ADEPTError("Could not locate ADE activation")
|
||||
for i in xrange(0, 16):
|
||||
for i in range(0, 16):
|
||||
try:
|
||||
plkparent = winreg.OpenKey(plkroot, "%04d" % (i,))
|
||||
except WindowsError:
|
||||
@@ -391,7 +372,7 @@ if iswindows:
|
||||
ktype = winreg.QueryValueEx(plkparent, None)[0]
|
||||
if ktype != 'credentials':
|
||||
continue
|
||||
for j in xrange(0, 16):
|
||||
for j in range(0, 16):
|
||||
try:
|
||||
plkkey = winreg.OpenKey(plkparent, "%04d" % (j,))
|
||||
except WindowsError:
|
||||
@@ -400,15 +381,15 @@ if iswindows:
|
||||
if ktype != 'privateLicenseKey':
|
||||
continue
|
||||
userkey = winreg.QueryValueEx(plkkey, 'value')[0]
|
||||
userkey = userkey.decode('base64')
|
||||
userkey = b64decode(userkey)
|
||||
aes = AES(keykey)
|
||||
userkey = aes.decrypt(userkey)
|
||||
userkey = userkey[26:-ord(userkey[-1])]
|
||||
userkey = userkey[26:-ord(userkey[-1:])]
|
||||
#print "found key:",userkey.encode('hex')
|
||||
keys.append(userkey)
|
||||
if len(keys) == 0:
|
||||
raise ADEPTError('Could not locate privateLicenseKey')
|
||||
print(u"Found {0:d} keys".format(len(keys)))
|
||||
print("Found {0:d} keys".format(len(keys)))
|
||||
return keys
|
||||
|
||||
|
||||
@@ -428,12 +409,12 @@ elif isosx:
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p2 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
out1, out2 = p2.communicate()
|
||||
reslst = out1.split('\n')
|
||||
reslst = out1.split(b'\n')
|
||||
cnt = len(reslst)
|
||||
ActDatPath = "activation.dat"
|
||||
for j in xrange(cnt):
|
||||
ActDatPath = b"activation.dat"
|
||||
for j in range(cnt):
|
||||
resline = reslst[j]
|
||||
pp = resline.find('activation.dat')
|
||||
pp = resline.find(b'activation.dat')
|
||||
if pp >= 0:
|
||||
ActDatPath = resline
|
||||
break
|
||||
@@ -449,7 +430,7 @@ elif isosx:
|
||||
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
||||
expr = '//%s/%s' % (adept('credentials'), adept('privateLicenseKey'))
|
||||
userkey = tree.findtext(expr)
|
||||
userkey = userkey.decode('base64')
|
||||
userkey = b64decode(userkey)
|
||||
userkey = userkey[26:]
|
||||
return [userkey]
|
||||
|
||||
@@ -464,41 +445,41 @@ def getkey(outpath):
|
||||
if len(keys) > 0:
|
||||
if not os.path.isdir(outpath):
|
||||
outfile = outpath
|
||||
with file(outfile, 'wb') as keyfileout:
|
||||
with open(outfile, 'wb') as keyfileout:
|
||||
keyfileout.write(keys[0])
|
||||
print(u"Saved a key to {0}".format(outfile))
|
||||
print("Saved a key to {0}".format(outfile))
|
||||
else:
|
||||
keycount = 0
|
||||
for key in keys:
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(outpath,u"adobekey_{0:d}.der".format(keycount))
|
||||
outfile = os.path.join(outpath,"adobekey_{0:d}.der".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
with file(outfile, 'wb') as keyfileout:
|
||||
with open(outfile, 'wb') as keyfileout:
|
||||
keyfileout.write(key)
|
||||
print(u"Saved a key to {0}".format(outfile))
|
||||
print("Saved a key to {0}".format(outfile))
|
||||
return True
|
||||
return False
|
||||
|
||||
def usage(progname):
|
||||
print(u"Finds, decrypts and saves the default Adobe Adept encryption key(s).")
|
||||
print(u"Keys are saved to the current directory, or a specified output directory.")
|
||||
print(u"If a file name is passed instead of a directory, only the first key is saved, in that file.")
|
||||
print(u"Usage:")
|
||||
print(u" {0:s} [-h] [<outpath>]".format(progname))
|
||||
print("Finds, decrypts and saves the default Adobe Adept encryption key(s).")
|
||||
print("Keys are saved to the current directory, or a specified output directory.")
|
||||
print("If a file name is passed instead of a directory, only the first key is saved, in that file.")
|
||||
print("Usage:")
|
||||
print(" {0:s} [-h] [<outpath>]".format(progname))
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print(u"{0} v{1}\nCopyright © 2009-2013 i♥cabbages and Apprentice Alf".format(progname,__version__))
|
||||
print("{0} v{1}\nCopyright © 2009-2020 i♥cabbages, Apprentice Harper et al.".format(progname,__version__))
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], "h")
|
||||
except getopt.GetoptError, err:
|
||||
print(u"Error in options or arguments: {0}".format(err.args[0]))
|
||||
except getopt.GetoptError as err:
|
||||
print("Error in options or arguments: {0}".format(err.args[0]))
|
||||
usage(progname)
|
||||
sys.exit(2)
|
||||
|
||||
@@ -527,48 +508,48 @@ def cli_main():
|
||||
if len(keys) > 0:
|
||||
if not os.path.isdir(outpath):
|
||||
outfile = outpath
|
||||
with file(outfile, 'wb') as keyfileout:
|
||||
with open(outfile, 'wb') as keyfileout:
|
||||
keyfileout.write(keys[0])
|
||||
print(u"Saved a key to {0}".format(outfile))
|
||||
print("Saved a key to {0}".format(outfile))
|
||||
else:
|
||||
keycount = 0
|
||||
for key in keys:
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(outpath,u"adobekey_{0:d}.der".format(keycount))
|
||||
outfile = os.path.join(outpath,"adobekey_{0:d}.der".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
with file(outfile, 'wb') as keyfileout:
|
||||
with open(outfile, 'wb') as keyfileout:
|
||||
keyfileout.write(key)
|
||||
print(u"Saved a key to {0}".format(outfile))
|
||||
print("Saved a key to {0}".format(outfile))
|
||||
else:
|
||||
print(u"Could not retrieve Adobe Adept key.")
|
||||
print("Could not retrieve Adobe Adept key.")
|
||||
return 0
|
||||
|
||||
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import tkinter
|
||||
import tkinter.constants
|
||||
import tkinter.messagebox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class ExceptionDialog(Tkinter.Frame):
|
||||
class ExceptionDialog(tkinter.Frame):
|
||||
def __init__(self, root, text):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
label = Tkinter.Label(self, text=u"Unexpected error:",
|
||||
anchor=Tkconstants.W, justify=Tkconstants.LEFT)
|
||||
label.pack(fill=Tkconstants.X, expand=0)
|
||||
self.text = Tkinter.Text(self)
|
||||
self.text.pack(fill=Tkconstants.BOTH, expand=1)
|
||||
tkinter.Frame.__init__(self, root, border=5)
|
||||
label = tkinter.Label(self, text="Unexpected error:",
|
||||
anchor=tkinter.constants.W, justify=tkinter.constants.LEFT)
|
||||
label.pack(fill=tkinter.constants.X, expand=0)
|
||||
self.text = tkinter.Text(self)
|
||||
self.text.pack(fill=tkinter.constants.BOTH, expand=1)
|
||||
|
||||
self.text.insert(Tkconstants.END, text)
|
||||
self.text.insert(tkinter.constants.END, text)
|
||||
|
||||
|
||||
argv=unicode_argv()
|
||||
root = Tkinter.Tk()
|
||||
root = tkinter.Tk()
|
||||
root.withdraw()
|
||||
progpath, progname = os.path.split(argv[0])
|
||||
success = False
|
||||
@@ -578,21 +559,21 @@ def gui_main():
|
||||
for key in keys:
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(progpath,u"adobekey_{0:d}.der".format(keycount))
|
||||
outfile = os.path.join(progpath,"adobekey_{0:d}.der".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
|
||||
with file(outfile, 'wb') as keyfileout:
|
||||
with open(outfile, 'wb') as keyfileout:
|
||||
keyfileout.write(key)
|
||||
success = True
|
||||
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
||||
except ADEPTError, e:
|
||||
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
|
||||
tkinter.messagebox.showinfo(progname, "Key successfully retrieved to {0}".format(outfile))
|
||||
except ADEPTError as e:
|
||||
tkinter.messagebox.showerror(progname, "Error: {0}".format(str(e)))
|
||||
except Exception:
|
||||
root.wm_state('normal')
|
||||
root.title(progname)
|
||||
text = traceback.format_exc()
|
||||
ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1)
|
||||
ExceptionDialog(root, text).pack(fill=tkinter.constants.BOTH, expand=1)
|
||||
root.mainloop()
|
||||
if not success:
|
||||
return 1
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#! /usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Routines for doing AES CBC in one file
|
||||
@@ -13,6 +14,8 @@
|
||||
CryptoPy Artisitic License Version 1.0
|
||||
See the wonderful pure python package cryptopy-1.2.5
|
||||
and read its LICENSE.txt for complete license details.
|
||||
|
||||
Adjusted for Python 3, September 2020
|
||||
"""
|
||||
|
||||
class CryptoError(Exception):
|
||||
@@ -101,7 +104,7 @@ class BlockCipher:
|
||||
numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize)
|
||||
if more == None: # no more calls to decrypt, should have all the data
|
||||
if numExtraBytes != 0:
|
||||
raise DecryptNotBlockAlignedError, 'Data not block aligned on decrypt'
|
||||
raise DecryptNotBlockAlignedError('Data not block aligned on decrypt')
|
||||
|
||||
# hold back some bytes in case last decrypt has zero len
|
||||
if (more != None) and (numExtraBytes == 0) and (numBlocks >0) :
|
||||
@@ -143,7 +146,7 @@ class padWithPadLen(Pad):
|
||||
def removePad(self, paddedBinaryString, blockSize):
|
||||
""" Remove padding from a binary string """
|
||||
if not(0<len(paddedBinaryString)):
|
||||
raise DecryptNotBlockAlignedError, 'Expected More Data'
|
||||
raise DecryptNotBlockAlignedError('Expected More Data')
|
||||
return paddedBinaryString[:-ord(paddedBinaryString[-1])]
|
||||
|
||||
class noPadding(Pad):
|
||||
@@ -173,8 +176,8 @@ class Rijndael(BlockCipher):
|
||||
self.blockSize = blockSize # blockSize is in bytes
|
||||
self.padding = padding # change default to noPadding() to get normal ECB behavior
|
||||
|
||||
assert( keySize%4==0 and NrTable[4].has_key(keySize/4)),'key size must be 16,20,24,29 or 32 bytes'
|
||||
assert( blockSize%4==0 and NrTable.has_key(blockSize/4)), 'block size must be 16,20,24,29 or 32 bytes'
|
||||
assert( keySize%4==0 and keySize/4 in NrTable[4]),'key size must be 16,20,24,29 or 32 bytes'
|
||||
assert( blockSize%4==0 and blockSize/4 in NrTable), 'block size must be 16,20,24,29 or 32 bytes'
|
||||
|
||||
self.Nb = self.blockSize/4 # Nb is number of columns of 32 bit words
|
||||
self.Nk = keySize/4 # Nk is the key length in 32-bit words
|
||||
@@ -451,7 +454,7 @@ class AES(Rijndael):
|
||||
def __init__(self, key = None, padding = padWithPadLen(), keySize=16):
|
||||
""" Initialize AES, keySize is in bytes """
|
||||
if not (keySize == 16 or keySize == 24 or keySize == 32) :
|
||||
raise BadKeySizeError, 'Illegal AES key size, must be 16, 24, or 32 bytes'
|
||||
raise BadKeySizeError('Illegal AES key size, must be 16, 24, or 32 bytes')
|
||||
|
||||
Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 )
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# crypto library mainly by some_updates
|
||||
@@ -8,7 +8,6 @@
|
||||
# pbkdf2.py Copyright © 2009 Daniel Holth <dholth@fastmail.fm>
|
||||
# pbkdf2.py This code may be freely used and modified for any purpose.
|
||||
|
||||
from __future__ import print_function
|
||||
import sys, os
|
||||
import hmac
|
||||
from struct import pack
|
||||
@@ -159,7 +158,7 @@ def _load_libalfcrypto():
|
||||
topazCryptoDecrypt(ctx, data, out, len(data))
|
||||
return out.raw
|
||||
|
||||
print(u"Using Library AlfCrypto DLL/DYLIB/SO")
|
||||
print("Using Library AlfCrypto DLL/DYLIB/SO")
|
||||
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
|
||||
|
||||
|
||||
@@ -178,13 +177,13 @@ def _load_python_alfcrypto():
|
||||
if len(key)!=16:
|
||||
raise Exception('Pukall_Cipher: Bad key length.')
|
||||
wkey = []
|
||||
for i in xrange(8):
|
||||
for i in range(8):
|
||||
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
|
||||
dst = ""
|
||||
for i in xrange(len(src)):
|
||||
for i in range(len(src)):
|
||||
temp1 = 0;
|
||||
byteXorVal = 0;
|
||||
for j in xrange(8):
|
||||
for j in range(8):
|
||||
temp1 ^= wkey[j]
|
||||
sum2 = (sum2+j)*20021 + sum1
|
||||
sum1 = (temp1*346)&0xFFFF
|
||||
@@ -197,7 +196,7 @@ def _load_python_alfcrypto():
|
||||
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
|
||||
if decryption:
|
||||
keyXorVal = curByte * 257;
|
||||
for j in xrange(8):
|
||||
for j in range(8):
|
||||
wkey[j] ^= keyXorVal;
|
||||
dst+=chr(curByte)
|
||||
return dst
|
||||
@@ -245,7 +244,7 @@ def _load_python_alfcrypto():
|
||||
cleartext = self.aes.decrypt(iv + data)
|
||||
return cleartext
|
||||
|
||||
print(u"Using Library AlfCrypto Python")
|
||||
print("Using Library AlfCrypto Python")
|
||||
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
|
||||
|
||||
|
||||
@@ -269,10 +268,10 @@ class KeyIVGen(object):
|
||||
# [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
|
||||
def pbkdf2(self, passwd, salt, iter, keylen):
|
||||
|
||||
def xorstr( a, b ):
|
||||
def xorbytes( a, b ):
|
||||
if len(a) != len(b):
|
||||
raise Exception("xorstr(): lengths differ")
|
||||
return ''.join((chr(ord(x)^ord(y)) for x, y in zip(a, b)))
|
||||
raise Exception("xorbytes(): lengths differ")
|
||||
return bytes([x ^ y for x, y in zip(a, b)])
|
||||
|
||||
def prf( h, data ):
|
||||
hm = h.copy()
|
||||
@@ -284,17 +283,17 @@ class KeyIVGen(object):
|
||||
T = U
|
||||
for i in range(2, itercount+1):
|
||||
U = prf( h, U )
|
||||
T = xorstr( T, U )
|
||||
T = xorbytes( T, U )
|
||||
return T
|
||||
|
||||
sha = hashlib.sha1
|
||||
digest_size = sha().digest_size
|
||||
# l - number of output blocks to produce
|
||||
l = keylen / digest_size
|
||||
l = keylen // digest_size
|
||||
if keylen % digest_size != 0:
|
||||
l += 1
|
||||
h = hmac.new( passwd, None, sha )
|
||||
T = ""
|
||||
T = b""
|
||||
for i in range(1, l+1):
|
||||
T += pbkdf2_F( h, salt, iter, i )
|
||||
return T[0: keylen]
|
||||
|
||||
181
DeDRM_plugin/androidkindlekey.py
Normal file → Executable file
181
DeDRM_plugin/androidkindlekey.py
Normal file → Executable file
@@ -1,12 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# androidkindlekey.py
|
||||
# Copyright © 2013-15 by Thom and Apprentice Harper
|
||||
# Some portions Copyright © 2010-15 by some_updates and Apprentice Alf
|
||||
#
|
||||
# Copyright © 2010-20 by Thom, Apprentice Harper et al.
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - AmazonSecureStorage.xml decryption to serial number
|
||||
@@ -17,14 +13,14 @@ from __future__ import with_statement
|
||||
# 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
|
||||
# 2.0 - Python 3 compatibility
|
||||
|
||||
"""
|
||||
Retrieve Kindle for Android Serial Number.
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '1.5'
|
||||
__version__ = '2.0'
|
||||
|
||||
import os
|
||||
import sys
|
||||
@@ -34,7 +30,7 @@ import tempfile
|
||||
import zlib
|
||||
import tarfile
|
||||
from hashlib import md5
|
||||
from cStringIO import StringIO
|
||||
from io import BytesIO
|
||||
from binascii import a2b_hex, b2a_hex
|
||||
|
||||
# Routines common to Mac and PC
|
||||
@@ -49,10 +45,11 @@ class SafeUnbuffered:
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
if isinstance(data,str):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
self.stream.buffer.write(data)
|
||||
self.stream.buffer.flush()
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
@@ -90,22 +87,20 @@ def unicode_argv():
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
range(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"kindlekey.py"]
|
||||
return ["kindlekey.py"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = "utf-8"
|
||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||
argvencoding = sys.stdin.encoding or "utf-8"
|
||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||
|
||||
class DrmException(Exception):
|
||||
pass
|
||||
|
||||
STORAGE = u"backup.ab"
|
||||
STORAGE1 = u"AmazonSecureStorage.xml"
|
||||
STORAGE2 = u"map_data_storage.db"
|
||||
STORAGE = "backup.ab"
|
||||
STORAGE1 = "AmazonSecureStorage.xml"
|
||||
STORAGE2 = "map_data_storage.db"
|
||||
|
||||
class AndroidObfuscation(object):
|
||||
'''AndroidObfuscation
|
||||
@@ -118,7 +113,7 @@ class AndroidObfuscation(object):
|
||||
cipher = self._get_cipher()
|
||||
padding = len(self.key) - len(plaintext) % len(self.key)
|
||||
plaintext += chr(padding) * padding
|
||||
return b2a_hex(cipher.encrypt(plaintext))
|
||||
return b2a_hex(cipher.encrypt(plaintext.encode('utf-8')))
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
cipher = self._get_cipher()
|
||||
@@ -138,7 +133,7 @@ class AndroidObfuscationV2(AndroidObfuscation):
|
||||
'''
|
||||
|
||||
count = 503
|
||||
password = 'Thomsun was here!'
|
||||
password = b'Thomsun was here!'
|
||||
|
||||
def __init__(self, salt):
|
||||
key = self.password + salt
|
||||
@@ -200,6 +195,7 @@ def get_serials1(path=STORAGE1):
|
||||
try:
|
||||
tokens = set(get_value('kindle.account.tokens').split(','))
|
||||
except:
|
||||
sys.stderr.write('cannot get kindle account tokens\n')
|
||||
return []
|
||||
|
||||
serials = []
|
||||
@@ -219,15 +215,14 @@ def get_serials2(path=STORAGE2):
|
||||
import sqlite3
|
||||
connection = sqlite3.connect(path)
|
||||
cursor = connection.cursor()
|
||||
cursor.execute('''select userdata_value from userdata where userdata_key like '%/%token.device.deviceserialname%' ''')
|
||||
userdata_keys = cursor.fetchall()
|
||||
cursor.execute('''select device_data_value from device_data where device_data_key like '%serial.number%' ''')
|
||||
device_data_keys = cursor.fetchall()
|
||||
dsns = []
|
||||
for userdata_row in userdata_keys:
|
||||
for device_data_row in device_data_keys:
|
||||
try:
|
||||
if userdata_row and userdata_row[0]:
|
||||
userdata_utf8 = userdata_row[0].encode('utf8')
|
||||
if len(userdata_utf8) > 0:
|
||||
dsns.append(userdata_utf8)
|
||||
if device_data_row and device_data_row[0]:
|
||||
if len(device_data_row[0]) > 0:
|
||||
dsns.append(device_data_row[0])
|
||||
except:
|
||||
print("Error getting one of the device serial name keys")
|
||||
traceback.print_exc()
|
||||
@@ -240,22 +235,24 @@ def get_serials2(path=STORAGE2):
|
||||
for userdata_row in userdata_keys:
|
||||
try:
|
||||
if userdata_row and userdata_row[0]:
|
||||
userdata_utf8 = userdata_row[0].encode('utf8')
|
||||
if len(userdata_utf8) > 0:
|
||||
tokens.append(userdata_utf8)
|
||||
if len(userdata_row[0]) > 0:
|
||||
if ',' in userdata_row[0]:
|
||||
splits = userdata_row[0].split(',')
|
||||
for split in splits:
|
||||
tokens.append(split)
|
||||
tokens.append(userdata_row[0])
|
||||
except:
|
||||
print("Error getting one of the account token keys")
|
||||
traceback.print_exc()
|
||||
pass
|
||||
tokens = list(set(tokens))
|
||||
|
||||
|
||||
serials = []
|
||||
for x in dsns:
|
||||
serials.append(x)
|
||||
for y in tokens:
|
||||
serials.append('%s%s' % (x, y))
|
||||
for y in tokens:
|
||||
serials.append(y)
|
||||
serials.append(y)
|
||||
serials.append(x+y)
|
||||
return serials
|
||||
|
||||
def get_serials(path=STORAGE):
|
||||
@@ -277,8 +274,8 @@ def get_serials(path=STORAGE):
|
||||
try :
|
||||
read = open(path, 'rb')
|
||||
head = read.read(24)
|
||||
if head[:14] == 'ANDROID BACKUP':
|
||||
output = StringIO(zlib.decompress(read.read()))
|
||||
if head[:14] == b'ANDROID BACKUP':
|
||||
output = BytesIO(zlib.decompress(read.read()))
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
@@ -313,7 +310,7 @@ __all__ = [ 'get_serials', 'getkey']
|
||||
def getkey(outfile, inpath):
|
||||
keys = get_serials(inpath)
|
||||
if len(keys) > 0:
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
with open(outfile, 'w') as keyfileout:
|
||||
for key in keys:
|
||||
keyfileout.write(key)
|
||||
keyfileout.write("\n")
|
||||
@@ -322,13 +319,13 @@ def getkey(outfile, inpath):
|
||||
|
||||
|
||||
def usage(progname):
|
||||
print(u"Decrypts the serial number(s) of Kindle For Android from Android backup or file")
|
||||
print(u"Get backup.ab file using adb backup com.amazon.kindle for Android 4.0+.")
|
||||
print(u"Otherwise extract AmazonSecureStorage.xml from /data/data/com.amazon.kindle/shared_prefs/AmazonSecureStorage.xml")
|
||||
print(u"Or map_data_storage.db from /data/data/com.amazon.kindle/databases/map_data_storage.db")
|
||||
print(u"")
|
||||
print(u"Usage:")
|
||||
print(u" {0:s} [-h] [-b <backup.ab>] [<outfile.k4a>]".format(progname))
|
||||
print("Decrypts the serial number(s) of Kindle For Android from Android backup or file")
|
||||
print("Get backup.ab file using adb backup com.amazon.kindle for Android 4.0+.")
|
||||
print("Otherwise extract AmazonSecureStorage.xml from /data/data/com.amazon.kindle/shared_prefs/AmazonSecureStorage.xml")
|
||||
print("Or map_data_storage.db from /data/data/com.amazon.kindle/databases/map_data_storage.db")
|
||||
print("")
|
||||
print("Usage:")
|
||||
print(" {0:s} [-h] [-b <backup.ab>] [<outfile.k4a>]".format(progname))
|
||||
|
||||
|
||||
def cli_main():
|
||||
@@ -336,13 +333,13 @@ def cli_main():
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print(u"{0} v{1}\nCopyright © 2010-2015 Thom, some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__))
|
||||
print("{0} v{1}\nCopyright © 2010-2020 Thom, Apprentice Harper et al.".format(progname,__version__))
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], "hb:")
|
||||
except getopt.GetoptError, err:
|
||||
except getopt.GetoptError as err:
|
||||
usage(progname)
|
||||
print(u"\nError in options or arguments: {0}".format(err.args[0]))
|
||||
print("\nError in options or arguments: {0}".format(err.args[0]))
|
||||
return 2
|
||||
|
||||
inpath = ""
|
||||
@@ -374,92 +371,92 @@ def cli_main():
|
||||
|
||||
if not os.path.isfile(inpath):
|
||||
usage(progname)
|
||||
print(u"\n{0:s} file not found".format(inpath))
|
||||
print("\n{0:s} file not found".format(inpath))
|
||||
return 2
|
||||
|
||||
if getkey(outfile, inpath):
|
||||
print(u"\nSaved Kindle for Android key to {0}".format(outfile))
|
||||
print("\nSaved Kindle for Android key to {0}".format(outfile))
|
||||
else:
|
||||
print(u"\nCould not retrieve Kindle for Android key.")
|
||||
print("\nCould not retrieve Kindle for Android key.")
|
||||
return 0
|
||||
|
||||
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import tkFileDialog
|
||||
import tkinter
|
||||
import tkinter.constants
|
||||
import tkinter.messagebox
|
||||
import tkinter.filedialog
|
||||
except:
|
||||
print("Tkinter not installed")
|
||||
return cli_main()
|
||||
print("tkinter not installed")
|
||||
return 0
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
class DecryptionDialog(tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = Tkinter.Label(self, text=u"Select backup.ab file")
|
||||
self.status.pack(fill=Tkconstants.X, expand=1)
|
||||
body = Tkinter.Frame(self)
|
||||
body.pack(fill=Tkconstants.X, expand=1)
|
||||
sticky = Tkconstants.E + Tkconstants.W
|
||||
tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = tkinter.Label(self, text="Select backup.ab file")
|
||||
self.status.pack(fill=tkinter.constants.X, expand=1)
|
||||
body = tkinter.Frame(self)
|
||||
body.pack(fill=tkinter.constants.X, expand=1)
|
||||
sticky = tkinter.constants.E + tkinter.constants.W
|
||||
body.grid_columnconfigure(1, weight=2)
|
||||
Tkinter.Label(body, text=u"Backup file").grid(row=0, column=0)
|
||||
self.keypath = Tkinter.Entry(body, width=40)
|
||||
tkinter.Label(body, text="Backup file").grid(row=0, column=0)
|
||||
self.keypath = tkinter.Entry(body, width=40)
|
||||
self.keypath.grid(row=0, column=1, sticky=sticky)
|
||||
self.keypath.insert(2, u"backup.ab")
|
||||
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
|
||||
self.keypath.insert(2, "backup.ab")
|
||||
button = tkinter.Button(body, text="...", command=self.get_keypath)
|
||||
button.grid(row=0, column=2)
|
||||
buttons = Tkinter.Frame(self)
|
||||
buttons = tkinter.Frame(self)
|
||||
buttons.pack()
|
||||
button2 = Tkinter.Button(
|
||||
buttons, text=u"Extract", width=10, command=self.generate)
|
||||
button2.pack(side=Tkconstants.LEFT)
|
||||
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
||||
button3 = Tkinter.Button(
|
||||
buttons, text=u"Quit", width=10, command=self.quit)
|
||||
button3.pack(side=Tkconstants.RIGHT)
|
||||
button2 = tkinter.Button(
|
||||
buttons, text="Extract", width=10, command=self.generate)
|
||||
button2.pack(side=tkinter.constants.LEFT)
|
||||
tkinter.Frame(buttons, width=10).pack(side=tkinter.constants.LEFT)
|
||||
button3 = tkinter.Button(
|
||||
buttons, text="Quit", width=10, command=self.quit)
|
||||
button3.pack(side=tkinter.constants.RIGHT)
|
||||
|
||||
def get_keypath(self):
|
||||
keypath = tkFileDialog.askopenfilename(
|
||||
parent=None, title=u"Select backup.ab file",
|
||||
defaultextension=u".ab",
|
||||
keypath = tkinter.filedialog.askopenfilename(
|
||||
parent=None, title="Select backup.ab file",
|
||||
defaultextension=".ab",
|
||||
filetypes=[('adb backup com.amazon.kindle', '.ab'),
|
||||
('All Files', '.*')])
|
||||
if keypath:
|
||||
keypath = os.path.normpath(keypath)
|
||||
self.keypath.delete(0, Tkconstants.END)
|
||||
self.keypath.delete(0, tkinter.constants.END)
|
||||
self.keypath.insert(0, keypath)
|
||||
return
|
||||
|
||||
def generate(self):
|
||||
inpath = self.keypath.get()
|
||||
self.status['text'] = u"Getting key..."
|
||||
self.status['text'] = "Getting key..."
|
||||
try:
|
||||
keys = get_serials(inpath)
|
||||
keycount = 0
|
||||
for key in keys:
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(progpath,u"kindlekey{0:d}.k4a".format(keycount))
|
||||
outfile = os.path.join(progpath,"kindlekey{0:d}.k4a".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
with open(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(key)
|
||||
success = True
|
||||
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
||||
except Exception, e:
|
||||
self.status['text'] = u"Error: {0}".format(e.args[0])
|
||||
tkinter.messagebox.showinfo(progname, "Key successfully retrieved to {0}".format(outfile))
|
||||
except Exception as e:
|
||||
self.status['text'] = "Error: {0}".format(e.args[0])
|
||||
return
|
||||
self.status['text'] = u"Select backup.ab file"
|
||||
self.status['text'] = "Select backup.ab file"
|
||||
|
||||
argv=unicode_argv()
|
||||
progpath, progname = os.path.split(argv[0])
|
||||
root = Tkinter.Tk()
|
||||
root.title(u"Kindle for Android Key Extraction v.{0}".format(__version__))
|
||||
root = tkinter.Tk()
|
||||
root.title("Kindle for Android Key Extraction v.{0}".format(__version__))
|
||||
root.resizable(True, False)
|
||||
root.minsize(300, 0)
|
||||
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
||||
DecryptionDialog(root).pack(fill=tkinter.constants.X, expand=1)
|
||||
root.mainloop()
|
||||
return 0
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys, os
|
||||
import locale
|
||||
import codecs
|
||||
import importlib
|
||||
|
||||
# get sys.argv arguments and encode them into utf-8
|
||||
def unicode_argv():
|
||||
@@ -34,15 +35,13 @@ def unicode_argv():
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
range(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"DeDRM.py"]
|
||||
return ["DeDRM.py"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = "utf-8"
|
||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||
argvencoding = sys.stdin.encoding or "utf-8"
|
||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||
|
||||
|
||||
def add_cp65001_codec():
|
||||
@@ -59,7 +58,7 @@ def set_utf8_default_encoding():
|
||||
return
|
||||
|
||||
# Regenerate setdefaultencoding.
|
||||
reload(sys)
|
||||
importlib.reload(sys)
|
||||
sys.setdefaultencoding('utf-8')
|
||||
|
||||
for attr in dir(locale):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
# and/or sell copies of the Software, and to permit persons to whom the
|
||||
# Software is furnished to do so, subject to the following conditions:
|
||||
#
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
@@ -29,6 +29,8 @@
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
|
||||
# Adjusted for Python 3, September 2020
|
||||
|
||||
"""
|
||||
AskFolder(...) -- Ask the user to select a folder Windows specific
|
||||
"""
|
||||
@@ -164,15 +166,15 @@ def AskFolder(
|
||||
def BrowseCallback(hwnd, uMsg, lParam, lpData):
|
||||
if uMsg == BFFM_INITIALIZED:
|
||||
if actionButtonLabel:
|
||||
label = unicode(actionButtonLabel, errors='replace')
|
||||
label = str(actionButtonLabel, errors='replace')
|
||||
user32.SendMessageW(hwnd, BFFM_SETOKTEXT, 0, label)
|
||||
if cancelButtonLabel:
|
||||
label = unicode(cancelButtonLabel, errors='replace')
|
||||
label = str(cancelButtonLabel, errors='replace')
|
||||
cancelButton = user32.GetDlgItem(hwnd, IDCANCEL)
|
||||
if cancelButton:
|
||||
user32.SetWindowTextW(cancelButton, label)
|
||||
if windowTitle:
|
||||
title = unicode(windowTitle, erros='replace')
|
||||
title = str(windowTitle, errors='replace')
|
||||
user32.SetWindowTextW(hwnd, title)
|
||||
if defaultLocation:
|
||||
user32.SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, defaultLocation.replace('/', '\\'))
|
||||
@@ -200,7 +202,7 @@ def AskFolder(
|
||||
if not pidl:
|
||||
result = None
|
||||
else:
|
||||
path = LPCWSTR(u" " * (MAX_PATH+1))
|
||||
path = LPCWSTR(" " * (MAX_PATH+1))
|
||||
shell32.SHGetPathFromIDListW(pidl, path)
|
||||
ole32.CoTaskMemFree(pidl)
|
||||
result = path.value
|
||||
|
||||
427
DeDRM_plugin/config.py
Normal file → Executable file
427
DeDRM_plugin/config.py
Normal file → Executable file
@@ -1,28 +1,18 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
from __future__ import print_function
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
|
||||
# Standard Python modules.
|
||||
import os, traceback, json
|
||||
# Python 3, September 2020
|
||||
|
||||
# PyQT4 modules (part of calibre).
|
||||
try:
|
||||
from PyQt5.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
|
||||
# Standard Python modules.
|
||||
import os, traceback, json, codecs
|
||||
|
||||
from PyQt5.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
|
||||
QGroupBox, QPushButton, QListWidget, QListWidgetItem,
|
||||
QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl)
|
||||
except ImportError:
|
||||
from PyQt4.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
|
||||
QGroupBox, QPushButton, QListWidget, QListWidgetItem,
|
||||
QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl)
|
||||
try:
|
||||
from PyQt5 import Qt as QtGui
|
||||
except ImportError:
|
||||
from PyQt4 import QtGui
|
||||
|
||||
|
||||
from PyQt5 import Qt as QtGui
|
||||
from zipfile import ZipFile
|
||||
|
||||
# calibre modules and constants.
|
||||
@@ -83,32 +73,32 @@ class ConfigWidget(QWidget):
|
||||
button_layout = QVBoxLayout()
|
||||
keys_group_box_layout.addLayout(button_layout)
|
||||
self.bandn_button = QtGui.QPushButton(self)
|
||||
self.bandn_button.setToolTip(_(u"Click to manage keys for Barnes and Noble ebooks"))
|
||||
self.bandn_button.setText(u"Barnes and Noble ebooks")
|
||||
self.bandn_button.setToolTip(_("Click to manage keys for Barnes and Noble ebooks"))
|
||||
self.bandn_button.setText("Barnes and Noble ebooks")
|
||||
self.bandn_button.clicked.connect(self.bandn_keys)
|
||||
self.kindle_android_button = QtGui.QPushButton(self)
|
||||
self.kindle_android_button.setToolTip(_(u"Click to manage keys for Kindle for Android ebooks"))
|
||||
self.kindle_android_button.setText(u"Kindle for Android ebooks")
|
||||
self.kindle_android_button.setToolTip(_("Click to manage keys for Kindle for Android ebooks"))
|
||||
self.kindle_android_button.setText("Kindle for Android ebooks")
|
||||
self.kindle_android_button.clicked.connect(self.kindle_android)
|
||||
self.kindle_serial_button = QtGui.QPushButton(self)
|
||||
self.kindle_serial_button.setToolTip(_(u"Click to manage eInk Kindle serial numbers for Kindle ebooks"))
|
||||
self.kindle_serial_button.setText(u"eInk Kindle ebooks")
|
||||
self.kindle_serial_button.setToolTip(_("Click to manage eInk Kindle serial numbers for Kindle ebooks"))
|
||||
self.kindle_serial_button.setText("eInk Kindle ebooks")
|
||||
self.kindle_serial_button.clicked.connect(self.kindle_serials)
|
||||
self.kindle_key_button = QtGui.QPushButton(self)
|
||||
self.kindle_key_button.setToolTip(_(u"Click to manage keys for Kindle for Mac/PC ebooks"))
|
||||
self.kindle_key_button.setText(u"Kindle for Mac/PC ebooks")
|
||||
self.kindle_key_button.setToolTip(_("Click to manage keys for Kindle for Mac/PC ebooks"))
|
||||
self.kindle_key_button.setText("Kindle for Mac/PC ebooks")
|
||||
self.kindle_key_button.clicked.connect(self.kindle_keys)
|
||||
self.adept_button = QtGui.QPushButton(self)
|
||||
self.adept_button.setToolTip(_(u"Click to manage keys for Adobe Digital Editions ebooks"))
|
||||
self.adept_button.setText(u"Adobe Digital Editions ebooks")
|
||||
self.adept_button.setToolTip(_("Click to manage keys for Adobe Digital Editions ebooks"))
|
||||
self.adept_button.setText("Adobe Digital Editions ebooks")
|
||||
self.adept_button.clicked.connect(self.adept_keys)
|
||||
self.mobi_button = QtGui.QPushButton(self)
|
||||
self.mobi_button.setToolTip(_(u"Click to manage PIDs for Mobipocket ebooks"))
|
||||
self.mobi_button.setText(u"Mobipocket ebooks")
|
||||
self.mobi_button.setToolTip(_("Click to manage PIDs for Mobipocket ebooks"))
|
||||
self.mobi_button.setText("Mobipocket ebooks")
|
||||
self.mobi_button.clicked.connect(self.mobi_keys)
|
||||
self.ereader_button = QtGui.QPushButton(self)
|
||||
self.ereader_button.setToolTip(_(u"Click to manage keys for eReader ebooks"))
|
||||
self.ereader_button.setText(u"eReader ebooks")
|
||||
self.ereader_button.setToolTip(_("Click to manage keys for eReader ebooks"))
|
||||
self.ereader_button.setText("eReader ebooks")
|
||||
self.ereader_button.clicked.connect(self.ereader_keys)
|
||||
button_layout.addWidget(self.kindle_serial_button)
|
||||
button_layout.addWidget(self.kindle_android_button)
|
||||
@@ -121,48 +111,48 @@ class ConfigWidget(QWidget):
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
def kindle_serials(self):
|
||||
d = ManageKeysDialog(self,u"EInk Kindle Serial Number",self.tempdedrmprefs['serials'], AddSerialDialog)
|
||||
d = ManageKeysDialog(self,"EInk Kindle Serial Number",self.tempdedrmprefs['serials'], AddSerialDialog)
|
||||
d.exec_()
|
||||
|
||||
|
||||
def kindle_android(self):
|
||||
d = ManageKeysDialog(self,u"Kindle for Android Key",self.tempdedrmprefs['androidkeys'], AddAndroidDialog, 'k4a')
|
||||
d = ManageKeysDialog(self,"Kindle for Android Key",self.tempdedrmprefs['androidkeys'], AddAndroidDialog, 'k4a')
|
||||
d.exec_()
|
||||
|
||||
def kindle_keys(self):
|
||||
if isosx or iswindows:
|
||||
d = ManageKeysDialog(self,u"Kindle for Mac and PC Key",self.tempdedrmprefs['kindlekeys'], AddKindleDialog, 'k4i')
|
||||
d = ManageKeysDialog(self,"Kindle for Mac and PC Key",self.tempdedrmprefs['kindlekeys'], AddKindleDialog, 'k4i')
|
||||
else:
|
||||
# linux
|
||||
d = ManageKeysDialog(self,u"Kindle for Mac and PC Key",self.tempdedrmprefs['kindlekeys'], AddKindleDialog, 'k4i', self.tempdedrmprefs['kindlewineprefix'])
|
||||
d = ManageKeysDialog(self,"Kindle for Mac and PC Key",self.tempdedrmprefs['kindlekeys'], AddKindleDialog, 'k4i', self.tempdedrmprefs['kindlewineprefix'])
|
||||
d.exec_()
|
||||
self.tempdedrmprefs['kindlewineprefix'] = d.getwineprefix()
|
||||
|
||||
def adept_keys(self):
|
||||
if isosx or iswindows:
|
||||
d = ManageKeysDialog(self,u"Adobe Digital Editions Key",self.tempdedrmprefs['adeptkeys'], AddAdeptDialog, 'der')
|
||||
d = ManageKeysDialog(self,"Adobe Digital Editions Key",self.tempdedrmprefs['adeptkeys'], AddAdeptDialog, 'der')
|
||||
else:
|
||||
# linux
|
||||
d = ManageKeysDialog(self,u"Adobe Digital Editions Key",self.tempdedrmprefs['adeptkeys'], AddAdeptDialog, 'der', self.tempdedrmprefs['adobewineprefix'])
|
||||
d = ManageKeysDialog(self,"Adobe Digital Editions Key",self.tempdedrmprefs['adeptkeys'], AddAdeptDialog, 'der', self.tempdedrmprefs['adobewineprefix'])
|
||||
d.exec_()
|
||||
self.tempdedrmprefs['adobewineprefix'] = d.getwineprefix()
|
||||
|
||||
def mobi_keys(self):
|
||||
d = ManageKeysDialog(self,u"Mobipocket PID",self.tempdedrmprefs['pids'], AddPIDDialog)
|
||||
d = ManageKeysDialog(self,"Mobipocket PID",self.tempdedrmprefs['pids'], AddPIDDialog)
|
||||
d.exec_()
|
||||
|
||||
def bandn_keys(self):
|
||||
d = ManageKeysDialog(self,u"Barnes and Noble Key",self.tempdedrmprefs['bandnkeys'], AddBandNKeyDialog, 'b64')
|
||||
d = ManageKeysDialog(self,"Barnes and Noble Key",self.tempdedrmprefs['bandnkeys'], AddBandNKeyDialog, 'b64')
|
||||
d.exec_()
|
||||
|
||||
def ereader_keys(self):
|
||||
d = ManageKeysDialog(self,u"eReader Key",self.tempdedrmprefs['ereaderkeys'], AddEReaderDialog, 'b63')
|
||||
d = ManageKeysDialog(self,"eReader Key",self.tempdedrmprefs['ereaderkeys'], AddEReaderDialog, 'b63')
|
||||
d.exec_()
|
||||
|
||||
def help_link_activated(self, url):
|
||||
def get_help_file_resource():
|
||||
# Copy the HTML helpfile to the plugin directory each time the
|
||||
# link is clicked in case the helpfile is updated in newer plugins.
|
||||
file_path = os.path.join(config_dir, u"plugins", u"DeDRM", u"help", help_file_name)
|
||||
file_path = os.path.join(config_dir, "plugins", "DeDRM", "help", help_file_name)
|
||||
with open(file_path,'w') as f:
|
||||
f.write(self.load_resource(help_file_name))
|
||||
return file_path
|
||||
@@ -185,23 +175,23 @@ class ConfigWidget(QWidget):
|
||||
def load_resource(self, name):
|
||||
with ZipFile(self.plugin_path, 'r') as zf:
|
||||
if name in zf.namelist():
|
||||
return zf.read(name)
|
||||
return zf.read(name).decode('utf-8')
|
||||
return ""
|
||||
|
||||
|
||||
|
||||
class ManageKeysDialog(QDialog):
|
||||
def __init__(self, parent, key_type_name, plugin_keys, create_key, keyfile_ext = u"", wineprefix = None):
|
||||
def __init__(self, parent, key_type_name, plugin_keys, create_key, keyfile_ext = "", wineprefix = None):
|
||||
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.import_key = (keyfile_ext != u"")
|
||||
self.binary_file = (keyfile_ext == u"der")
|
||||
self.json_file = (keyfile_ext == u"k4i")
|
||||
self.android_file = (keyfile_ext == u"k4a")
|
||||
self.import_key = (keyfile_ext != "")
|
||||
self.binary_file = (keyfile_ext == "der")
|
||||
self.json_file = (keyfile_ext == "k4i")
|
||||
self.android_file = (keyfile_ext == "k4a")
|
||||
self.wineprefix = wineprefix
|
||||
|
||||
self.setWindowTitle("{0} {1}: Manage {2}s".format(PLUGIN_NAME, PLUGIN_VERSION, self.key_type_name))
|
||||
@@ -219,13 +209,13 @@ class ManageKeysDialog(QDialog):
|
||||
help_label.linkActivated.connect(self.help_link_activated)
|
||||
help_layout.addWidget(help_label)
|
||||
|
||||
keys_group_box = QGroupBox(_(u"{0}s".format(self.key_type_name)), self)
|
||||
keys_group_box = QGroupBox(_("{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.setToolTip("{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)
|
||||
@@ -234,25 +224,25 @@ class ManageKeysDialog(QDialog):
|
||||
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.setToolTip("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.setToolTip(_("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)
|
||||
|
||||
if type(self.plugin_keys) == dict and self.import_key:
|
||||
self._rename_key_button = QtGui.QToolButton(self)
|
||||
self._rename_key_button.setToolTip(_(u"Rename highlighted key"))
|
||||
self._rename_key_button.setToolTip(_("Rename highlighted key"))
|
||||
self._rename_key_button.setIcon(QIcon(I('edit-select-all.png')))
|
||||
self._rename_key_button.clicked.connect(self.rename_key)
|
||||
button_layout.addWidget(self._rename_key_button)
|
||||
|
||||
self.export_key_button = QtGui.QToolButton(self)
|
||||
self.export_key_button.setToolTip(u"Save highlighted key to a .{0} file".format(self.keyfile_ext))
|
||||
self.export_key_button.setToolTip("Save highlighted key to a .{0} file".format(self.keyfile_ext))
|
||||
self.export_key_button.setIcon(QIcon(I('save.png')))
|
||||
self.export_key_button.clicked.connect(self.export_key)
|
||||
button_layout.addWidget(self.export_key_button)
|
||||
@@ -264,7 +254,7 @@ class ManageKeysDialog(QDialog):
|
||||
wineprefix_layout = QHBoxLayout()
|
||||
layout.addLayout(wineprefix_layout)
|
||||
wineprefix_layout.setAlignment(Qt.AlignCenter)
|
||||
self.wp_label = QLabel(u"WINEPREFIX:")
|
||||
self.wp_label = QLabel("WINEPREFIX:")
|
||||
wineprefix_layout.addWidget(self.wp_label)
|
||||
self.wp_lineedit = QLineEdit(self)
|
||||
wineprefix_layout.addWidget(self.wp_lineedit)
|
||||
@@ -276,8 +266,8 @@ class ManageKeysDialog(QDialog):
|
||||
layout.addLayout(migrate_layout)
|
||||
if self.import_key:
|
||||
migrate_layout.setAlignment(Qt.AlignJustify)
|
||||
self.migrate_btn = QPushButton(u"Import Existing Keyfiles", self)
|
||||
self.migrate_btn.setToolTip(u"Import *.{0} files (created using other tools).".format(self.keyfile_ext))
|
||||
self.migrate_btn = QPushButton("Import Existing Keyfiles", self)
|
||||
self.migrate_btn.setToolTip("Import *.{0} files (created using other tools).".format(self.keyfile_ext))
|
||||
self.migrate_btn.clicked.connect(self.migrate_wrapper)
|
||||
migrate_layout.addWidget(self.migrate_btn)
|
||||
migrate_layout.addStretch()
|
||||
@@ -289,8 +279,8 @@ class ManageKeysDialog(QDialog):
|
||||
|
||||
def getwineprefix(self):
|
||||
if self.wineprefix is not None:
|
||||
return unicode(self.wp_lineedit.text()).strip()
|
||||
return u""
|
||||
return str(self.wp_lineedit.text()).strip()
|
||||
return ""
|
||||
|
||||
def populate_list(self):
|
||||
if type(self.plugin_keys) == dict:
|
||||
@@ -310,15 +300,15 @@ class ManageKeysDialog(QDialog):
|
||||
new_key_value = d.key_value
|
||||
if type(self.plugin_keys) == dict:
|
||||
if new_key_value in self.plugin_keys.values():
|
||||
old_key_name = [name for name, value in self.plugin_keys.iteritems() if value == new_key_value][0]
|
||||
old_key_name = [name for name, value in self.plugin_keys.items() if value == new_key_value][0]
|
||||
info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
|
||||
u"The new {1} is the same as the existing {1} named <strong>{0}</strong> and has not been added.".format(old_key_name,self.key_type_name), show=True)
|
||||
"The new {1} is the same as the existing {1} named <strong>{0}</strong> and has not been added.".format(old_key_name,self.key_type_name), show=True)
|
||||
return
|
||||
self.plugin_keys[d.key_name] = new_key_value
|
||||
else:
|
||||
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)
|
||||
"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)
|
||||
@@ -327,7 +317,7 @@ class ManageKeysDialog(QDialog):
|
||||
|
||||
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)
|
||||
errmsg = "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
|
||||
@@ -338,8 +328,8 @@ class ManageKeysDialog(QDialog):
|
||||
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):
|
||||
keyname = str(self.listy.currentItem().text())
|
||||
if not question_dialog(self, "{0} {1}: Confirm Rename".format(PLUGIN_NAME, PLUGIN_VERSION), "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]
|
||||
@@ -350,8 +340,8 @@ class ManageKeysDialog(QDialog):
|
||||
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):
|
||||
keyname = str(self.listy.currentItem().text())
|
||||
if not question_dialog(self, "{0} {1}: Confirm Delete".format(PLUGIN_NAME, PLUGIN_VERSION), "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
|
||||
if type(self.plugin_keys) == dict:
|
||||
del self.plugin_keys[keyname]
|
||||
@@ -365,8 +355,8 @@ class ManageKeysDialog(QDialog):
|
||||
def get_help_file_resource():
|
||||
# Copy the HTML helpfile to the plugin directory each time the
|
||||
# link is clicked in case the helpfile is updated in newer plugins.
|
||||
help_file_name = u"{0}_{1}_Help.htm".format(PLUGIN_NAME, self.key_type_name)
|
||||
file_path = os.path.join(config_dir, u"plugins", u"DeDRM", u"help", help_file_name)
|
||||
help_file_name = "{0}_{1}_Help.htm".format(PLUGIN_NAME, self.key_type_name)
|
||||
file_path = os.path.join(config_dir, "plugins", "DeDRM", "help", help_file_name)
|
||||
with open(file_path,'w') as f:
|
||||
f.write(self.parent.load_resource(help_file_name))
|
||||
return file_path
|
||||
@@ -374,9 +364,9 @@ class ManageKeysDialog(QDialog):
|
||||
open_url(QUrl(url))
|
||||
|
||||
def migrate_files(self):
|
||||
unique_dlg_name = PLUGIN_NAME + u"import {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory
|
||||
caption = u"Select {0} files to import".format(self.key_type_name)
|
||||
filters = [(u"{0} files".format(self.key_type_name), [self.keyfile_ext])]
|
||||
unique_dlg_name = PLUGIN_NAME + "import {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory
|
||||
caption = "Select {0} files to import".format(self.key_type_name)
|
||||
filters = [("{0} files".format(self.key_type_name), [self.keyfile_ext])]
|
||||
files = choose_files(self, unique_dlg_name, caption, filters, all_files=False)
|
||||
counter = 0
|
||||
skipped = 0
|
||||
@@ -388,7 +378,7 @@ class ManageKeysDialog(QDialog):
|
||||
with open(fpath,'rb') as keyfile:
|
||||
new_key_value = keyfile.read()
|
||||
if self.binary_file:
|
||||
new_key_value = new_key_value.encode('hex')
|
||||
new_key_value = codecs.encode(new_key_value,'hex')
|
||||
elif self.json_file:
|
||||
new_key_value = json.loads(new_key_value)
|
||||
elif self.android_file:
|
||||
@@ -398,27 +388,27 @@ class ManageKeysDialog(QDialog):
|
||||
for key in self.plugin_keys.keys():
|
||||
if uStrCmp(new_key_name, key, True):
|
||||
skipped += 1
|
||||
msg = u"A key with the name <strong>{0}</strong> already exists!\nSkipping key file <strong>{1}</strong>.\nRename the existing key and import again".format(new_key_name,filename)
|
||||
msg = "A key with the name <strong>{0}</strong> already exists!\nSkipping key file <strong>{1}</strong>.\nRename the existing key and import again".format(new_key_name,filename)
|
||||
inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||
_(msg), show_copy_button=False, show=True)
|
||||
match = True
|
||||
break
|
||||
if not match:
|
||||
if new_key_value in self.plugin_keys.values():
|
||||
old_key_name = [name for name, value in self.plugin_keys.iteritems() if value == new_key_value][0]
|
||||
old_key_name = [name for name, value in self.plugin_keys.items() if value == new_key_value][0]
|
||||
skipped += 1
|
||||
info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||
u"The key in file {0} is the same as the existing key <strong>{1}</strong> and has been skipped.".format(filename,old_key_name), show_copy_button=False, show=True)
|
||||
"The key in file {0} is the same as the existing key <strong>{1}</strong> and has been skipped.".format(filename,old_key_name), show_copy_button=False, show=True)
|
||||
else:
|
||||
counter += 1
|
||||
self.plugin_keys[new_key_name] = new_key_value
|
||||
|
||||
msg = u""
|
||||
|
||||
msg = ""
|
||||
if counter+skipped > 1:
|
||||
if counter > 0:
|
||||
msg += u"Imported <strong>{0:d}</strong> key {1}. ".format(counter, u"file" if counter == 1 else u"files")
|
||||
msg += "Imported <strong>{0:d}</strong> key {1}. ".format(counter, "file" if counter == 1 else "files")
|
||||
if skipped > 0:
|
||||
msg += u"Skipped <strong>{0:d}</strong> key {1}.".format(skipped, u"file" if counter == 1 else u"files")
|
||||
msg += "Skipped <strong>{0:d}</strong> key {1}.".format(skipped, "file" if counter == 1 else "files")
|
||||
inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||
_(msg), show_copy_button=False, show=True)
|
||||
return counter > 0
|
||||
@@ -430,27 +420,30 @@ class ManageKeysDialog(QDialog):
|
||||
|
||||
def export_key(self):
|
||||
if not self.listy.currentItem():
|
||||
errmsg = u"No keyfile selected to export. Highlight a keyfile first."
|
||||
errmsg = "No keyfile selected to export. Highlight a keyfile first."
|
||||
r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||
_(errmsg), show=True, show_copy_button=False)
|
||||
return
|
||||
keyname = unicode(self.listy.currentItem().text())
|
||||
unique_dlg_name = PLUGIN_NAME + u"export {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory
|
||||
caption = u"Save {0} File as...".format(self.key_type_name)
|
||||
filters = [(u"{0} Files".format(self.key_type_name), [u"{0}".format(self.keyfile_ext)])]
|
||||
defaultname = u"{0}.{1}".format(keyname, self.keyfile_ext)
|
||||
keyname = str(self.listy.currentItem().text())
|
||||
unique_dlg_name = PLUGIN_NAME + "export {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory
|
||||
caption = "Save {0} File as...".format(self.key_type_name)
|
||||
filters = [("{0} Files".format(self.key_type_name), ["{0}".format(self.keyfile_ext)])]
|
||||
defaultname = "{0}.{1}".format(keyname, self.keyfile_ext)
|
||||
filename = choose_save_file(self, unique_dlg_name, caption, filters, all_files=False, initial_filename=defaultname)
|
||||
if filename:
|
||||
with file(filename, 'wb') as fname:
|
||||
if self.binary_file:
|
||||
fname.write(self.plugin_keys[keyname].decode('hex'))
|
||||
elif self.json_file:
|
||||
if self.binary_file:
|
||||
with open(filename, 'wb') as fname:
|
||||
fname.write(codecs.decode(self.plugin_keys[keyname],'hex'))
|
||||
elif self.json_file:
|
||||
with open(filename, 'w') as fname:
|
||||
fname.write(json.dumps(self.plugin_keys[keyname]))
|
||||
elif self.android_file:
|
||||
elif self.android_file:
|
||||
with open(filename, 'w') as fname:
|
||||
for key in self.plugin_keys[keyname]:
|
||||
fname.write(key)
|
||||
fname.write("\n")
|
||||
else:
|
||||
fname.write('\n')
|
||||
else:
|
||||
with open(filename, 'w') as fname:
|
||||
fname.write(self.plugin_keys[keyname])
|
||||
|
||||
|
||||
@@ -472,7 +465,7 @@ class RenameKeyDialog(QDialog):
|
||||
|
||||
data_group_box_layout.addWidget(QLabel('New Key Name:', self))
|
||||
self.key_ledit = QLineEdit(self.parent.listy.currentItem().text(), self)
|
||||
self.key_ledit.setToolTip(u"Enter a new name for this existing {0}.".format(parent.key_type_name))
|
||||
self.key_ledit.setToolTip("Enter a new name for this existing {0}.".format(parent.key_type_name))
|
||||
data_group_box_layout.addWidget(self.key_ledit)
|
||||
|
||||
layout.addSpacing(20)
|
||||
@@ -485,12 +478,12 @@ class RenameKeyDialog(QDialog):
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
def accept(self):
|
||||
if not unicode(self.key_ledit.text()) or unicode(self.key_ledit.text()).isspace():
|
||||
errmsg = u"Key name field cannot be empty!"
|
||||
if not str(self.key_ledit.text()) or str(self.key_ledit.text()).isspace():
|
||||
errmsg = "Key name field cannot be empty!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||
_(errmsg), show=True, show_copy_button=False)
|
||||
if len(self.key_ledit.text()) < 4:
|
||||
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||
errmsg = "Key name must be at <i>least</i> 4 characters long!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||
_(errmsg), show=True, show_copy_button=False)
|
||||
if uStrCmp(self.key_ledit.text(), self.parent.listy.currentItem().text()):
|
||||
@@ -499,14 +492,14 @@ class RenameKeyDialog(QDialog):
|
||||
for k in self.parent.plugin_keys.keys():
|
||||
if (uStrCmp(self.key_ledit.text(), k, True) and
|
||||
not uStrCmp(k, self.parent.listy.currentItem().text(), True)):
|
||||
errmsg = u"The key name <strong>{0}</strong> is already being used.".format(self.key_ledit.text())
|
||||
errmsg = "The key name <strong>{0}</strong> is already being used.".format(self.key_ledit.text())
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||
_(errmsg), show=True, show_copy_button=False)
|
||||
QDialog.accept(self)
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
return unicode(self.key_ledit.text()).strip()
|
||||
return str(self.key_ledit.text()).strip()
|
||||
|
||||
|
||||
|
||||
@@ -519,48 +512,48 @@ class AddBandNKeyDialog(QDialog):
|
||||
def __init__(self, parent=None,):
|
||||
QDialog.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.setWindowTitle(u"{0} {1}: Create New Barnes & Noble Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
self.setWindowTitle("{0} {1}: Create New Barnes & Noble Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
data_group_box = QGroupBox(u"", self)
|
||||
data_group_box = QGroupBox("", 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"Unique Key Name:", self))
|
||||
key_group.addWidget(QLabel("Unique Key Name:", self))
|
||||
self.key_ledit = QLineEdit("", self)
|
||||
self.key_ledit.setToolTip(_(u"<p>Enter an identifying name for this new key.</p>" +
|
||||
u"<p>It should be something that will help you remember " +
|
||||
u"what personal information was used to create it."))
|
||||
self.key_ledit.setToolTip(_("<p>Enter an identifying name for this new key.</p>" +
|
||||
"<p>It should be something that will help you remember " +
|
||||
"what personal information was used to create it."))
|
||||
key_group.addWidget(self.key_ledit)
|
||||
|
||||
name_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(name_group)
|
||||
name_group.addWidget(QLabel(u"B&N/nook account email address:", self))
|
||||
self.name_ledit = QLineEdit(u"", self)
|
||||
self.name_ledit.setToolTip(_(u"<p>Enter your email address as it appears in your B&N " +
|
||||
u"account.</p>" +
|
||||
u"<p>It will only be used to generate this " +
|
||||
u"key and won\'t be stored anywhere " +
|
||||
u"in calibre or on your computer.</p>" +
|
||||
u"<p>eg: apprenticeharper@gmail.com</p>"))
|
||||
name_group.addWidget(QLabel("B&N/nook account email address:", self))
|
||||
self.name_ledit = QLineEdit("", self)
|
||||
self.name_ledit.setToolTip(_("<p>Enter your email address as it appears in your B&N " +
|
||||
"account.</p>" +
|
||||
"<p>It will only be used to generate this " +
|
||||
"key and won\'t be stored anywhere " +
|
||||
"in calibre or on your computer.</p>" +
|
||||
"<p>eg: apprenticeharper@gmail.com</p>"))
|
||||
name_group.addWidget(self.name_ledit)
|
||||
name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self)
|
||||
name_disclaimer_label = QLabel(_("(Will not be saved in configuration data)"), self)
|
||||
name_disclaimer_label.setAlignment(Qt.AlignHCenter)
|
||||
data_group_box_layout.addWidget(name_disclaimer_label)
|
||||
|
||||
ccn_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(ccn_group)
|
||||
ccn_group.addWidget(QLabel(u"B&N/nook account password:", self))
|
||||
self.cc_ledit = QLineEdit(u"", self)
|
||||
self.cc_ledit.setToolTip(_(u"<p>Enter the password " +
|
||||
u"for your B&N account.</p>" +
|
||||
u"<p>The password will only be used to generate this " +
|
||||
u"key and won\'t be stored anywhere in " +
|
||||
u"calibre or on your computer."))
|
||||
ccn_group.addWidget(QLabel("B&N/nook account password:", self))
|
||||
self.cc_ledit = QLineEdit("", self)
|
||||
self.cc_ledit.setToolTip(_("<p>Enter the password " +
|
||||
"for your B&N account.</p>" +
|
||||
"<p>The password will only be used to generate this " +
|
||||
"key and won\'t be stored anywhere in " +
|
||||
"calibre or on your computer."))
|
||||
ccn_group.addWidget(self.cc_ledit)
|
||||
ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self)
|
||||
ccn_disclaimer_label.setAlignment(Qt.AlignHCenter)
|
||||
@@ -569,13 +562,13 @@ class AddBandNKeyDialog(QDialog):
|
||||
|
||||
key_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(key_group)
|
||||
key_group.addWidget(QLabel(u"Retrieved key:", self))
|
||||
self.key_display = QLabel(u"", self)
|
||||
self.key_display.setToolTip(_(u"Click the Retrieve Key button to fetch your B&N encryption key from the B&N servers"))
|
||||
key_group.addWidget(QLabel("Retrieved key:", self))
|
||||
self.key_display = QLabel("", self)
|
||||
self.key_display.setToolTip(_("Click the Retrieve Key button to fetch your B&N encryption key from the B&N servers"))
|
||||
key_group.addWidget(self.key_display)
|
||||
self.retrieve_button = QtGui.QPushButton(self)
|
||||
self.retrieve_button.setToolTip(_(u"Click to retrieve your B&N encryption key from the B&N servers"))
|
||||
self.retrieve_button.setText(u"Retrieve Key")
|
||||
self.retrieve_button.setToolTip(_("Click to retrieve your B&N encryption key from the B&N servers"))
|
||||
self.retrieve_button.setText("Retrieve Key")
|
||||
self.retrieve_button.clicked.connect(self.retrieve_key)
|
||||
key_group.addWidget(self.retrieve_button)
|
||||
layout.addSpacing(10)
|
||||
@@ -589,35 +582,35 @@ class AddBandNKeyDialog(QDialog):
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
return unicode(self.key_ledit.text()).strip()
|
||||
return str(self.key_ledit.text()).strip()
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
return unicode(self.key_display.text()).strip()
|
||||
return str(self.key_display.text()).strip()
|
||||
|
||||
@property
|
||||
def user_name(self):
|
||||
return unicode(self.name_ledit.text()).strip().lower().replace(' ','')
|
||||
return str(self.name_ledit.text()).strip().lower().replace(' ','')
|
||||
|
||||
@property
|
||||
def cc_number(self):
|
||||
return unicode(self.cc_ledit.text()).strip()
|
||||
return str(self.cc_ledit.text()).strip()
|
||||
|
||||
def retrieve_key(self):
|
||||
from calibre_plugins.dedrm.ignoblekeyfetch import fetch_key as fetch_bandn_key
|
||||
fetched_key = fetch_bandn_key(self.user_name,self.cc_number)
|
||||
if fetched_key == "":
|
||||
errmsg = u"Could not retrieve key. Check username, password and intenet connectivity and try again."
|
||||
errmsg = "Could not retrieve key. Check username, password and intenet connectivity and try again."
|
||||
error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
else:
|
||||
self.key_display.setText(fetched_key)
|
||||
|
||||
def accept(self):
|
||||
if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace():
|
||||
errmsg = u"All fields are required!"
|
||||
errmsg = "All fields are required!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if len(self.key_name) < 4:
|
||||
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||
errmsg = "Key name must be at <i>least</i> 4 characters long!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if len(self.key_value) == 0:
|
||||
self.retrieve_key()
|
||||
@@ -629,37 +622,37 @@ class AddEReaderDialog(QDialog):
|
||||
def __init__(self, parent=None,):
|
||||
QDialog.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.setWindowTitle(u"{0} {1}: Create New eReader Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
self.setWindowTitle("{0} {1}: Create New eReader Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
data_group_box = QGroupBox(u"", self)
|
||||
data_group_box = QGroupBox("", 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"Unique Key Name:", self))
|
||||
key_group.addWidget(QLabel("Unique Key Name:", self))
|
||||
self.key_ledit = QLineEdit("", self)
|
||||
self.key_ledit.setToolTip(u"<p>Enter an identifying name for this new key.\nIt should be something that will help you remember what personal information was used to create it.")
|
||||
self.key_ledit.setToolTip("<p>Enter an identifying name for this new key.\nIt should be something that will help you remember what personal information was used to create it.")
|
||||
key_group.addWidget(self.key_ledit)
|
||||
|
||||
name_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(name_group)
|
||||
name_group.addWidget(QLabel(u"Your Name:", self))
|
||||
self.name_ledit = QLineEdit(u"", self)
|
||||
self.name_ledit.setToolTip(u"Enter the name for this eReader key, usually the name on your credit card.\nIt will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.\n(ex: Mr Jonathan Q Smith)")
|
||||
name_group.addWidget(QLabel("Your Name:", self))
|
||||
self.name_ledit = QLineEdit("", self)
|
||||
self.name_ledit.setToolTip("Enter the name for this eReader key, usually the name on your credit card.\nIt will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.\n(ex: Mr Jonathan Q Smith)")
|
||||
name_group.addWidget(self.name_ledit)
|
||||
name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self)
|
||||
name_disclaimer_label = QLabel(_("(Will not be saved in configuration data)"), self)
|
||||
name_disclaimer_label.setAlignment(Qt.AlignHCenter)
|
||||
data_group_box_layout.addWidget(name_disclaimer_label)
|
||||
|
||||
ccn_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(ccn_group)
|
||||
ccn_group.addWidget(QLabel(u"Credit Card#:", self))
|
||||
self.cc_ledit = QLineEdit(u"", self)
|
||||
self.cc_ledit.setToolTip(u"<p>Enter the last 8 digits of credit card number for this eReader key.\nThey will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.")
|
||||
ccn_group.addWidget(QLabel("Credit Card#:", self))
|
||||
self.cc_ledit = QLineEdit("", self)
|
||||
self.cc_ledit.setToolTip("<p>Enter the last 8 digits of credit card number for this eReader key.\nThey will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.")
|
||||
ccn_group.addWidget(self.cc_ledit)
|
||||
ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self)
|
||||
ccn_disclaimer_label.setAlignment(Qt.AlignHCenter)
|
||||
@@ -675,31 +668,31 @@ class AddEReaderDialog(QDialog):
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
return unicode(self.key_ledit.text()).strip()
|
||||
return str(self.key_ledit.text()).strip()
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
from calibre_plugins.dedrm.erdr2pml import getuser_key as generate_ereader_key
|
||||
return generate_ereader_key(self.user_name,self.cc_number).encode('hex')
|
||||
return codecs.encode(generate_ereader_key(self.user_name, self.cc_number),'hex')
|
||||
|
||||
@property
|
||||
def user_name(self):
|
||||
return unicode(self.name_ledit.text()).strip().lower().replace(' ','')
|
||||
return str(self.name_ledit.text()).strip().lower().replace(' ','')
|
||||
|
||||
@property
|
||||
def cc_number(self):
|
||||
return unicode(self.cc_ledit.text()).strip().replace(' ', '').replace('-','')
|
||||
return str(self.cc_ledit.text()).strip().replace(' ', '').replace('-','')
|
||||
|
||||
|
||||
def accept(self):
|
||||
if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace():
|
||||
errmsg = u"All fields are required!"
|
||||
errmsg = "All fields are required!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if not self.cc_number.isdigit():
|
||||
errmsg = u"Numbers only in the credit card number field!"
|
||||
errmsg = "Numbers only in the credit card number field!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if len(self.key_name) < 4:
|
||||
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||
errmsg = "Key name must be at <i>least</i> 4 characters long!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
QDialog.accept(self)
|
||||
|
||||
@@ -708,7 +701,7 @@ class AddAdeptDialog(QDialog):
|
||||
def __init__(self, parent=None,):
|
||||
QDialog.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.setWindowTitle(u"{0} {1}: Getting Default Adobe Digital Editions Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
self.setWindowTitle("{0} {1}: Getting Default Adobe Digital Editions Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
@@ -718,34 +711,34 @@ class AddAdeptDialog(QDialog):
|
||||
|
||||
defaultkeys = adeptkeys()
|
||||
else: # linux
|
||||
from wineutils import WineGetKeys
|
||||
from .wineutils import WineGetKeys
|
||||
|
||||
scriptpath = os.path.join(parent.parent.alfdir,u"adobekey.py")
|
||||
defaultkeys = WineGetKeys(scriptpath, u".der",parent.getwineprefix())
|
||||
scriptpath = os.path.join(parent.parent.alfdir,"adobekey.py")
|
||||
defaultkeys = WineGetKeys(scriptpath, ".der",parent.getwineprefix())
|
||||
|
||||
self.default_key = defaultkeys[0]
|
||||
except:
|
||||
traceback.print_exc()
|
||||
self.default_key = u""
|
||||
self.default_key = ""
|
||||
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
|
||||
if len(self.default_key)>0:
|
||||
data_group_box = QGroupBox(u"", self)
|
||||
data_group_box = QGroupBox("", 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"Unique Key Name:", self))
|
||||
self.key_ledit = QLineEdit(u"default_key", self)
|
||||
self.key_ledit.setToolTip(u"<p>Enter an identifying name for the current default Adobe Digital Editions key.")
|
||||
key_group.addWidget(QLabel("Unique Key Name:", self))
|
||||
self.key_ledit = QLineEdit("default_key", self)
|
||||
self.key_ledit.setToolTip("<p>Enter an identifying name for the current default Adobe Digital Editions key.")
|
||||
key_group.addWidget(self.key_ledit)
|
||||
|
||||
self.button_box.accepted.connect(self.accept)
|
||||
else:
|
||||
default_key_error = QLabel(u"The default encryption key for Adobe Digital Editions could not be found.", self)
|
||||
default_key_error = QLabel("The default encryption key for Adobe Digital Editions could not be found.", self)
|
||||
default_key_error.setAlignment(Qt.AlignHCenter)
|
||||
layout.addWidget(default_key_error)
|
||||
# if no default, bot buttons do the same
|
||||
@@ -758,19 +751,19 @@ class AddAdeptDialog(QDialog):
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
return unicode(self.key_ledit.text()).strip()
|
||||
return str(self.key_ledit.text()).strip()
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
return self.default_key.encode('hex')
|
||||
return codecs.encode(self.default_key,'hex')
|
||||
|
||||
|
||||
def accept(self):
|
||||
if len(self.key_name) == 0 or self.key_name.isspace():
|
||||
errmsg = u"All fields are required!"
|
||||
errmsg = "All fields are required!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if len(self.key_name) < 4:
|
||||
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||
errmsg = "Key name must be at <i>least</i> 4 characters long!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
QDialog.accept(self)
|
||||
|
||||
@@ -779,7 +772,7 @@ class AddKindleDialog(QDialog):
|
||||
def __init__(self, parent=None,):
|
||||
QDialog.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.setWindowTitle(u"{0} {1}: Getting Default Kindle for Mac/PC Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
self.setWindowTitle("{0} {1}: Getting Default Kindle for Mac/PC Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
@@ -789,37 +782,37 @@ class AddKindleDialog(QDialog):
|
||||
|
||||
defaultkeys = kindlekeys()
|
||||
else: # linux
|
||||
from wineutils import WineGetKeys
|
||||
from .wineutils import WineGetKeys
|
||||
|
||||
scriptpath = os.path.join(parent.parent.alfdir,u"kindlekey.py")
|
||||
defaultkeys = WineGetKeys(scriptpath, u".k4i",parent.getwineprefix())
|
||||
scriptpath = os.path.join(parent.parent.alfdir,"kindlekey.py")
|
||||
defaultkeys = WineGetKeys(scriptpath, ".k4i",parent.getwineprefix())
|
||||
|
||||
self.default_key = defaultkeys[0]
|
||||
except:
|
||||
traceback.print_exc()
|
||||
self.default_key = u""
|
||||
self.default_key = ""
|
||||
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
|
||||
if len(self.default_key)>0:
|
||||
data_group_box = QGroupBox(u"", self)
|
||||
data_group_box = QGroupBox("", 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"Unique Key Name:", self))
|
||||
self.key_ledit = QLineEdit(u"default_key", self)
|
||||
self.key_ledit.setToolTip(u"<p>Enter an identifying name for the current default Kindle for Mac/PC key.")
|
||||
key_group.addWidget(QLabel("Unique Key Name:", self))
|
||||
self.key_ledit = QLineEdit("default_key", self)
|
||||
self.key_ledit.setToolTip("<p>Enter an identifying name for the current default Kindle for Mac/PC key.")
|
||||
key_group.addWidget(self.key_ledit)
|
||||
|
||||
self.button_box.accepted.connect(self.accept)
|
||||
else:
|
||||
default_key_error = QLabel(u"The default encryption key for Kindle for Mac/PC could not be found.", self)
|
||||
default_key_error = QLabel("The default encryption key for Kindle for Mac/PC could not be found.", self)
|
||||
default_key_error.setAlignment(Qt.AlignHCenter)
|
||||
layout.addWidget(default_key_error)
|
||||
|
||||
|
||||
# if no default, both buttons do the same
|
||||
self.button_box.accepted.connect(self.reject)
|
||||
|
||||
@@ -830,7 +823,7 @@ class AddKindleDialog(QDialog):
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
return unicode(self.key_ledit.text()).strip()
|
||||
return str(self.key_ledit.text()).strip()
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
@@ -839,10 +832,10 @@ class AddKindleDialog(QDialog):
|
||||
|
||||
def accept(self):
|
||||
if len(self.key_name) == 0 or self.key_name.isspace():
|
||||
errmsg = u"All fields are required!"
|
||||
errmsg = "All fields are required!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if len(self.key_name) < 4:
|
||||
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||
errmsg = "Key name must be at <i>least</i> 4 characters long!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
QDialog.accept(self)
|
||||
|
||||
@@ -851,20 +844,20 @@ class AddSerialDialog(QDialog):
|
||||
def __init__(self, parent=None,):
|
||||
QDialog.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.setWindowTitle(u"{0} {1}: Add New EInk Kindle Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
self.setWindowTitle("{0} {1}: Add New EInk Kindle Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
data_group_box = QGroupBox(u"", self)
|
||||
data_group_box = QGroupBox("", 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 Kindle Serial Number:", self))
|
||||
key_group.addWidget(QLabel("EInk Kindle Serial Number:", self))
|
||||
self.key_ledit = QLineEdit("", self)
|
||||
self.key_ledit.setToolTip(u"Enter an eInk Kindle serial number. EInk Kindle serial numbers are 16 characters long and usually start with a 'B' or a '9'. Kindle Serial Numbers are case-sensitive, so be sure to enter the upper and lower case letters unchanged.")
|
||||
self.key_ledit.setToolTip("Enter an eInk Kindle serial number. EInk Kindle serial numbers are 16 characters long and usually start with a 'B' or a '9'. Kindle 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)
|
||||
@@ -876,18 +869,18 @@ class AddSerialDialog(QDialog):
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
return unicode(self.key_ledit.text()).strip()
|
||||
return str(self.key_ledit.text()).strip()
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
return unicode(self.key_ledit.text()).replace(' ', '')
|
||||
return str(self.key_ledit.text()).replace(' ', '')
|
||||
|
||||
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."
|
||||
errmsg = "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) != 16:
|
||||
errmsg = u"EInk Kindle Serial Numbers must be 16 characters long. This is {0:d} characters long.".format(len(self.key_name))
|
||||
errmsg = "EInk Kindle Serial Numbers must be 16 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)
|
||||
|
||||
@@ -897,36 +890,36 @@ class AddAndroidDialog(QDialog):
|
||||
|
||||
QDialog.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.setWindowTitle(u"{0} {1}: Add new Kindle for Android Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
self.setWindowTitle("{0} {1}: Add new Kindle for Android Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
|
||||
data_group_box = QGroupBox(u"", self)
|
||||
data_group_box = QGroupBox("", self)
|
||||
layout.addWidget(data_group_box)
|
||||
data_group_box_layout = QVBoxLayout()
|
||||
data_group_box.setLayout(data_group_box_layout)
|
||||
|
||||
file_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(file_group)
|
||||
add_btn = QPushButton(u"Choose Backup File", self)
|
||||
add_btn.setToolTip(u"Import Kindle for Android backup file.")
|
||||
add_btn = QPushButton("Choose Backup File", self)
|
||||
add_btn.setToolTip("Import Kindle for Android backup file.")
|
||||
add_btn.clicked.connect(self.get_android_file)
|
||||
file_group.addWidget(add_btn)
|
||||
self.selected_file_name = QLabel(u"",self)
|
||||
self.selected_file_name = QLabel("",self)
|
||||
self.selected_file_name.setAlignment(Qt.AlignHCenter)
|
||||
file_group.addWidget(self.selected_file_name)
|
||||
|
||||
|
||||
key_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(key_group)
|
||||
key_group.addWidget(QLabel(u"Unique Key Name:", self))
|
||||
self.key_ledit = QLineEdit(u"", self)
|
||||
self.key_ledit.setToolTip(u"<p>Enter an identifying name for the Android for Kindle key.")
|
||||
key_group.addWidget(QLabel("Unique Key Name:", self))
|
||||
self.key_ledit = QLineEdit("", self)
|
||||
self.key_ledit.setToolTip("<p>Enter an identifying name for the Android for Kindle key.")
|
||||
key_group.addWidget(self.key_ledit)
|
||||
#key_label = QLabel(_(''), self)
|
||||
#key_label.setAlignment(Qt.AlignHCenter)
|
||||
#data_group_box_layout.addWidget(key_label)
|
||||
|
||||
|
||||
self.button_box.accepted.connect(self.accept)
|
||||
self.button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(self.button_box)
|
||||
@@ -934,23 +927,23 @@ class AddAndroidDialog(QDialog):
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
return unicode(self.key_ledit.text()).strip()
|
||||
return str(self.key_ledit.text()).strip()
|
||||
|
||||
@property
|
||||
def file_name(self):
|
||||
return unicode(self.selected_file_name.text()).strip()
|
||||
return str(self.selected_file_name.text()).strip()
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
return self.serials_from_file
|
||||
|
||||
|
||||
def get_android_file(self):
|
||||
unique_dlg_name = PLUGIN_NAME + u"Import Kindle for Android backup file" #takes care of automatically remembering last directory
|
||||
caption = u"Select Kindle for Android backup file to add"
|
||||
filters = [(u"Kindle for Android backup files", ['db','ab','xml'])]
|
||||
unique_dlg_name = PLUGIN_NAME + "Import Kindle for Android backup file" #takes care of automatically remembering last directory
|
||||
caption = "Select Kindle for Android backup file to add"
|
||||
filters = [("Kindle for Android backup files", ['db','ab','xml'])]
|
||||
files = choose_files(self, unique_dlg_name, caption, filters, all_files=False)
|
||||
self.serials_from_file = []
|
||||
file_name = u""
|
||||
file_name = ""
|
||||
if files:
|
||||
# find the first selected file that yields some serial numbers
|
||||
for filename in files:
|
||||
@@ -961,17 +954,17 @@ class AddAndroidDialog(QDialog):
|
||||
file_name = os.path.basename(self.filename)
|
||||
self.serials_from_file.extend(file_serials)
|
||||
self.selected_file_name.setText(file_name)
|
||||
|
||||
|
||||
|
||||
def accept(self):
|
||||
if len(self.file_name) == 0 or len(self.key_value) == 0:
|
||||
errmsg = u"Please choose a Kindle for Android backup file."
|
||||
errmsg = "Please choose a Kindle for Android backup file."
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if len(self.key_name) == 0 or self.key_name.isspace():
|
||||
errmsg = u"Please enter a key name."
|
||||
errmsg = "Please enter a key name."
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if len(self.key_name) < 4:
|
||||
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||
errmsg = "Key name must be at <i>least</i> 4 characters long!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
QDialog.accept(self)
|
||||
|
||||
@@ -979,20 +972,20 @@ class AddPIDDialog(QDialog):
|
||||
def __init__(self, parent=None,):
|
||||
QDialog.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.setWindowTitle(u"{0} {1}: Add New Mobipocket PID".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
self.setWindowTitle("{0} {1}: Add New Mobipocket PID".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
data_group_box = QGroupBox(u"", self)
|
||||
data_group_box = QGroupBox("", 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"PID:", self))
|
||||
key_group.addWidget(QLabel("PID:", self))
|
||||
self.key_ledit = QLineEdit("", self)
|
||||
self.key_ledit.setToolTip(u"Enter a Mobipocket PID. Mobipocket PIDs are 8 or 10 characters long. Mobipocket PIDs are case-sensitive, so be sure to enter the upper and lower case letters unchanged.")
|
||||
self.key_ledit.setToolTip("Enter a Mobipocket PID. Mobipocket PIDs are 8 or 10 characters long. Mobipocket PIDs 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)
|
||||
@@ -1004,18 +997,18 @@ class AddPIDDialog(QDialog):
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
return unicode(self.key_ledit.text()).strip()
|
||||
return str(self.key_ledit.text()).strip()
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
return unicode(self.key_ledit.text()).strip()
|
||||
return str(self.key_ledit.text()).strip()
|
||||
|
||||
def accept(self):
|
||||
if len(self.key_name) == 0 or self.key_name.isspace():
|
||||
errmsg = u"Please enter a Mobipocket PID or click Cancel in the dialog."
|
||||
errmsg = "Please enter a Mobipocket PID 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) != 8 and len(self.key_name) != 10:
|
||||
errmsg = u"Mobipocket PIDs must be 8 or 10 characters long. This is {0:d} characters long.".format(len(self.key_name))
|
||||
errmsg = "Mobipocket PIDs must be 8 or 10 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)
|
||||
|
||||
|
||||
@@ -1,20 +1,29 @@
|
||||
#! /usr/bin/python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||
# For use with Topaz Scripts Version 2.6
|
||||
|
||||
from __future__ import print_function
|
||||
class Unbuffered:
|
||||
# For use with Topaz Scripts Version 2.6
|
||||
# Python 3, September 2020
|
||||
|
||||
# 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):
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
if isinstance(data, str):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.buffer.write(data)
|
||||
self.stream.buffer.flush()
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
import sys
|
||||
sys.stdout=Unbuffered(sys.stdout)
|
||||
|
||||
import csv
|
||||
import os
|
||||
import getopt
|
||||
@@ -47,7 +56,7 @@ def readEncodedNumber(file):
|
||||
c = file.read(1)
|
||||
if (len(c) == 0):
|
||||
return None
|
||||
data = ord(c)
|
||||
data = c[0]
|
||||
datax = (datax <<7) + (data & 0x7F)
|
||||
data = datax
|
||||
|
||||
@@ -107,7 +116,7 @@ def readString(file):
|
||||
def convert(i):
|
||||
result = ''
|
||||
val = encodeNumber(i)
|
||||
for j in xrange(len(val)):
|
||||
for j in range(len(val)):
|
||||
c = ord(val[j:j+1])
|
||||
result += '%02x' % c
|
||||
return result
|
||||
@@ -121,10 +130,10 @@ class Dictionary(object):
|
||||
def __init__(self, dictFile):
|
||||
self.filename = dictFile
|
||||
self.size = 0
|
||||
self.fo = file(dictFile,'rb')
|
||||
self.fo = open(dictFile,'rb')
|
||||
self.stable = []
|
||||
self.size = readEncodedNumber(self.fo)
|
||||
for i in xrange(self.size):
|
||||
for i in range(self.size):
|
||||
self.stable.append(self.escapestr(readString(self.fo)))
|
||||
self.pos = 0
|
||||
|
||||
@@ -151,7 +160,7 @@ class Dictionary(object):
|
||||
return self.pos
|
||||
|
||||
def dumpDict(self):
|
||||
for i in xrange(self.size):
|
||||
for i in range(self.size):
|
||||
print("%d %s %s" % (i, convert(i), self.stable[i]))
|
||||
return
|
||||
|
||||
@@ -161,7 +170,7 @@ class Dictionary(object):
|
||||
|
||||
class PageParser(object):
|
||||
def __init__(self, filename, dict, debug, flat_xml):
|
||||
self.fo = file(filename,'rb')
|
||||
self.fo = open(filename,'rb')
|
||||
self.id = os.path.basename(filename).replace('.dat','')
|
||||
self.dict = dict
|
||||
self.debug = debug
|
||||
@@ -179,232 +188,232 @@ class PageParser(object):
|
||||
# tag : (number of arguments, argument type, subtags present, special case of subtags presents when escaped)
|
||||
|
||||
token_tags = {
|
||||
'x' : (1, 'scalar_number', 0, 0),
|
||||
'y' : (1, 'scalar_number', 0, 0),
|
||||
'h' : (1, 'scalar_number', 0, 0),
|
||||
'w' : (1, 'scalar_number', 0, 0),
|
||||
'firstWord' : (1, 'scalar_number', 0, 0),
|
||||
'lastWord' : (1, 'scalar_number', 0, 0),
|
||||
'rootID' : (1, 'scalar_number', 0, 0),
|
||||
'stemID' : (1, 'scalar_number', 0, 0),
|
||||
'type' : (1, 'scalar_text', 0, 0),
|
||||
b'x' : (1, 'scalar_number', 0, 0),
|
||||
b'y' : (1, 'scalar_number', 0, 0),
|
||||
b'h' : (1, 'scalar_number', 0, 0),
|
||||
b'w' : (1, 'scalar_number', 0, 0),
|
||||
b'firstWord' : (1, 'scalar_number', 0, 0),
|
||||
b'lastWord' : (1, 'scalar_number', 0, 0),
|
||||
b'rootID' : (1, 'scalar_number', 0, 0),
|
||||
b'stemID' : (1, 'scalar_number', 0, 0),
|
||||
b'type' : (1, 'scalar_text', 0, 0),
|
||||
|
||||
'info' : (0, 'number', 1, 0),
|
||||
b'info' : (0, 'number', 1, 0),
|
||||
|
||||
'info.word' : (0, 'number', 1, 1),
|
||||
'info.word.ocrText' : (1, 'text', 0, 0),
|
||||
'info.word.firstGlyph' : (1, 'raw', 0, 0),
|
||||
'info.word.lastGlyph' : (1, 'raw', 0, 0),
|
||||
'info.word.bl' : (1, 'raw', 0, 0),
|
||||
'info.word.link_id' : (1, 'number', 0, 0),
|
||||
b'info.word' : (0, 'number', 1, 1),
|
||||
b'info.word.ocrText' : (1, 'text', 0, 0),
|
||||
b'info.word.firstGlyph' : (1, 'raw', 0, 0),
|
||||
b'info.word.lastGlyph' : (1, 'raw', 0, 0),
|
||||
b'info.word.bl' : (1, 'raw', 0, 0),
|
||||
b'info.word.link_id' : (1, 'number', 0, 0),
|
||||
|
||||
'glyph' : (0, 'number', 1, 1),
|
||||
'glyph.x' : (1, 'number', 0, 0),
|
||||
'glyph.y' : (1, 'number', 0, 0),
|
||||
'glyph.glyphID' : (1, 'number', 0, 0),
|
||||
b'glyph' : (0, 'number', 1, 1),
|
||||
b'glyph.x' : (1, 'number', 0, 0),
|
||||
b'glyph.y' : (1, 'number', 0, 0),
|
||||
b'glyph.glyphID' : (1, 'number', 0, 0),
|
||||
|
||||
'dehyphen' : (0, 'number', 1, 1),
|
||||
'dehyphen.rootID' : (1, 'number', 0, 0),
|
||||
'dehyphen.stemID' : (1, 'number', 0, 0),
|
||||
'dehyphen.stemPage' : (1, 'number', 0, 0),
|
||||
'dehyphen.sh' : (1, 'number', 0, 0),
|
||||
b'dehyphen' : (0, 'number', 1, 1),
|
||||
b'dehyphen.rootID' : (1, 'number', 0, 0),
|
||||
b'dehyphen.stemID' : (1, 'number', 0, 0),
|
||||
b'dehyphen.stemPage' : (1, 'number', 0, 0),
|
||||
b'dehyphen.sh' : (1, 'number', 0, 0),
|
||||
|
||||
'links' : (0, 'number', 1, 1),
|
||||
'links.page' : (1, 'number', 0, 0),
|
||||
'links.rel' : (1, 'number', 0, 0),
|
||||
'links.row' : (1, 'number', 0, 0),
|
||||
'links.title' : (1, 'text', 0, 0),
|
||||
'links.href' : (1, 'text', 0, 0),
|
||||
'links.type' : (1, 'text', 0, 0),
|
||||
'links.id' : (1, 'number', 0, 0),
|
||||
b'links' : (0, 'number', 1, 1),
|
||||
b'links.page' : (1, 'number', 0, 0),
|
||||
b'links.rel' : (1, 'number', 0, 0),
|
||||
b'links.row' : (1, 'number', 0, 0),
|
||||
b'links.title' : (1, 'text', 0, 0),
|
||||
b'links.href' : (1, 'text', 0, 0),
|
||||
b'links.type' : (1, 'text', 0, 0),
|
||||
b'links.id' : (1, 'number', 0, 0),
|
||||
|
||||
'paraCont' : (0, 'number', 1, 1),
|
||||
'paraCont.rootID' : (1, 'number', 0, 0),
|
||||
'paraCont.stemID' : (1, 'number', 0, 0),
|
||||
'paraCont.stemPage' : (1, 'number', 0, 0),
|
||||
b'paraCont' : (0, 'number', 1, 1),
|
||||
b'paraCont.rootID' : (1, 'number', 0, 0),
|
||||
b'paraCont.stemID' : (1, 'number', 0, 0),
|
||||
b'paraCont.stemPage' : (1, 'number', 0, 0),
|
||||
|
||||
'paraStems' : (0, 'number', 1, 1),
|
||||
'paraStems.stemID' : (1, 'number', 0, 0),
|
||||
b'paraStems' : (0, 'number', 1, 1),
|
||||
b'paraStems.stemID' : (1, 'number', 0, 0),
|
||||
|
||||
'wordStems' : (0, 'number', 1, 1),
|
||||
'wordStems.stemID' : (1, 'number', 0, 0),
|
||||
b'wordStems' : (0, 'number', 1, 1),
|
||||
b'wordStems.stemID' : (1, 'number', 0, 0),
|
||||
|
||||
'empty' : (1, 'snippets', 1, 0),
|
||||
b'empty' : (1, 'snippets', 1, 0),
|
||||
|
||||
'page' : (1, 'snippets', 1, 0),
|
||||
'page.class' : (1, 'scalar_text', 0, 0),
|
||||
'page.pageid' : (1, 'scalar_text', 0, 0),
|
||||
'page.pagelabel' : (1, 'scalar_text', 0, 0),
|
||||
'page.type' : (1, 'scalar_text', 0, 0),
|
||||
'page.h' : (1, 'scalar_number', 0, 0),
|
||||
'page.w' : (1, 'scalar_number', 0, 0),
|
||||
'page.startID' : (1, 'scalar_number', 0, 0),
|
||||
b'page' : (1, 'snippets', 1, 0),
|
||||
b'page.class' : (1, 'scalar_text', 0, 0),
|
||||
b'page.pageid' : (1, 'scalar_text', 0, 0),
|
||||
b'page.pagelabel' : (1, 'scalar_text', 0, 0),
|
||||
b'page.type' : (1, 'scalar_text', 0, 0),
|
||||
b'page.h' : (1, 'scalar_number', 0, 0),
|
||||
b'page.w' : (1, 'scalar_number', 0, 0),
|
||||
b'page.startID' : (1, 'scalar_number', 0, 0),
|
||||
|
||||
'group' : (1, 'snippets', 1, 0),
|
||||
'group.class' : (1, 'scalar_text', 0, 0),
|
||||
'group.type' : (1, 'scalar_text', 0, 0),
|
||||
'group._tag' : (1, 'scalar_text', 0, 0),
|
||||
'group.orientation': (1, 'scalar_text', 0, 0),
|
||||
b'group' : (1, 'snippets', 1, 0),
|
||||
b'group.class' : (1, 'scalar_text', 0, 0),
|
||||
b'group.type' : (1, 'scalar_text', 0, 0),
|
||||
b'group._tag' : (1, 'scalar_text', 0, 0),
|
||||
b'group.orientation': (1, 'scalar_text', 0, 0),
|
||||
|
||||
'region' : (1, 'snippets', 1, 0),
|
||||
'region.class' : (1, 'scalar_text', 0, 0),
|
||||
'region.type' : (1, 'scalar_text', 0, 0),
|
||||
'region.x' : (1, 'scalar_number', 0, 0),
|
||||
'region.y' : (1, 'scalar_number', 0, 0),
|
||||
'region.h' : (1, 'scalar_number', 0, 0),
|
||||
'region.w' : (1, 'scalar_number', 0, 0),
|
||||
'region.orientation' : (1, 'scalar_text', 0, 0),
|
||||
b'region' : (1, 'snippets', 1, 0),
|
||||
b'region.class' : (1, 'scalar_text', 0, 0),
|
||||
b'region.type' : (1, 'scalar_text', 0, 0),
|
||||
b'region.x' : (1, 'scalar_number', 0, 0),
|
||||
b'region.y' : (1, 'scalar_number', 0, 0),
|
||||
b'region.h' : (1, 'scalar_number', 0, 0),
|
||||
b'region.w' : (1, 'scalar_number', 0, 0),
|
||||
b'region.orientation' : (1, 'scalar_text', 0, 0),
|
||||
|
||||
'empty_text_region' : (1, 'snippets', 1, 0),
|
||||
b'empty_text_region' : (1, 'snippets', 1, 0),
|
||||
|
||||
'img' : (1, 'snippets', 1, 0),
|
||||
'img.x' : (1, 'scalar_number', 0, 0),
|
||||
'img.y' : (1, 'scalar_number', 0, 0),
|
||||
'img.h' : (1, 'scalar_number', 0, 0),
|
||||
'img.w' : (1, 'scalar_number', 0, 0),
|
||||
'img.src' : (1, 'scalar_number', 0, 0),
|
||||
'img.color_src' : (1, 'scalar_number', 0, 0),
|
||||
'img.gridSize' : (1, 'scalar_number', 0, 0),
|
||||
'img.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
||||
'img.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
||||
'img.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
||||
'img.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
||||
'img.image_type' : (1, 'scalar_number', 0, 0),
|
||||
b'img' : (1, 'snippets', 1, 0),
|
||||
b'img.x' : (1, 'scalar_number', 0, 0),
|
||||
b'img.y' : (1, 'scalar_number', 0, 0),
|
||||
b'img.h' : (1, 'scalar_number', 0, 0),
|
||||
b'img.w' : (1, 'scalar_number', 0, 0),
|
||||
b'img.src' : (1, 'scalar_number', 0, 0),
|
||||
b'img.color_src' : (1, 'scalar_number', 0, 0),
|
||||
b'img.gridSize' : (1, 'scalar_number', 0, 0),
|
||||
b'img.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'img.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'img.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'img.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'img.image_type' : (1, 'scalar_number', 0, 0),
|
||||
|
||||
'paragraph' : (1, 'snippets', 1, 0),
|
||||
'paragraph.class' : (1, 'scalar_text', 0, 0),
|
||||
'paragraph.firstWord' : (1, 'scalar_number', 0, 0),
|
||||
'paragraph.lastWord' : (1, 'scalar_number', 0, 0),
|
||||
'paragraph.lastWord' : (1, 'scalar_number', 0, 0),
|
||||
'paragraph.gridSize' : (1, 'scalar_number', 0, 0),
|
||||
'paragraph.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
||||
'paragraph.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
||||
'paragraph.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
||||
'paragraph.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'paragraph' : (1, 'snippets', 1, 0),
|
||||
b'paragraph.class' : (1, 'scalar_text', 0, 0),
|
||||
b'paragraph.firstWord' : (1, 'scalar_number', 0, 0),
|
||||
b'paragraph.lastWord' : (1, 'scalar_number', 0, 0),
|
||||
b'paragraph.lastWord' : (1, 'scalar_number', 0, 0),
|
||||
b'paragraph.gridSize' : (1, 'scalar_number', 0, 0),
|
||||
b'paragraph.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'paragraph.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'paragraph.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'paragraph.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
||||
|
||||
|
||||
'word_semantic' : (1, 'snippets', 1, 1),
|
||||
'word_semantic.type' : (1, 'scalar_text', 0, 0),
|
||||
'word_semantic.class' : (1, 'scalar_text', 0, 0),
|
||||
'word_semantic.firstWord' : (1, 'scalar_number', 0, 0),
|
||||
'word_semantic.lastWord' : (1, 'scalar_number', 0, 0),
|
||||
'word_semantic.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
||||
'word_semantic.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
||||
'word_semantic.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
||||
'word_semantic.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'word_semantic' : (1, 'snippets', 1, 1),
|
||||
b'word_semantic.type' : (1, 'scalar_text', 0, 0),
|
||||
b'word_semantic.class' : (1, 'scalar_text', 0, 0),
|
||||
b'word_semantic.firstWord' : (1, 'scalar_number', 0, 0),
|
||||
b'word_semantic.lastWord' : (1, 'scalar_number', 0, 0),
|
||||
b'word_semantic.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'word_semantic.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'word_semantic.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'word_semantic.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
||||
|
||||
'word' : (1, 'snippets', 1, 0),
|
||||
'word.type' : (1, 'scalar_text', 0, 0),
|
||||
'word.class' : (1, 'scalar_text', 0, 0),
|
||||
'word.firstGlyph' : (1, 'scalar_number', 0, 0),
|
||||
'word.lastGlyph' : (1, 'scalar_number', 0, 0),
|
||||
b'word' : (1, 'snippets', 1, 0),
|
||||
b'word.type' : (1, 'scalar_text', 0, 0),
|
||||
b'word.class' : (1, 'scalar_text', 0, 0),
|
||||
b'word.firstGlyph' : (1, 'scalar_number', 0, 0),
|
||||
b'word.lastGlyph' : (1, 'scalar_number', 0, 0),
|
||||
|
||||
'_span' : (1, 'snippets', 1, 0),
|
||||
'_span.class' : (1, 'scalar_text', 0, 0),
|
||||
'_span.firstWord' : (1, 'scalar_number', 0, 0),
|
||||
'_span.lastWord' : (1, 'scalar_number', 0, 0),
|
||||
'_span.gridSize' : (1, 'scalar_number', 0, 0),
|
||||
'_span.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
||||
'_span.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
||||
'_span.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
||||
'_span.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'_span' : (1, 'snippets', 1, 0),
|
||||
b'_span.class' : (1, 'scalar_text', 0, 0),
|
||||
b'_span.firstWord' : (1, 'scalar_number', 0, 0),
|
||||
b'_span.lastWord' : (1, 'scalar_number', 0, 0),
|
||||
b'_span.gridSize' : (1, 'scalar_number', 0, 0),
|
||||
b'_span.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'_span.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'_span.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'_span.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
||||
|
||||
'span' : (1, 'snippets', 1, 0),
|
||||
'span.firstWord' : (1, 'scalar_number', 0, 0),
|
||||
'span.lastWord' : (1, 'scalar_number', 0, 0),
|
||||
'span.gridSize' : (1, 'scalar_number', 0, 0),
|
||||
'span.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
||||
'span.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
||||
'span.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
||||
'span.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'span' : (1, 'snippets', 1, 0),
|
||||
b'span.firstWord' : (1, 'scalar_number', 0, 0),
|
||||
b'span.lastWord' : (1, 'scalar_number', 0, 0),
|
||||
b'span.gridSize' : (1, 'scalar_number', 0, 0),
|
||||
b'span.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'span.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'span.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'span.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
||||
|
||||
'extratokens' : (1, 'snippets', 1, 0),
|
||||
'extratokens.class' : (1, 'scalar_text', 0, 0),
|
||||
'extratokens.type' : (1, 'scalar_text', 0, 0),
|
||||
'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0),
|
||||
'extratokens.lastGlyph' : (1, 'scalar_number', 0, 0),
|
||||
'extratokens.gridSize' : (1, 'scalar_number', 0, 0),
|
||||
'extratokens.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
||||
'extratokens.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
||||
'extratokens.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
||||
'extratokens.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'extratokens' : (1, 'snippets', 1, 0),
|
||||
b'extratokens.class' : (1, 'scalar_text', 0, 0),
|
||||
b'extratokens.type' : (1, 'scalar_text', 0, 0),
|
||||
b'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0),
|
||||
b'extratokens.lastGlyph' : (1, 'scalar_number', 0, 0),
|
||||
b'extratokens.gridSize' : (1, 'scalar_number', 0, 0),
|
||||
b'extratokens.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'extratokens.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'extratokens.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
||||
b'extratokens.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
||||
|
||||
'glyph.h' : (1, 'number', 0, 0),
|
||||
'glyph.w' : (1, 'number', 0, 0),
|
||||
'glyph.use' : (1, 'number', 0, 0),
|
||||
'glyph.vtx' : (1, 'number', 0, 1),
|
||||
'glyph.len' : (1, 'number', 0, 1),
|
||||
'glyph.dpi' : (1, 'number', 0, 0),
|
||||
'vtx' : (0, 'number', 1, 1),
|
||||
'vtx.x' : (1, 'number', 0, 0),
|
||||
'vtx.y' : (1, 'number', 0, 0),
|
||||
'len' : (0, 'number', 1, 1),
|
||||
'len.n' : (1, 'number', 0, 0),
|
||||
b'glyph.h' : (1, 'number', 0, 0),
|
||||
b'glyph.w' : (1, 'number', 0, 0),
|
||||
b'glyph.use' : (1, 'number', 0, 0),
|
||||
b'glyph.vtx' : (1, 'number', 0, 1),
|
||||
b'glyph.len' : (1, 'number', 0, 1),
|
||||
b'glyph.dpi' : (1, 'number', 0, 0),
|
||||
b'vtx' : (0, 'number', 1, 1),
|
||||
b'vtx.x' : (1, 'number', 0, 0),
|
||||
b'vtx.y' : (1, 'number', 0, 0),
|
||||
b'len' : (0, 'number', 1, 1),
|
||||
b'len.n' : (1, 'number', 0, 0),
|
||||
|
||||
'book' : (1, 'snippets', 1, 0),
|
||||
'version' : (1, 'snippets', 1, 0),
|
||||
'version.FlowEdit_1_id' : (1, 'scalar_text', 0, 0),
|
||||
'version.FlowEdit_1_version' : (1, 'scalar_text', 0, 0),
|
||||
'version.Schema_id' : (1, 'scalar_text', 0, 0),
|
||||
'version.Schema_version' : (1, 'scalar_text', 0, 0),
|
||||
'version.Topaz_version' : (1, 'scalar_text', 0, 0),
|
||||
'version.WordDetailEdit_1_id' : (1, 'scalar_text', 0, 0),
|
||||
'version.WordDetailEdit_1_version' : (1, 'scalar_text', 0, 0),
|
||||
'version.ZoneEdit_1_id' : (1, 'scalar_text', 0, 0),
|
||||
'version.ZoneEdit_1_version' : (1, 'scalar_text', 0, 0),
|
||||
'version.chapterheaders' : (1, 'scalar_text', 0, 0),
|
||||
'version.creation_date' : (1, 'scalar_text', 0, 0),
|
||||
'version.header_footer' : (1, 'scalar_text', 0, 0),
|
||||
'version.init_from_ocr' : (1, 'scalar_text', 0, 0),
|
||||
'version.letter_insertion' : (1, 'scalar_text', 0, 0),
|
||||
'version.xmlinj_convert' : (1, 'scalar_text', 0, 0),
|
||||
'version.xmlinj_reflow' : (1, 'scalar_text', 0, 0),
|
||||
'version.xmlinj_transform' : (1, 'scalar_text', 0, 0),
|
||||
'version.findlists' : (1, 'scalar_text', 0, 0),
|
||||
'version.page_num' : (1, 'scalar_text', 0, 0),
|
||||
'version.page_type' : (1, 'scalar_text', 0, 0),
|
||||
'version.bad_text' : (1, 'scalar_text', 0, 0),
|
||||
'version.glyph_mismatch' : (1, 'scalar_text', 0, 0),
|
||||
'version.margins' : (1, 'scalar_text', 0, 0),
|
||||
'version.staggered_lines' : (1, 'scalar_text', 0, 0),
|
||||
'version.paragraph_continuation' : (1, 'scalar_text', 0, 0),
|
||||
'version.toc' : (1, 'scalar_text', 0, 0),
|
||||
b'book' : (1, 'snippets', 1, 0),
|
||||
b'version' : (1, 'snippets', 1, 0),
|
||||
b'version.FlowEdit_1_id' : (1, 'scalar_text', 0, 0),
|
||||
b'version.FlowEdit_1_version' : (1, 'scalar_text', 0, 0),
|
||||
b'version.Schema_id' : (1, 'scalar_text', 0, 0),
|
||||
b'version.Schema_version' : (1, 'scalar_text', 0, 0),
|
||||
b'version.Topaz_version' : (1, 'scalar_text', 0, 0),
|
||||
b'version.WordDetailEdit_1_id' : (1, 'scalar_text', 0, 0),
|
||||
b'version.WordDetailEdit_1_version' : (1, 'scalar_text', 0, 0),
|
||||
b'version.ZoneEdit_1_id' : (1, 'scalar_text', 0, 0),
|
||||
b'version.ZoneEdit_1_version' : (1, 'scalar_text', 0, 0),
|
||||
b'version.chapterheaders' : (1, 'scalar_text', 0, 0),
|
||||
b'version.creation_date' : (1, 'scalar_text', 0, 0),
|
||||
b'version.header_footer' : (1, 'scalar_text', 0, 0),
|
||||
b'version.init_from_ocr' : (1, 'scalar_text', 0, 0),
|
||||
b'version.letter_insertion' : (1, 'scalar_text', 0, 0),
|
||||
b'version.xmlinj_convert' : (1, 'scalar_text', 0, 0),
|
||||
b'version.xmlinj_reflow' : (1, 'scalar_text', 0, 0),
|
||||
b'version.xmlinj_transform' : (1, 'scalar_text', 0, 0),
|
||||
b'version.findlists' : (1, 'scalar_text', 0, 0),
|
||||
b'version.page_num' : (1, 'scalar_text', 0, 0),
|
||||
b'version.page_type' : (1, 'scalar_text', 0, 0),
|
||||
b'version.bad_text' : (1, 'scalar_text', 0, 0),
|
||||
b'version.glyph_mismatch' : (1, 'scalar_text', 0, 0),
|
||||
b'version.margins' : (1, 'scalar_text', 0, 0),
|
||||
b'version.staggered_lines' : (1, 'scalar_text', 0, 0),
|
||||
b'version.paragraph_continuation' : (1, 'scalar_text', 0, 0),
|
||||
b'version.toc' : (1, 'scalar_text', 0, 0),
|
||||
|
||||
'stylesheet' : (1, 'snippets', 1, 0),
|
||||
'style' : (1, 'snippets', 1, 0),
|
||||
'style._tag' : (1, 'scalar_text', 0, 0),
|
||||
'style.type' : (1, 'scalar_text', 0, 0),
|
||||
'style._after_type' : (1, 'scalar_text', 0, 0),
|
||||
'style._parent_type' : (1, 'scalar_text', 0, 0),
|
||||
'style._after_parent_type' : (1, 'scalar_text', 0, 0),
|
||||
'style.class' : (1, 'scalar_text', 0, 0),
|
||||
'style._after_class' : (1, 'scalar_text', 0, 0),
|
||||
'rule' : (1, 'snippets', 1, 0),
|
||||
'rule.attr' : (1, 'scalar_text', 0, 0),
|
||||
'rule.value' : (1, 'scalar_text', 0, 0),
|
||||
b'stylesheet' : (1, 'snippets', 1, 0),
|
||||
b'style' : (1, 'snippets', 1, 0),
|
||||
b'style._tag' : (1, 'scalar_text', 0, 0),
|
||||
b'style.type' : (1, 'scalar_text', 0, 0),
|
||||
b'style._after_type' : (1, 'scalar_text', 0, 0),
|
||||
b'style._parent_type' : (1, 'scalar_text', 0, 0),
|
||||
b'style._after_parent_type' : (1, 'scalar_text', 0, 0),
|
||||
b'style.class' : (1, 'scalar_text', 0, 0),
|
||||
b'style._after_class' : (1, 'scalar_text', 0, 0),
|
||||
b'rule' : (1, 'snippets', 1, 0),
|
||||
b'rule.attr' : (1, 'scalar_text', 0, 0),
|
||||
b'rule.value' : (1, 'scalar_text', 0, 0),
|
||||
|
||||
'original' : (0, 'number', 1, 1),
|
||||
'original.pnum' : (1, 'number', 0, 0),
|
||||
'original.pid' : (1, 'text', 0, 0),
|
||||
'pages' : (0, 'number', 1, 1),
|
||||
'pages.ref' : (1, 'number', 0, 0),
|
||||
'pages.id' : (1, 'number', 0, 0),
|
||||
'startID' : (0, 'number', 1, 1),
|
||||
'startID.page' : (1, 'number', 0, 0),
|
||||
'startID.id' : (1, 'number', 0, 0),
|
||||
|
||||
'median_d' : (1, 'number', 0, 0),
|
||||
'median_h' : (1, 'number', 0, 0),
|
||||
'median_firsty' : (1, 'number', 0, 0),
|
||||
'median_lasty' : (1, 'number', 0, 0),
|
||||
b'original' : (0, 'number', 1, 1),
|
||||
b'original.pnum' : (1, 'number', 0, 0),
|
||||
b'original.pid' : (1, 'text', 0, 0),
|
||||
b'pages' : (0, 'number', 1, 1),
|
||||
b'pages.ref' : (1, 'number', 0, 0),
|
||||
b'pages.id' : (1, 'number', 0, 0),
|
||||
b'startID' : (0, 'number', 1, 1),
|
||||
b'startID.page' : (1, 'number', 0, 0),
|
||||
b'startID.id' : (1, 'number', 0, 0),
|
||||
|
||||
'num_footers_maybe' : (1, 'number', 0, 0),
|
||||
'num_footers_yes' : (1, 'number', 0, 0),
|
||||
'num_headers_maybe' : (1, 'number', 0, 0),
|
||||
'num_headers_yes' : (1, 'number', 0, 0),
|
||||
b'median_d' : (1, 'number', 0, 0),
|
||||
b'median_h' : (1, 'number', 0, 0),
|
||||
b'median_firsty' : (1, 'number', 0, 0),
|
||||
b'median_lasty' : (1, 'number', 0, 0),
|
||||
|
||||
'tracking' : (1, 'number', 0, 0),
|
||||
'src' : (1, 'text', 0, 0),
|
||||
b'num_footers_maybe' : (1, 'number', 0, 0),
|
||||
b'num_footers_yes' : (1, 'number', 0, 0),
|
||||
b'num_headers_maybe' : (1, 'number', 0, 0),
|
||||
b'num_headers_yes' : (1, 'number', 0, 0),
|
||||
|
||||
b'tracking' : (1, 'number', 0, 0),
|
||||
b'src' : (1, 'text', 0, 0),
|
||||
|
||||
}
|
||||
|
||||
@@ -420,8 +429,8 @@ class PageParser(object):
|
||||
def get_tagpath(self, i):
|
||||
cnt = len(self.tagpath)
|
||||
if i < cnt : result = self.tagpath[i]
|
||||
for j in xrange(i+1, cnt) :
|
||||
result += '.' + self.tagpath[j]
|
||||
for j in range(i+1, cnt) :
|
||||
result += b'.' + self.tagpath[j]
|
||||
return result
|
||||
|
||||
|
||||
@@ -472,7 +481,7 @@ class PageParser(object):
|
||||
|
||||
if self.debug : print('Processing: ', self.get_tagpath(0))
|
||||
cnt = self.tagpath_len()
|
||||
for j in xrange(cnt):
|
||||
for j in range(cnt):
|
||||
tkn = self.get_tagpath(j)
|
||||
if tkn in self.token_tags :
|
||||
num_args = self.token_tags[tkn][0]
|
||||
@@ -496,8 +505,8 @@ class PageParser(object):
|
||||
|
||||
if (subtags == 1):
|
||||
ntags = readEncodedNumber(self.fo)
|
||||
if self.debug : print('subtags: ' + token + ' has ' + str(ntags))
|
||||
for j in xrange(ntags):
|
||||
if self.debug : print('subtags: ', token , ' has ' , str(ntags))
|
||||
for j in range(ntags):
|
||||
val = readEncodedNumber(self.fo)
|
||||
subtagres.append(self.procToken(self.dict.lookup(val)))
|
||||
|
||||
@@ -511,7 +520,7 @@ class PageParser(object):
|
||||
argres = self.decodeCMD(arg,argtype)
|
||||
else :
|
||||
# num_arg scalar arguments
|
||||
for i in xrange(num_args):
|
||||
for i in range(num_args):
|
||||
argres.append(self.formatArg(readEncodedNumber(self.fo), argtype))
|
||||
|
||||
# build the return tag
|
||||
@@ -546,7 +555,7 @@ class PageParser(object):
|
||||
result += 'of the document is indicated by snippet number sets at the\n'
|
||||
result += 'end of each snippet. \n'
|
||||
print(result)
|
||||
for i in xrange(cnt):
|
||||
for i in range(cnt):
|
||||
if self.debug: print('Snippet:',str(i))
|
||||
snippet = []
|
||||
snippet.append(i)
|
||||
@@ -565,12 +574,12 @@ class PageParser(object):
|
||||
adj = readEncodedNumber(self.fo)
|
||||
mode = mode >> 1
|
||||
x = []
|
||||
for i in xrange(cnt):
|
||||
for i in range(cnt):
|
||||
x.append(readEncodedNumber(self.fo) - adj)
|
||||
for i in xrange(mode):
|
||||
for j in xrange(1, cnt):
|
||||
for i in range(mode):
|
||||
for j in range(1, cnt):
|
||||
x[j] = x[j] + x[j - 1]
|
||||
for i in xrange(cnt):
|
||||
for i in range(cnt):
|
||||
result.append(self.formatArg(x[i],argtype))
|
||||
return result
|
||||
|
||||
@@ -604,7 +613,7 @@ class PageParser(object):
|
||||
subtagList = tag[1]
|
||||
argtype = tag[2]
|
||||
argList = tag[3]
|
||||
nname = prefix + '.' + name
|
||||
nname = prefix + b'.' + name
|
||||
nsubtaglist = []
|
||||
for j in subtagList:
|
||||
nsubtaglist.append(self.updateName(j,prefix))
|
||||
@@ -653,34 +662,34 @@ class PageParser(object):
|
||||
subtagList = node[1]
|
||||
argtype = node[2]
|
||||
argList = node[3]
|
||||
fullpathname = name.split('.')
|
||||
fullpathname = name.split(b'.')
|
||||
nodename = fullpathname.pop()
|
||||
ilvl = len(fullpathname)
|
||||
indent = ' ' * (3 * ilvl)
|
||||
indent = b' ' * (3 * ilvl)
|
||||
rlst = []
|
||||
rlst.append(indent + '<' + nodename + '>')
|
||||
rlst.append(indent + b'<' + nodename + b'>')
|
||||
if len(argList) > 0:
|
||||
alst = []
|
||||
for j in argList:
|
||||
if (argtype == 'text') or (argtype == 'scalar_text') :
|
||||
alst.append(j + '|')
|
||||
if (argtype == b'text') or (argtype == b'scalar_text') :
|
||||
alst.append(j + b'|')
|
||||
else :
|
||||
alst.append(str(j) + ',')
|
||||
argres = "".join(alst)
|
||||
alst.append(str(j).encode('utf-8') + b',')
|
||||
argres = b"".join(alst)
|
||||
argres = argres[0:-1]
|
||||
if argtype == 'snippets' :
|
||||
rlst.append('snippets:' + argres)
|
||||
if argtype == b'snippets' :
|
||||
rlst.append(b'snippets:' + argres)
|
||||
else :
|
||||
rlst.append(argres)
|
||||
if len(subtagList) > 0 :
|
||||
rlst.append('\n')
|
||||
rlst.append(b'\n')
|
||||
for j in subtagList:
|
||||
if len(j) > 0 :
|
||||
rlst.append(self.formatTag(j))
|
||||
rlst.append(indent + '</' + nodename + '>\n')
|
||||
rlst.append(indent + b'</' + nodename + b'>\n')
|
||||
else:
|
||||
rlst.append('</' + nodename + '>\n')
|
||||
return "".join(rlst)
|
||||
rlst.append(b'</' + nodename + b'>\n')
|
||||
return b"".join(rlst)
|
||||
|
||||
|
||||
# flatten tag
|
||||
@@ -695,20 +704,20 @@ class PageParser(object):
|
||||
alst = []
|
||||
for j in argList:
|
||||
if (argtype == 'text') or (argtype == 'scalar_text') :
|
||||
alst.append(j + '|')
|
||||
alst.append(j + b'|')
|
||||
else :
|
||||
alst.append(str(j) + '|')
|
||||
argres = "".join(alst)
|
||||
alst.append(str(j).encode('utf-8') + b'|')
|
||||
argres = b"".join(alst)
|
||||
argres = argres[0:-1]
|
||||
if argtype == 'snippets' :
|
||||
rlst.append('.snippets=' + argres)
|
||||
if argtype == b'snippets' :
|
||||
rlst.append(b'.snippets=' + argres)
|
||||
else :
|
||||
rlst.append('=' + argres)
|
||||
rlst.append('\n')
|
||||
rlst.append(b'=' + argres)
|
||||
rlst.append(b'\n')
|
||||
for j in subtagList:
|
||||
if len(j) > 0 :
|
||||
rlst.append(self.flattenTag(j))
|
||||
return "".join(rlst)
|
||||
return b"".join(rlst)
|
||||
|
||||
|
||||
# reduce create xml output
|
||||
@@ -720,7 +729,7 @@ class PageParser(object):
|
||||
rlst.append(self.flattenTag(j))
|
||||
else:
|
||||
rlst.append(self.formatTag(j))
|
||||
result = "".join(rlst)
|
||||
result = b"".join(rlst)
|
||||
if self.debug : print(result)
|
||||
return result
|
||||
|
||||
@@ -738,16 +747,16 @@ class PageParser(object):
|
||||
|
||||
# peek at the first bytes to see what type of file it is
|
||||
magic = self.fo.read(9)
|
||||
if (magic[0:1] == 'p') and (magic[2:9] == 'marker_'):
|
||||
first_token = 'info'
|
||||
elif (magic[0:1] == 'p') and (magic[2:9] == '__PAGE_'):
|
||||
if (magic[0:1] == b'p') and (magic[2:9] == b'marker_'):
|
||||
first_token = b'info'
|
||||
elif (magic[0:1] == b'p') and (magic[2:9] == b'__PAGE_'):
|
||||
skip = self.fo.read(2)
|
||||
first_token = 'info'
|
||||
elif (magic[0:1] == 'p') and (magic[2:8] == '_PAGE_'):
|
||||
first_token = 'info'
|
||||
elif (magic[0:1] == 'g') and (magic[2:9] == '__GLYPH'):
|
||||
first_token = b'info'
|
||||
elif (magic[0:1] == b'p') and (magic[2:8] == b'_PAGE_'):
|
||||
first_token = b'info'
|
||||
elif (magic[0:1] == b'g') and (magic[2:9] == b'__GLYPH'):
|
||||
skip = self.fo.read(3)
|
||||
first_token = 'info'
|
||||
first_token = b'info'
|
||||
else :
|
||||
# other0.dat file
|
||||
first_token = None
|
||||
@@ -769,7 +778,7 @@ class PageParser(object):
|
||||
break
|
||||
|
||||
if (v == 0x72):
|
||||
self.doLoop72('number')
|
||||
self.doLoop72(b'number')
|
||||
elif (v > 0) and (v < self.dict.getSize()) :
|
||||
tag = self.procToken(self.dict.lookup(v))
|
||||
if len(tag) > 0 :
|
||||
@@ -780,7 +789,7 @@ class PageParser(object):
|
||||
if (v == 0):
|
||||
if (self.peek(1) == 0x5f):
|
||||
skip = self.fo.read(1)
|
||||
first_token = 'info'
|
||||
first_token = b'info'
|
||||
|
||||
# now do snippet injection
|
||||
if len(self.snippetList) > 0 :
|
||||
@@ -800,14 +809,14 @@ class PageParser(object):
|
||||
|
||||
def fromData(dict, fname):
|
||||
flat_xml = True
|
||||
debug = False
|
||||
debug = True
|
||||
pp = PageParser(fname, dict, debug, flat_xml)
|
||||
xmlpage = pp.process()
|
||||
return xmlpage
|
||||
|
||||
def getXML(dict, fname):
|
||||
flat_xml = False
|
||||
debug = False
|
||||
debug = True
|
||||
pp = PageParser(fname, dict, debug, flat_xml)
|
||||
xmlpage = pp.process()
|
||||
return xmlpage
|
||||
@@ -832,9 +841,11 @@ def usage():
|
||||
#
|
||||
|
||||
def main(argv):
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
dictFile = ""
|
||||
pageFile = ""
|
||||
debug = False
|
||||
debug = True
|
||||
flat_xml = False
|
||||
printOutput = False
|
||||
if len(argv) == 0:
|
||||
@@ -844,7 +855,7 @@ def main(argv):
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], "hd", ["flat-xml"])
|
||||
|
||||
except getopt.GetoptError, err:
|
||||
except getopt.GetoptError as err:
|
||||
|
||||
# print help information and exit:
|
||||
print(str(err)) # will print something like "option -a not recognized"
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# base64.py, version 1.0
|
||||
# Copyright © 2010 Apprentice Alf
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3 or
|
||||
# later. <http://www.gnu.org/licenses/>
|
||||
|
||||
# Revision history:
|
||||
# 1 - Initial release. To allow Applescript to do base64 encoding
|
||||
|
||||
"""
|
||||
Provide base64 encoding.
|
||||
"""
|
||||
|
||||
from __future__ import with_statement
|
||||
from __future__ import print_function
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
|
||||
import sys
|
||||
import os
|
||||
import base64
|
||||
|
||||
def usage(progname):
|
||||
print("Applies base64 encoding to the supplied file, sending to standard output")
|
||||
print("Usage:")
|
||||
print(" %s <infile>" % progname)
|
||||
|
||||
def cli_main(argv=sys.argv):
|
||||
progname = os.path.basename(argv[0])
|
||||
|
||||
if len(argv)<2:
|
||||
usage(progname)
|
||||
sys.exit(2)
|
||||
|
||||
keypath = argv[1]
|
||||
with open(keypath, 'rb') as f:
|
||||
keyder = f.read()
|
||||
print(keyder.encode('base64'))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(cli_main())
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# This is a python script. You need a Python interpreter to run it.
|
||||
# For example, ActiveState Python, which exists for windows.
|
||||
@@ -10,6 +11,7 @@
|
||||
# Changelog epubtest
|
||||
# 1.00 - Cut to epubtest.py, testing ePub files only by Apprentice Alf
|
||||
# 1.01 - Added routine for use by Windows DeDRM
|
||||
# 2.00 - Python 3, September 2020
|
||||
#
|
||||
# Written in 2011 by Paul Durrant
|
||||
# Released with unlicense. See http://unlicense.org/
|
||||
@@ -44,10 +46,7 @@
|
||||
# It's still polite to give attribution if you do reuse this code.
|
||||
#
|
||||
|
||||
from __future__ import with_statement
|
||||
from __future__ import print_function
|
||||
|
||||
__version__ = '1.01'
|
||||
__version__ = '2.0'
|
||||
|
||||
import sys, struct, os, traceback
|
||||
import zlib
|
||||
@@ -67,10 +66,11 @@ class SafeUnbuffered:
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
if isinstance(data, str):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
self.stream.buffer.write(data)
|
||||
self.stream.buffer.flush()
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
@@ -108,15 +108,13 @@ def unicode_argv():
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
range(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"epubtest.py"]
|
||||
return ["epubtest.py"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = "utf-8"
|
||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||
argvencoding = sys.stdin.encoding or "utf-8"
|
||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||
|
||||
_FILENAME_LEN_OFFSET = 26
|
||||
_EXTRA_LEN_OFFSET = 28
|
||||
@@ -171,12 +169,12 @@ def getfiledata(file, zi):
|
||||
|
||||
def encryption(infile):
|
||||
# returns encryption: one of Unencrypted, Adobe, B&N and Unknown
|
||||
encryption = "Unknown"
|
||||
encryption = "Error When Checking."
|
||||
try:
|
||||
with open(infile,'rb') as infileobject:
|
||||
bookdata = infileobject.read(58)
|
||||
# Check for Zip
|
||||
if bookdata[0:0+2] == "PK":
|
||||
if bookdata[0:0+2] == b"PK":
|
||||
foundrights = False
|
||||
foundencryption = False
|
||||
inzip = zipfile.ZipFile(infile,'r')
|
||||
@@ -200,7 +198,10 @@ def encryption(infile):
|
||||
|
||||
def main():
|
||||
argv=unicode_argv()
|
||||
print(encryption(argv[1]))
|
||||
if len(argv) < 2:
|
||||
print("Give an ePub file as a parameter.")
|
||||
else:
|
||||
print(encryption(argv[1]))
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
150
DeDRM_plugin/erdr2pml.py
Normal file → Executable file
150
DeDRM_plugin/erdr2pml.py
Normal file → Executable file
@@ -1,13 +1,9 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# erdr2pml.py
|
||||
# Copyright © 2008 The Dark Reverser
|
||||
# Copyright © 2008-2020 The Dark Reverser, Apprentice Harper et al.
|
||||
#
|
||||
# Modified 2008–2012 by some_updates, DiapDealer and Apprentice Alf
|
||||
|
||||
# This is a python script. You need a Python interpreter to run it.
|
||||
# For example, ActiveState Python, which exists for windows.
|
||||
# Changelog
|
||||
#
|
||||
# Based on ereader2html version 0.08 plus some later small fixes
|
||||
@@ -67,8 +63,9 @@
|
||||
# - Ignore sidebars for dictionaries (different format?)
|
||||
# 0.22 - Unicode and plugin support, different image folders for PMLZ and source
|
||||
# 0.23 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 1.00 - Added Python 3 compatibility for calibre 5.0
|
||||
|
||||
__version__='0.23'
|
||||
__version__='1.00'
|
||||
|
||||
import sys, re
|
||||
import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile, traceback
|
||||
@@ -88,10 +85,10 @@ class SafeUnbuffered:
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
if isinstance(data,str):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
self.stream.buffer.write(data)
|
||||
self.stream.buffer.flush()
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
@@ -126,15 +123,13 @@ def unicode_argv():
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
range(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"mobidedrm.py"]
|
||||
return ["mobidedrm.py"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = "utf-8"
|
||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||
argvencoding = sys.stdin.encoding or "utf-8"
|
||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||
|
||||
Des = None
|
||||
if iswindows:
|
||||
@@ -200,17 +195,17 @@ class Sectionizer(object):
|
||||
bkType = "Book"
|
||||
|
||||
def __init__(self, filename, ident):
|
||||
self.contents = file(filename, 'rb').read()
|
||||
self.contents = open(filename, 'rb').read()
|
||||
self.header = self.contents[0:72]
|
||||
self.num_sections, = struct.unpack('>H', self.contents[76:78])
|
||||
# Dictionary or normal content (TODO: Not hard-coded)
|
||||
if self.header[0x3C:0x3C+8] != ident:
|
||||
if self.header[0x3C:0x3C+8] == "PDctPPrs":
|
||||
if self.header[0x3C:0x3C+8] == b"PDctPPrs":
|
||||
self.bkType = "Dict"
|
||||
else:
|
||||
raise ValueError('Invalid file format')
|
||||
self.sections = []
|
||||
for i in xrange(self.num_sections):
|
||||
for i in range(self.num_sections):
|
||||
offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.contents[78+i*8:78+i*8+8])
|
||||
flags, val = a1, a2<<16|a3<<8|a4
|
||||
self.sections.append( (offset, flags, val) )
|
||||
@@ -229,28 +224,28 @@ class Sectionizer(object):
|
||||
# and with some (heavily edited) code from Paul Durrant's kindlenamer.py
|
||||
def sanitizeFileName(name):
|
||||
# substitute filename unfriendly characters
|
||||
name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" – ").replace(u": ",u" – ").replace(u":",u"—").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'")
|
||||
name = name.replace("<","[").replace(">","]").replace(" : "," – ").replace(": "," – ").replace(":","—").replace("/","_").replace("\\","_").replace("|","_").replace("\"","\'")
|
||||
# delete control characters
|
||||
name = u"".join(char for char in name if ord(char)>=32)
|
||||
name = "".join(char for char in name if ord(char)>=32)
|
||||
# white space to single space, delete leading and trailing while space
|
||||
name = re.sub(ur"\s", u" ", name).strip()
|
||||
name = re.sub(r"\s", " ", name).strip()
|
||||
# remove leading dots
|
||||
while len(name)>0 and name[0] == u".":
|
||||
while len(name)>0 and name[0] == ".":
|
||||
name = name[1:]
|
||||
# remove trailing dots (Windows doesn't like them)
|
||||
if name.endswith(u'.'):
|
||||
if name.endswith("."):
|
||||
name = name[:-1]
|
||||
return name
|
||||
|
||||
def fixKey(key):
|
||||
def fixByte(b):
|
||||
return b ^ ((b ^ (b<<1) ^ (b<<2) ^ (b<<3) ^ (b<<4) ^ (b<<5) ^ (b<<6) ^ (b<<7) ^ 0x80) & 0x80)
|
||||
return "".join([chr(fixByte(ord(a))) for a in key])
|
||||
return bytes([fixByte(a) for a in key])
|
||||
|
||||
def deXOR(text, sp, table):
|
||||
r=''
|
||||
j = sp
|
||||
for i in xrange(len(text)):
|
||||
for i in range(len(text)):
|
||||
r += chr(ord(table[j]) ^ ord(text[i]))
|
||||
j = j + 1
|
||||
if j == len(table):
|
||||
@@ -274,13 +269,13 @@ class EreaderProcessor(object):
|
||||
raise ValueError('incorrect eReader version (error 2)')
|
||||
input = des.decrypt(data[-cookie_size:])
|
||||
def unshuff(data, shuf):
|
||||
r = [''] * len(data)
|
||||
r = [0] * len(data)
|
||||
j = 0
|
||||
for i in xrange(len(data)):
|
||||
for i in range(len(data)):
|
||||
j = (j + shuf) % len(data)
|
||||
r[j] = data[i]
|
||||
assert len("".join(r)) == len(data)
|
||||
return "".join(r)
|
||||
assert len(bytes(r)) == len(data)
|
||||
return bytes(r)
|
||||
r = unshuff(input[0:-8], cookie_shuf)
|
||||
|
||||
drm_sub_version = struct.unpack('>H', r[0:2])[0]
|
||||
@@ -330,7 +325,7 @@ class EreaderProcessor(object):
|
||||
self.flags = struct.unpack('>L', r[4:8])[0]
|
||||
reqd_flags = (1<<9) | (1<<7) | (1<<10)
|
||||
if (self.flags & reqd_flags) != reqd_flags:
|
||||
print "Flags: 0x%X" % self.flags
|
||||
print("Flags: 0x%X" % self.flags)
|
||||
raise ValueError('incompatible eReader file')
|
||||
des = Des(fixKey(user_key))
|
||||
if version == 259:
|
||||
@@ -359,9 +354,9 @@ class EreaderProcessor(object):
|
||||
|
||||
def getImage(self, i):
|
||||
sect = self.section_reader(self.first_image_page + i)
|
||||
name = sect[4:4+32].strip('\0')
|
||||
name = sect[4:4+32].strip(b'\0')
|
||||
data = sect[62:]
|
||||
return sanitizeFileName(unicode(name,'windows-1252')), data
|
||||
return sanitizeFileName(name.decode('windows-1252')), data
|
||||
|
||||
|
||||
# def getChapterNamePMLOffsetData(self):
|
||||
@@ -409,8 +404,8 @@ class EreaderProcessor(object):
|
||||
|
||||
def getText(self):
|
||||
des = Des(fixKey(self.content_key))
|
||||
r = ''
|
||||
for i in xrange(self.num_text_pages):
|
||||
r = b''
|
||||
for i in range(self.num_text_pages):
|
||||
logging.debug('get page %d', i)
|
||||
r += zlib.decompress(des.decrypt(self.section_reader(1 + i)))
|
||||
|
||||
@@ -422,7 +417,7 @@ class EreaderProcessor(object):
|
||||
fnote_ids = deXOR(sect, 0, self.xortable)
|
||||
# the remaining records of the footnote sections need to be decoded with the content_key and zlib inflated
|
||||
des = Des(fixKey(self.content_key))
|
||||
for i in xrange(1,self.num_footnote_pages):
|
||||
for i in range(1,self.num_footnote_pages):
|
||||
logging.debug('get footnotepage %d', i)
|
||||
id_len = ord(fnote_ids[2])
|
||||
id = fnote_ids[3:3+id_len]
|
||||
@@ -446,7 +441,7 @@ class EreaderProcessor(object):
|
||||
sbar_ids = deXOR(sect, 0, self.xortable)
|
||||
# the remaining records of the sidebar sections need to be decoded with the content_key and zlib inflated
|
||||
des = Des(fixKey(self.content_key))
|
||||
for i in xrange(1,self.num_sidebar_pages):
|
||||
for i in range(1,self.num_sidebar_pages):
|
||||
id_len = ord(sbar_ids[2])
|
||||
id = sbar_ids[3:3+id_len]
|
||||
smarker = '<sidebar id="%s">\n' % id
|
||||
@@ -460,9 +455,8 @@ class EreaderProcessor(object):
|
||||
def cleanPML(pml):
|
||||
# Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255)
|
||||
pml2 = pml
|
||||
for k in xrange(128,256):
|
||||
badChar = chr(k)
|
||||
pml2 = pml2.replace(badChar, '\\a%03d' % k)
|
||||
for k in range(128,256):
|
||||
pml2 = pml2.replace(bytes([k]), b'\\a%03d' % k)
|
||||
return pml2
|
||||
|
||||
def decryptBook(infile, outpath, make_pmlz, user_key):
|
||||
@@ -471,35 +465,35 @@ def decryptBook(infile, outpath, make_pmlz, user_key):
|
||||
# outpath is actually pmlz name
|
||||
pmlzname = outpath
|
||||
outdir = tempfile.mkdtemp()
|
||||
imagedirpath = os.path.join(outdir,u"images")
|
||||
imagedirpath = os.path.join(outdir,"images")
|
||||
else:
|
||||
pmlzname = None
|
||||
outdir = outpath
|
||||
imagedirpath = os.path.join(outdir,bookname + u"_img")
|
||||
imagedirpath = os.path.join(outdir,bookname + "_img")
|
||||
|
||||
try:
|
||||
if not os.path.exists(outdir):
|
||||
os.makedirs(outdir)
|
||||
print u"Decoding File"
|
||||
sect = Sectionizer(infile, 'PNRdPPrs')
|
||||
print("Decoding File")
|
||||
sect =Sectionizer(infile, b'PNRdPPrs')
|
||||
er = EreaderProcessor(sect, user_key)
|
||||
|
||||
if er.getNumImages() > 0:
|
||||
print u"Extracting images"
|
||||
print("Extracting images")
|
||||
if not os.path.exists(imagedirpath):
|
||||
os.makedirs(imagedirpath)
|
||||
for i in xrange(er.getNumImages()):
|
||||
for i in range(er.getNumImages()):
|
||||
name, contents = er.getImage(i)
|
||||
file(os.path.join(imagedirpath, name), 'wb').write(contents)
|
||||
open(os.path.join(imagedirpath, name), 'wb').write(contents)
|
||||
|
||||
print u"Extracting pml"
|
||||
print("Extracting pml")
|
||||
pml_string = er.getText()
|
||||
pmlfilename = bookname + ".pml"
|
||||
file(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string))
|
||||
open(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string))
|
||||
if pmlzname is not None:
|
||||
import zipfile
|
||||
import shutil
|
||||
print u"Creating PMLZ file {0}".format(os.path.basename(pmlzname))
|
||||
print("Creating PMLZ file {0}".format(os.path.basename(pmlzname)))
|
||||
myZipFile = zipfile.ZipFile(pmlzname,'w',zipfile.ZIP_STORED, False)
|
||||
list = os.listdir(outdir)
|
||||
for filename in list:
|
||||
@@ -518,48 +512,48 @@ def decryptBook(infile, outpath, make_pmlz, user_key):
|
||||
myZipFile.close()
|
||||
# remove temporary directory
|
||||
shutil.rmtree(outdir, True)
|
||||
print u"Output is {0}".format(pmlzname)
|
||||
else :
|
||||
print u"Output is in {0}".format(outdir)
|
||||
print "done"
|
||||
except ValueError, e:
|
||||
print u"Error: {0}".format(e)
|
||||
print("Output is {0}".format(pmlzname))
|
||||
else:
|
||||
print("Output is in {0}".format(outdir))
|
||||
print("done")
|
||||
except ValueError as e:
|
||||
print("Error: {0}".format(e))
|
||||
traceback.print_exc()
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def usage():
|
||||
print u"Converts DRMed eReader books to PML Source"
|
||||
print u"Usage:"
|
||||
print u" erdr2pml [options] infile.pdb [outpath] \"your name\" credit_card_number"
|
||||
print u" "
|
||||
print u"Options: "
|
||||
print u" -h prints this message"
|
||||
print u" -p create PMLZ instead of source folder"
|
||||
print u" --make-pmlz create PMLZ instead of source folder"
|
||||
print u" "
|
||||
print u"Note:"
|
||||
print u" if outpath is ommitted, creates source in 'infile_Source' folder"
|
||||
print u" if outpath is ommitted and pmlz option, creates PMLZ 'infile.pmlz'"
|
||||
print u" if source folder created, images are in infile_img folder"
|
||||
print u" if pmlz file created, images are in images folder"
|
||||
print u" It's enough to enter the last 8 digits of the credit card number"
|
||||
print("Converts DRMed eReader books to PML Source")
|
||||
print("Usage:")
|
||||
print(" erdr2pml [options] infile.pdb [outpath] \"your name\" credit_card_number")
|
||||
print(" ")
|
||||
print("Options: ")
|
||||
print(" -h prints this message")
|
||||
print(" -p create PMLZ instead of source folder")
|
||||
print(" --make-pmlz create PMLZ instead of source folder")
|
||||
print(" ")
|
||||
print("Note:")
|
||||
print(" if outpath is ommitted, creates source in 'infile_Source' folder")
|
||||
print(" if outpath is ommitted and pmlz option, creates PMLZ 'infile.pmlz'")
|
||||
print(" if source folder created, images are in infile_img folder")
|
||||
print(" if pmlz file created, images are in images folder")
|
||||
print(" It's enough to enter the last 8 digits of the credit card number")
|
||||
return
|
||||
|
||||
def getuser_key(name,cc):
|
||||
newname = "".join(c for c in name.lower() if c >= 'a' and c <= 'z' or c >= '0' and c <= '9')
|
||||
cc = cc.replace(" ","")
|
||||
return struct.pack('>LL', binascii.crc32(newname) & 0xffffffff,binascii.crc32(cc[-8:])& 0xffffffff)
|
||||
return struct.pack('>LL', binascii.crc32(bytes(newname.encode('utf-8'))) & 0xffffffff, binascii.crc32(bytes(cc[-8:].encode('utf-8'))) & 0xffffffff)
|
||||
|
||||
def cli_main():
|
||||
print u"eRdr2Pml v{0}. Copyright © 2009–2012 The Dark Reverser et al.".format(__version__)
|
||||
print("eRdr2Pml v{0}. Copyright © 2009–2020 The Dark Reverser et al.".format(__version__))
|
||||
|
||||
argv=unicode_argv()
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], "hp", ["make-pmlz"])
|
||||
except getopt.GetoptError, err:
|
||||
print err.args[0]
|
||||
except getopt.GetoptError as err:
|
||||
print(err.args[0])
|
||||
usage()
|
||||
return 1
|
||||
make_pmlz = False
|
||||
@@ -579,13 +573,13 @@ def cli_main():
|
||||
if len(args)==3:
|
||||
infile, name, cc = args
|
||||
if make_pmlz:
|
||||
outpath = os.path.splitext(infile)[0] + u".pmlz"
|
||||
outpath = os.path.splitext(infile)[0] + ".pmlz"
|
||||
else:
|
||||
outpath = os.path.splitext(infile)[0] + u"_Source"
|
||||
outpath = os.path.splitext(infile)[0] + "_Source"
|
||||
elif len(args)==4:
|
||||
infile, outpath, name, cc = args
|
||||
|
||||
print getuser_key(name,cc).encode('hex')
|
||||
print(binascii.b2a_hex(getuser_key(name,cc)))
|
||||
|
||||
return decryptBook(infile, outpath, make_pmlz, getuser_key(name,cc))
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||
# For use with Topaz Scripts Version 2.6
|
||||
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
import csv
|
||||
import os
|
||||
import math
|
||||
import getopt
|
||||
import functools
|
||||
from struct import pack
|
||||
from struct import unpack
|
||||
|
||||
@@ -16,14 +16,14 @@ class DocParser(object):
|
||||
def __init__(self, flatxml, classlst, fileid, bookDir, gdict, fixedimage):
|
||||
self.id = os.path.basename(fileid).replace('.dat','')
|
||||
self.svgcount = 0
|
||||
self.docList = flatxml.split('\n')
|
||||
self.docList = flatxml.split(b'\n')
|
||||
self.docSize = len(self.docList)
|
||||
self.classList = {}
|
||||
self.bookDir = bookDir
|
||||
self.gdict = gdict
|
||||
tmpList = classlst.split('\n')
|
||||
for pclass in tmpList:
|
||||
if pclass != '':
|
||||
if pclass != b'':
|
||||
# remove the leading period from the css name
|
||||
cname = pclass[1:]
|
||||
self.classList[cname] = True
|
||||
@@ -58,9 +58,9 @@ class DocParser(object):
|
||||
imgfile = os.path.join(imgDir,imgname)
|
||||
|
||||
# get glyph information
|
||||
gxList = self.getData('info.glyph.x',0,-1)
|
||||
gyList = self.getData('info.glyph.y',0,-1)
|
||||
gidList = self.getData('info.glyph.glyphID',0,-1)
|
||||
gxList = self.getData(b'info.glyph.x',0,-1)
|
||||
gyList = self.getData(b'info.glyph.y',0,-1)
|
||||
gidList = self.getData(b'info.glyph.glyphID',0,-1)
|
||||
|
||||
gids = []
|
||||
maxws = []
|
||||
@@ -95,7 +95,7 @@ class DocParser(object):
|
||||
# change the origin to minx, miny and calc max height and width
|
||||
maxw = maxws[0] + xs[0] - minx
|
||||
maxh = maxhs[0] + ys[0] - miny
|
||||
for j in xrange(0, len(xs)):
|
||||
for j in range(0, len(xs)):
|
||||
xs[j] = xs[j] - minx
|
||||
ys[j] = ys[j] - miny
|
||||
maxw = max( maxw, (maxws[j] + xs[j]) )
|
||||
@@ -107,10 +107,10 @@ class DocParser(object):
|
||||
ifile.write('<!DOCTYPE svg PUBLIC "-//W3C/DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n')
|
||||
ifile.write('<svg width="%dpx" height="%dpx" viewBox="0 0 %d %d" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">\n' % (math.floor(maxw/10), math.floor(maxh/10), maxw, maxh))
|
||||
ifile.write('<defs>\n')
|
||||
for j in xrange(0,len(gdefs)):
|
||||
for j in range(0,len(gdefs)):
|
||||
ifile.write(gdefs[j])
|
||||
ifile.write('</defs>\n')
|
||||
for j in xrange(0,len(gids)):
|
||||
for j in range(0,len(gids)):
|
||||
ifile.write('<use xlink:href="#gl%d" x="%d" y="%d" />\n' % (gids[j], xs[j], ys[j]))
|
||||
ifile.write('</svg>')
|
||||
ifile.close()
|
||||
@@ -123,11 +123,11 @@ class DocParser(object):
|
||||
def lineinDoc(self, pos) :
|
||||
if (pos >= 0) and (pos < self.docSize) :
|
||||
item = self.docList[pos]
|
||||
if item.find('=') >= 0:
|
||||
(name, argres) = item.split('=',1)
|
||||
if item.find(b'=') >= 0:
|
||||
(name, argres) = item.split(b'=',1)
|
||||
else :
|
||||
name = item
|
||||
argres = ''
|
||||
argres = b''
|
||||
return name, argres
|
||||
|
||||
|
||||
@@ -139,13 +139,15 @@ class DocParser(object):
|
||||
else:
|
||||
end = min(self.docSize, end)
|
||||
foundat = -1
|
||||
for j in xrange(pos, end):
|
||||
for j in range(pos, end):
|
||||
item = self.docList[j]
|
||||
if item.find('=') >= 0:
|
||||
(name, argres) = item.split('=',1)
|
||||
if item.find(b'=') >= 0:
|
||||
(name, argres) = item.split(b'=',1)
|
||||
else :
|
||||
name = item
|
||||
argres = ''
|
||||
if (isinstance(tagpath,str)):
|
||||
tagpath = tagpath.encode('utf-8')
|
||||
if name.endswith(tagpath) :
|
||||
result = argres
|
||||
foundat = j
|
||||
@@ -171,7 +173,7 @@ class DocParser(object):
|
||||
argres=[]
|
||||
(foundat, argt) = self.findinDoc(tagpath, pos, end)
|
||||
if (argt != None) and (len(argt) > 0) :
|
||||
argList = argt.split('|')
|
||||
argList = argt.split(b'|')
|
||||
argres = [ int(strval) for strval in argList]
|
||||
return argres
|
||||
|
||||
@@ -192,21 +194,21 @@ class DocParser(object):
|
||||
|
||||
# also some class names have spaces in them so need to convert to dashes
|
||||
if nclass != None :
|
||||
nclass = nclass.replace(' ','-')
|
||||
classres = ''
|
||||
nclass = nclass.replace(b' ',b'-')
|
||||
classres = b''
|
||||
nclass = nclass.lower()
|
||||
nclass = 'cl-' + nclass
|
||||
baseclass = ''
|
||||
nclass = b'cl-' + nclass
|
||||
baseclass = b''
|
||||
# graphic is the base class for captions
|
||||
if nclass.find('cl-cap-') >=0 :
|
||||
classres = 'graphic' + ' '
|
||||
if nclass.find(b'cl-cap-') >=0 :
|
||||
classres = b'graphic' + b' '
|
||||
else :
|
||||
# strip to find baseclass
|
||||
p = nclass.find('_')
|
||||
p = nclass.find(b'_')
|
||||
if p > 0 :
|
||||
baseclass = nclass[0:p]
|
||||
if baseclass in self.classList:
|
||||
classres += baseclass + ' '
|
||||
classres += baseclass + b' '
|
||||
classres += nclass
|
||||
nclass = classres
|
||||
return nclass
|
||||
@@ -226,11 +228,11 @@ class DocParser(object):
|
||||
return -1
|
||||
|
||||
result = []
|
||||
(pos, pagetype) = self.findinDoc('page.type',0,-1)
|
||||
(pos, pagetype) = self.findinDoc(b'page.type',0,-1)
|
||||
|
||||
groupList = self.posinDoc('page.group')
|
||||
groupregionList = self.posinDoc('page.group.region')
|
||||
pageregionList = self.posinDoc('page.region')
|
||||
groupList = self.posinDoc(b'page.group')
|
||||
groupregionList = self.posinDoc(b'page.group.region')
|
||||
pageregionList = self.posinDoc(b'page.region')
|
||||
# integrate into one list
|
||||
for j in groupList:
|
||||
result.append(('grpbeg',j))
|
||||
@@ -238,7 +240,7 @@ class DocParser(object):
|
||||
result.append(('gregion',j))
|
||||
for j in pageregionList:
|
||||
result.append(('pregion',j))
|
||||
result.sort(compare)
|
||||
result.sort(key=functools.cmp_to_key(compare))
|
||||
|
||||
# insert group end and page end indicators
|
||||
inGroup = False
|
||||
@@ -268,39 +270,39 @@ class DocParser(object):
|
||||
result = []
|
||||
|
||||
# paragraph
|
||||
(pos, pclass) = self.findinDoc('paragraph.class',start,end)
|
||||
(pos, pclass) = self.findinDoc(b'paragraph.class',start,end)
|
||||
|
||||
pclass = self.getClass(pclass)
|
||||
|
||||
# if paragraph uses extratokens (extra glyphs) then make it fixed
|
||||
(pos, extraglyphs) = self.findinDoc('paragraph.extratokens',start,end)
|
||||
(pos, extraglyphs) = self.findinDoc(b'paragraph.extratokens',start,end)
|
||||
|
||||
# build up a description of the paragraph in result and return it
|
||||
# first check for the basic - all words paragraph
|
||||
(pos, sfirst) = self.findinDoc('paragraph.firstWord',start,end)
|
||||
(pos, slast) = self.findinDoc('paragraph.lastWord',start,end)
|
||||
(pos, sfirst) = self.findinDoc(b'paragraph.firstWord',start,end)
|
||||
(pos, slast) = self.findinDoc(b'paragraph.lastWord',start,end)
|
||||
if (sfirst != None) and (slast != None) :
|
||||
first = int(sfirst)
|
||||
last = int(slast)
|
||||
|
||||
makeImage = (regtype == 'vertical') or (regtype == 'table')
|
||||
makeImage = (regtype == b'vertical') or (regtype == b'table')
|
||||
makeImage = makeImage or (extraglyphs != None)
|
||||
if self.fixedimage:
|
||||
makeImage = makeImage or (regtype == 'fixed')
|
||||
makeImage = makeImage or (regtype == b'fixed')
|
||||
|
||||
if (pclass != None):
|
||||
makeImage = makeImage or (pclass.find('.inverted') >= 0)
|
||||
makeImage = makeImage or (pclass.find(b'.inverted') >= 0)
|
||||
if self.fixedimage :
|
||||
makeImage = makeImage or (pclass.find('cl-f-') >= 0)
|
||||
makeImage = makeImage or (pclass.find(b'cl-f-') >= 0)
|
||||
|
||||
# before creating an image make sure glyph info exists
|
||||
gidList = self.getData('info.glyph.glyphID',0,-1)
|
||||
gidList = self.getData(b'info.glyph.glyphID',0,-1)
|
||||
|
||||
makeImage = makeImage & (len(gidList) > 0)
|
||||
|
||||
if not makeImage :
|
||||
# standard all word paragraph
|
||||
for wordnum in xrange(first, last):
|
||||
for wordnum in range(first, last):
|
||||
result.append(('ocr', wordnum))
|
||||
return pclass, result
|
||||
|
||||
@@ -308,8 +310,8 @@ class DocParser(object):
|
||||
# translate first and last word into first and last glyphs
|
||||
# and generate inline image and include it
|
||||
glyphList = []
|
||||
firstglyphList = self.getData('word.firstGlyph',0,-1)
|
||||
gidList = self.getData('info.glyph.glyphID',0,-1)
|
||||
firstglyphList = self.getData(b'word.firstGlyph',0,-1)
|
||||
gidList = self.getData(b'info.glyph.glyphID',0,-1)
|
||||
firstGlyph = firstglyphList[first]
|
||||
if last < len(firstglyphList):
|
||||
lastGlyph = firstglyphList[last]
|
||||
@@ -320,17 +322,17 @@ class DocParser(object):
|
||||
# by reverting to text based paragraph
|
||||
if firstGlyph >= lastGlyph:
|
||||
# revert to standard text based paragraph
|
||||
for wordnum in xrange(first, last):
|
||||
for wordnum in range(first, last):
|
||||
result.append(('ocr', wordnum))
|
||||
return pclass, result
|
||||
|
||||
for glyphnum in xrange(firstGlyph, lastGlyph):
|
||||
for glyphnum in range(firstGlyph, lastGlyph):
|
||||
glyphList.append(glyphnum)
|
||||
# include any extratokens if they exist
|
||||
(pos, sfg) = self.findinDoc('extratokens.firstGlyph',start,end)
|
||||
(pos, slg) = self.findinDoc('extratokens.lastGlyph',start,end)
|
||||
(pos, sfg) = self.findinDoc(b'extratokens.firstGlyph',start,end)
|
||||
(pos, slg) = self.findinDoc(b'extratokens.lastGlyph',start,end)
|
||||
if (sfg != None) and (slg != None):
|
||||
for glyphnum in xrange(int(sfg), int(slg)):
|
||||
for glyphnum in range(int(sfg), int(slg)):
|
||||
glyphList.append(glyphnum)
|
||||
num = self.svgcount
|
||||
self.glyphs_to_image(glyphList)
|
||||
@@ -369,50 +371,50 @@ class DocParser(object):
|
||||
|
||||
(name, argres) = self.lineinDoc(line)
|
||||
|
||||
if name.endswith('span.firstWord') :
|
||||
if name.endswith(b'span.firstWord') :
|
||||
sp_first = int(argres)
|
||||
|
||||
elif name.endswith('span.lastWord') :
|
||||
elif name.endswith(b'span.lastWord') :
|
||||
sp_last = int(argres)
|
||||
|
||||
elif name.endswith('word.firstGlyph') :
|
||||
elif name.endswith(b'word.firstGlyph') :
|
||||
gl_first = int(argres)
|
||||
|
||||
elif name.endswith('word.lastGlyph') :
|
||||
elif name.endswith(b'word.lastGlyph') :
|
||||
gl_last = int(argres)
|
||||
|
||||
elif name.endswith('word_semantic.firstWord'):
|
||||
elif name.endswith(b'word_semantic.firstWord'):
|
||||
ws_first = int(argres)
|
||||
|
||||
elif name.endswith('word_semantic.lastWord'):
|
||||
elif name.endswith(b'word_semantic.lastWord'):
|
||||
ws_last = int(argres)
|
||||
|
||||
elif name.endswith('word.class'):
|
||||
elif name.endswith(b'word.class'):
|
||||
# we only handle spaceafter word class
|
||||
try:
|
||||
(cname, space) = argres.split('-',1)
|
||||
if space == '' : space = '0'
|
||||
if (cname == 'spaceafter') and (int(space) > 0) :
|
||||
(cname, space) = argres.split(b'-',1)
|
||||
if space == b'' : space = b'0'
|
||||
if (cname == b'spaceafter') and (int(space) > 0) :
|
||||
word_class = 'sa'
|
||||
except:
|
||||
pass
|
||||
|
||||
elif name.endswith('word.img.src'):
|
||||
elif name.endswith(b'word.img.src'):
|
||||
result.append(('img' + word_class, int(argres)))
|
||||
word_class = ''
|
||||
|
||||
elif name.endswith('region.img.src'):
|
||||
elif name.endswith(b'region.img.src'):
|
||||
result.append(('img' + word_class, int(argres)))
|
||||
|
||||
if (sp_first != -1) and (sp_last != -1):
|
||||
for wordnum in xrange(sp_first, sp_last):
|
||||
for wordnum in range(sp_first, sp_last):
|
||||
result.append(('ocr', wordnum))
|
||||
sp_first = -1
|
||||
sp_last = -1
|
||||
|
||||
if (gl_first != -1) and (gl_last != -1):
|
||||
glyphList = []
|
||||
for glyphnum in xrange(gl_first, gl_last):
|
||||
for glyphnum in range(gl_first, gl_last):
|
||||
glyphList.append(glyphnum)
|
||||
num = self.svgcount
|
||||
self.glyphs_to_image(glyphList)
|
||||
@@ -422,7 +424,7 @@ class DocParser(object):
|
||||
gl_last = -1
|
||||
|
||||
if (ws_first != -1) and (ws_last != -1):
|
||||
for wordnum in xrange(ws_first, ws_last):
|
||||
for wordnum in range(ws_first, ws_last):
|
||||
result.append(('ocr', wordnum))
|
||||
ws_first = -1
|
||||
ws_last = -1
|
||||
@@ -438,7 +440,7 @@ class DocParser(object):
|
||||
|
||||
classres = ''
|
||||
if pclass :
|
||||
classres = ' class="' + pclass + '"'
|
||||
classres = ' class="' + pclass.decode('utf-8') + '"'
|
||||
|
||||
br_lb = (regtype == 'fixed') or (regtype == 'chapterheading') or (regtype == 'vertical')
|
||||
|
||||
@@ -454,7 +456,7 @@ class DocParser(object):
|
||||
|
||||
cnt = len(pdesc)
|
||||
|
||||
for j in xrange( 0, cnt) :
|
||||
for j in range( 0, cnt) :
|
||||
|
||||
(wtype, num) = pdesc[j]
|
||||
|
||||
@@ -471,8 +473,8 @@ class DocParser(object):
|
||||
if (link > 0):
|
||||
linktype = self.link_type[link-1]
|
||||
title = self.link_title[link-1]
|
||||
if (title == "") or (parares.rfind(title) < 0):
|
||||
title=parares[lstart:]
|
||||
if (title == b"") or (parares.rfind(title.decode('utf-8')) < 0):
|
||||
title=parares[lstart:].encode('utf-8')
|
||||
if linktype == 'external' :
|
||||
linkhref = self.link_href[link-1]
|
||||
linkhtml = '<a href="%s">' % linkhref
|
||||
@@ -483,33 +485,34 @@ class DocParser(object):
|
||||
else :
|
||||
# just link to the current page
|
||||
linkhtml = '<a href="#' + self.id + '">'
|
||||
linkhtml += title + '</a>'
|
||||
pos = parares.rfind(title)
|
||||
linkhtml += title.decode('utf-8')
|
||||
linkhtml += '</a>'
|
||||
pos = parares.rfind(title.decode('utf-8'))
|
||||
if pos >= 0:
|
||||
parares = parares[0:pos] + linkhtml + parares[pos+len(title):]
|
||||
else :
|
||||
parares += linkhtml
|
||||
lstart = len(parares)
|
||||
if word == '_link_' : word = ''
|
||||
if word == b'_link_' : word = b''
|
||||
elif (link < 0) :
|
||||
if word == '_link_' : word = ''
|
||||
if word == b'_link_' : word = b''
|
||||
|
||||
if word == '_lb_':
|
||||
if word == b'_lb_':
|
||||
if ((num-1) in self.dehyphen_rootid ) or handle_links:
|
||||
word = ''
|
||||
word = b''
|
||||
sep = ''
|
||||
elif br_lb :
|
||||
word = '<br />\n'
|
||||
word = b'<br />\n'
|
||||
sep = ''
|
||||
else :
|
||||
word = '\n'
|
||||
word = b'\n'
|
||||
sep = ''
|
||||
|
||||
if num in self.dehyphen_rootid :
|
||||
word = word[0:-1]
|
||||
sep = ''
|
||||
|
||||
parares += word + sep
|
||||
parares += word.decode('utf-8') + sep
|
||||
|
||||
elif wtype == 'img' :
|
||||
sep = ''
|
||||
@@ -523,7 +526,9 @@ class DocParser(object):
|
||||
|
||||
elif wtype == 'svg' :
|
||||
sep = ''
|
||||
parares += '<img src="img/' + self.id + '_%04d.svg" alt="" />' % num
|
||||
parares += '<img src="img/'
|
||||
parares += self.id
|
||||
parares += '_%04d.svg" alt="" />' % num
|
||||
parares += sep
|
||||
|
||||
if len(sep) > 0 : parares = parares[0:-1]
|
||||
@@ -541,12 +546,12 @@ class DocParser(object):
|
||||
lstart = 0
|
||||
|
||||
cnt = len(pdesc)
|
||||
for j in xrange( 0, cnt) :
|
||||
for j in range( 0, cnt) :
|
||||
|
||||
(wtype, num) = pdesc[j]
|
||||
|
||||
if wtype == 'ocr' :
|
||||
word = self.ocrtext[num]
|
||||
word = self.ocrtext[num].decode('utf-8')
|
||||
sep = ' '
|
||||
|
||||
if handle_links:
|
||||
@@ -554,7 +559,7 @@ class DocParser(object):
|
||||
if (link > 0):
|
||||
linktype = self.link_type[link-1]
|
||||
title = self.link_title[link-1]
|
||||
title = title.rstrip('. ')
|
||||
title = title.rstrip(b'. ')
|
||||
alt_title = parares[lstart:]
|
||||
alt_title = alt_title.strip()
|
||||
# now strip off the actual printed page number
|
||||
@@ -608,38 +613,38 @@ class DocParser(object):
|
||||
hlst = []
|
||||
|
||||
# get the ocr text
|
||||
(pos, argres) = self.findinDoc('info.word.ocrText',0,-1)
|
||||
if argres : self.ocrtext = argres.split('|')
|
||||
(pos, argres) = self.findinDoc(b'info.word.ocrText',0,-1)
|
||||
if argres : self.ocrtext = argres.split(b'|')
|
||||
|
||||
# get information to dehyphenate the text
|
||||
self.dehyphen_rootid = self.getData('info.dehyphen.rootID',0,-1)
|
||||
self.dehyphen_rootid = self.getData(b'info.dehyphen.rootID',0,-1)
|
||||
|
||||
# determine if first paragraph is continued from previous page
|
||||
(pos, self.parastems_stemid) = self.findinDoc('info.paraStems.stemID',0,-1)
|
||||
(pos, self.parastems_stemid) = self.findinDoc(b'info.paraStems.stemID',0,-1)
|
||||
first_para_continued = (self.parastems_stemid != None)
|
||||
|
||||
# determine if last paragraph is continued onto the next page
|
||||
(pos, self.paracont_stemid) = self.findinDoc('info.paraCont.stemID',0,-1)
|
||||
(pos, self.paracont_stemid) = self.findinDoc(b'info.paraCont.stemID',0,-1)
|
||||
last_para_continued = (self.paracont_stemid != None)
|
||||
|
||||
# collect link ids
|
||||
self.link_id = self.getData('info.word.link_id',0,-1)
|
||||
self.link_id = self.getData(b'info.word.link_id',0,-1)
|
||||
|
||||
# collect link destination page numbers
|
||||
self.link_page = self.getData('info.links.page',0,-1)
|
||||
self.link_page = self.getData(b'info.links.page',0,-1)
|
||||
|
||||
# collect link types (container versus external)
|
||||
(pos, argres) = self.findinDoc('info.links.type',0,-1)
|
||||
if argres : self.link_type = argres.split('|')
|
||||
(pos, argres) = self.findinDoc(b'info.links.type',0,-1)
|
||||
if argres : self.link_type = argres.split(b'|')
|
||||
|
||||
# collect link destinations
|
||||
(pos, argres) = self.findinDoc('info.links.href',0,-1)
|
||||
if argres : self.link_href = argres.split('|')
|
||||
(pos, argres) = self.findinDoc(b'info.links.href',0,-1)
|
||||
if argres : self.link_href = argres.split(b'|')
|
||||
|
||||
# collect link titles
|
||||
(pos, argres) = self.findinDoc('info.links.title',0,-1)
|
||||
(pos, argres) = self.findinDoc(b'info.links.title',0,-1)
|
||||
if argres :
|
||||
self.link_title = argres.split('|')
|
||||
self.link_title = argres.split(b'|')
|
||||
else:
|
||||
self.link_title.append('')
|
||||
|
||||
@@ -654,7 +659,7 @@ class DocParser(object):
|
||||
|
||||
# process each region on the page and convert what you can to html
|
||||
|
||||
for j in xrange(regcnt):
|
||||
for j in range(regcnt):
|
||||
|
||||
(etype, start) = pageDesc[j]
|
||||
(ntype, end) = pageDesc[j+1]
|
||||
@@ -663,51 +668,51 @@ class DocParser(object):
|
||||
# set anchor for link target on this page
|
||||
if not anchorSet and not first_para_continued:
|
||||
hlst.append('<div style="visibility: hidden; height: 0; width: 0;" id="')
|
||||
hlst.append(self.id + '" title="pagetype_' + pagetype + '"></div>\n')
|
||||
hlst.append(self.id + '" title="pagetype_' + pagetype.decode('utf-8') + '"></div>\n')
|
||||
anchorSet = True
|
||||
|
||||
# handle groups of graphics with text captions
|
||||
if (etype == 'grpbeg'):
|
||||
(pos, grptype) = self.findinDoc('group.type', start, end)
|
||||
if (etype == b'grpbeg'):
|
||||
(pos, grptype) = self.findinDoc(b'group.type', start, end)
|
||||
if grptype != None:
|
||||
if grptype == 'graphic':
|
||||
gcstr = ' class="' + grptype + '"'
|
||||
if grptype == b'graphic':
|
||||
gcstr = ' class="' + grptype.decode('utf-8') + '"'
|
||||
hlst.append('<div' + gcstr + '>')
|
||||
inGroup = True
|
||||
|
||||
elif (etype == 'grpend'):
|
||||
elif (etype == b'grpend'):
|
||||
if inGroup:
|
||||
hlst.append('</div>\n')
|
||||
inGroup = False
|
||||
|
||||
else:
|
||||
(pos, regtype) = self.findinDoc('region.type',start,end)
|
||||
(pos, regtype) = self.findinDoc(b'region.type',start,end)
|
||||
|
||||
if regtype == 'graphic' :
|
||||
(pos, simgsrc) = self.findinDoc('img.src',start,end)
|
||||
if regtype == b'graphic' :
|
||||
(pos, simgsrc) = self.findinDoc(b'img.src',start,end)
|
||||
if simgsrc:
|
||||
if inGroup:
|
||||
hlst.append('<img src="img/img%04d.jpg" alt="" />' % int(simgsrc))
|
||||
else:
|
||||
hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
|
||||
|
||||
elif regtype == 'chapterheading' :
|
||||
elif regtype == b'chapterheading' :
|
||||
(pclass, pdesc) = self.getParaDescription(start,end, regtype)
|
||||
if not breakSet:
|
||||
hlst.append('<div style="page-break-after: always;"> </div>\n')
|
||||
breakSet = True
|
||||
tag = 'h1'
|
||||
if pclass and (len(pclass) >= 7):
|
||||
if pclass[3:7] == 'ch1-' : tag = 'h1'
|
||||
if pclass[3:7] == 'ch2-' : tag = 'h2'
|
||||
if pclass[3:7] == 'ch3-' : tag = 'h3'
|
||||
hlst.append('<' + tag + ' class="' + pclass + '">')
|
||||
if pclass[3:7] == b'ch1-' : tag = 'h1'
|
||||
if pclass[3:7] == b'ch2-' : tag = 'h2'
|
||||
if pclass[3:7] == b'ch3-' : tag = 'h3'
|
||||
hlst.append('<' + tag + ' class="' + pclass.decode('utf-8') + '">')
|
||||
else:
|
||||
hlst.append('<' + tag + '>')
|
||||
hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
|
||||
hlst.append('</' + tag + '>')
|
||||
|
||||
elif (regtype == 'text') or (regtype == 'fixed') or (regtype == 'insert') or (regtype == 'listitem'):
|
||||
elif (regtype == b'text') or (regtype == b'fixed') or (regtype == b'insert') or (regtype == b'listitem'):
|
||||
ptype = 'full'
|
||||
# check to see if this is a continution from the previous page
|
||||
if first_para_continued :
|
||||
@@ -716,16 +721,16 @@ class DocParser(object):
|
||||
(pclass, pdesc) = self.getParaDescription(start,end, regtype)
|
||||
if pclass and (len(pclass) >= 6) and (ptype == 'full'):
|
||||
tag = 'p'
|
||||
if pclass[3:6] == 'h1-' : tag = 'h4'
|
||||
if pclass[3:6] == 'h2-' : tag = 'h5'
|
||||
if pclass[3:6] == 'h3-' : tag = 'h6'
|
||||
hlst.append('<' + tag + ' class="' + pclass + '">')
|
||||
if pclass[3:6] == b'h1-' : tag = 'h4'
|
||||
if pclass[3:6] == b'h2-' : tag = 'h5'
|
||||
if pclass[3:6] == b'h3-' : tag = 'h6'
|
||||
hlst.append('<' + tag + ' class="' + pclass.decode('utf-8') + '">')
|
||||
hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
|
||||
hlst.append('</' + tag + '>')
|
||||
else :
|
||||
hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
|
||||
|
||||
elif (regtype == 'tocentry') :
|
||||
elif (regtype == b'tocentry') :
|
||||
ptype = 'full'
|
||||
if first_para_continued :
|
||||
ptype = 'end'
|
||||
@@ -734,7 +739,7 @@ class DocParser(object):
|
||||
tocinfo += self.buildTOCEntry(pdesc)
|
||||
hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
|
||||
|
||||
elif (regtype == 'vertical') or (regtype == 'table') :
|
||||
elif (regtype == b'vertical') or (regtype == b'table') :
|
||||
ptype = 'full'
|
||||
if inGroup:
|
||||
ptype = 'middle'
|
||||
@@ -745,19 +750,19 @@ class DocParser(object):
|
||||
hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
|
||||
|
||||
|
||||
elif (regtype == 'synth_fcvr.center'):
|
||||
(pos, simgsrc) = self.findinDoc('img.src',start,end)
|
||||
elif (regtype == b'synth_fcvr.center'):
|
||||
(pos, simgsrc) = self.findinDoc(b'img.src',start,end)
|
||||
if simgsrc:
|
||||
hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
|
||||
|
||||
else :
|
||||
print(' Making region type', regtype, end=' ')
|
||||
(pos, temp) = self.findinDoc('paragraph',start,end)
|
||||
(pos2, temp) = self.findinDoc('span',start,end)
|
||||
(pos, temp) = self.findinDoc(b'paragraph',start,end)
|
||||
(pos2, temp) = self.findinDoc(b'span',start,end)
|
||||
if pos != -1 or pos2 != -1:
|
||||
print(' a "text" region')
|
||||
orig_regtype = regtype
|
||||
regtype = 'fixed'
|
||||
regtype = b'fixed'
|
||||
ptype = 'full'
|
||||
# check to see if this is a continution from the previous page
|
||||
if first_para_continued :
|
||||
@@ -765,23 +770,23 @@ class DocParser(object):
|
||||
first_para_continued = False
|
||||
(pclass, pdesc) = self.getParaDescription(start,end, regtype)
|
||||
if not pclass:
|
||||
if orig_regtype.endswith('.right') : pclass = 'cl-right'
|
||||
elif orig_regtype.endswith('.center') : pclass = 'cl-center'
|
||||
elif orig_regtype.endswith('.left') : pclass = 'cl-left'
|
||||
elif orig_regtype.endswith('.justify') : pclass = 'cl-justify'
|
||||
if orig_regtype.endswith(b'.right') : pclass = 'cl-right'
|
||||
elif orig_regtype.endswith(b'.center') : pclass = 'cl-center'
|
||||
elif orig_regtype.endswith(b'.left') : pclass = 'cl-left'
|
||||
elif orig_regtype.endswith(b'.justify') : pclass = 'cl-justify'
|
||||
if pclass and (ptype == 'full') and (len(pclass) >= 6):
|
||||
tag = 'p'
|
||||
if pclass[3:6] == 'h1-' : tag = 'h4'
|
||||
if pclass[3:6] == 'h2-' : tag = 'h5'
|
||||
if pclass[3:6] == 'h3-' : tag = 'h6'
|
||||
hlst.append('<' + tag + ' class="' + pclass + '">')
|
||||
if pclass[3:6] == b'h1-' : tag = 'h4'
|
||||
if pclass[3:6] == b'h2-' : tag = 'h5'
|
||||
if pclass[3:6] == b'h3-' : tag = 'h6'
|
||||
hlst.append('<' + tag + ' class="' + pclass.decode('utf-8') + '">')
|
||||
hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
|
||||
hlst.append('</' + tag + '>')
|
||||
else :
|
||||
hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
|
||||
else :
|
||||
print(' a "graphic" region')
|
||||
(pos, simgsrc) = self.findinDoc('img.src',start,end)
|
||||
(pos, simgsrc) = self.findinDoc(b'img.src',start,end)
|
||||
if simgsrc:
|
||||
hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ from struct import unpack
|
||||
class PParser(object):
|
||||
def __init__(self, gd, flatxml, meta_array):
|
||||
self.gd = gd
|
||||
self.flatdoc = flatxml.split('\n')
|
||||
self.flatdoc = flatxml.split(b'\n')
|
||||
self.docSize = len(self.flatdoc)
|
||||
self.temp = []
|
||||
|
||||
@@ -58,11 +58,11 @@ class PParser(object):
|
||||
def lineinDoc(self, pos) :
|
||||
if (pos >= 0) and (pos < self.docSize) :
|
||||
item = self.flatdoc[pos]
|
||||
if item.find('=') >= 0:
|
||||
(name, argres) = item.split('=',1)
|
||||
if item.find(b'=') >= 0:
|
||||
(name, argres) = item.split(b'=',1)
|
||||
else :
|
||||
name = item
|
||||
argres = ''
|
||||
argres = b''
|
||||
return name, argres
|
||||
|
||||
# find tag in doc if within pos to end inclusive
|
||||
@@ -73,13 +73,15 @@ class PParser(object):
|
||||
else:
|
||||
end = min(self.docSize, end)
|
||||
foundat = -1
|
||||
for j in xrange(pos, end):
|
||||
for j in range(pos, end):
|
||||
item = self.flatdoc[j]
|
||||
if item.find('=') >= 0:
|
||||
(name, argres) = item.split('=',1)
|
||||
if item.find(b'=') >= 0:
|
||||
(name, argres) = item.split(b'=',1)
|
||||
else :
|
||||
name = item
|
||||
argres = ''
|
||||
argres = b''
|
||||
if (isinstance(tagpath,str)):
|
||||
tagpath = tagpath.encode('utf-8')
|
||||
if name.endswith(tagpath) :
|
||||
result = argres
|
||||
foundat = j
|
||||
@@ -101,11 +103,11 @@ class PParser(object):
|
||||
def getData(self, path):
|
||||
result = None
|
||||
cnt = len(self.flatdoc)
|
||||
for j in xrange(cnt):
|
||||
for j in range(cnt):
|
||||
item = self.flatdoc[j]
|
||||
if item.find('=') >= 0:
|
||||
(name, argt) = item.split('=')
|
||||
argres = argt.split('|')
|
||||
if item.find(b'=') >= 0:
|
||||
(name, argt) = item.split(b'=')
|
||||
argres = argt.split(b'|')
|
||||
else:
|
||||
name = item
|
||||
argres = []
|
||||
@@ -113,22 +115,24 @@ class PParser(object):
|
||||
result = argres
|
||||
break
|
||||
if (len(argres) > 0) :
|
||||
for j in xrange(0,len(argres)):
|
||||
for j in range(0,len(argres)):
|
||||
argres[j] = int(argres[j])
|
||||
return result
|
||||
|
||||
def getDataatPos(self, path, pos):
|
||||
result = None
|
||||
item = self.flatdoc[pos]
|
||||
if item.find('=') >= 0:
|
||||
(name, argt) = item.split('=')
|
||||
argres = argt.split('|')
|
||||
if item.find(b'=') >= 0:
|
||||
(name, argt) = item.split(b'=')
|
||||
argres = argt.split(b'|')
|
||||
else:
|
||||
name = item
|
||||
argres = []
|
||||
if (len(argres) > 0) :
|
||||
for j in xrange(0,len(argres)):
|
||||
for j in range(0,len(argres)):
|
||||
argres[j] = int(argres[j])
|
||||
if (isinstance(path,str)):
|
||||
path = path.encode('utf-8')
|
||||
if (name.endswith(path)):
|
||||
result = argres
|
||||
return result
|
||||
@@ -136,20 +140,22 @@ class PParser(object):
|
||||
def getDataTemp(self, path):
|
||||
result = None
|
||||
cnt = len(self.temp)
|
||||
for j in xrange(cnt):
|
||||
for j in range(cnt):
|
||||
item = self.temp[j]
|
||||
if item.find('=') >= 0:
|
||||
(name, argt) = item.split('=')
|
||||
argres = argt.split('|')
|
||||
if item.find(b'=') >= 0:
|
||||
(name, argt) = item.split(b'=')
|
||||
argres = argt.split(b'|')
|
||||
else:
|
||||
name = item
|
||||
argres = []
|
||||
if (isinstance(path,str)):
|
||||
path = path.encode('utf-8')
|
||||
if (name.endswith(path)):
|
||||
result = argres
|
||||
self.temp.pop(j)
|
||||
break
|
||||
if (len(argres) > 0) :
|
||||
for j in xrange(0,len(argres)):
|
||||
for j in range(0,len(argres)):
|
||||
argres[j] = int(argres[j])
|
||||
return result
|
||||
|
||||
@@ -220,15 +226,15 @@ def convert2SVG(gdict, flat_xml, pageid, previd, nextid, svgDir, raw, meta_array
|
||||
if (pp.gid != None):
|
||||
mlst.append('<defs>\n')
|
||||
gdefs = pp.getGlyphs()
|
||||
for j in xrange(0,len(gdefs)):
|
||||
for j in range(0,len(gdefs)):
|
||||
mlst.append(gdefs[j])
|
||||
mlst.append('</defs>\n')
|
||||
img = pp.getImages()
|
||||
if (img != None):
|
||||
for j in xrange(0,len(img)):
|
||||
for j in range(0,len(img)):
|
||||
mlst.append(img[j])
|
||||
if (pp.gid != None):
|
||||
for j in xrange(0,len(pp.gid)):
|
||||
for j in range(0,len(pp.gid)):
|
||||
mlst.append('<use xlink:href="#gl%d" x="%d" y="%d" />\n' % (pp.gid[j], pp.gx[j], pp.gy[j]))
|
||||
if (img == None or len(img) == 0) and (pp.gid == None or len(pp.gid) == 0):
|
||||
xpos = "%d" % (pp.pw // 3)
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
#! /usr/bin/python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||
|
||||
# Python 3 for calibre 5.0
|
||||
from __future__ import print_function
|
||||
from .convert2xml import encodeNumber
|
||||
|
||||
class Unbuffered:
|
||||
# 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):
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
if isinstance(data, str):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.buffer.write(data)
|
||||
self.stream.buffer.flush()
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
import sys
|
||||
sys.stdout=Unbuffered(sys.stdout)
|
||||
|
||||
import csv
|
||||
import os
|
||||
import getopt
|
||||
@@ -87,13 +94,13 @@ def readString(file):
|
||||
def getMetaArray(metaFile):
|
||||
# parse the meta file
|
||||
result = {}
|
||||
fo = file(metaFile,'rb')
|
||||
fo = open(metaFile,'rb')
|
||||
size = readEncodedNumber(fo)
|
||||
for i in xrange(size):
|
||||
for i in range(size):
|
||||
tag = readString(fo)
|
||||
value = readString(fo)
|
||||
result[tag] = value
|
||||
# print tag, value
|
||||
# print(tag, value)
|
||||
fo.close()
|
||||
return result
|
||||
|
||||
@@ -103,17 +110,17 @@ class Dictionary(object):
|
||||
def __init__(self, dictFile):
|
||||
self.filename = dictFile
|
||||
self.size = 0
|
||||
self.fo = file(dictFile,'rb')
|
||||
self.fo = open(dictFile,'rb')
|
||||
self.stable = []
|
||||
self.size = readEncodedNumber(self.fo)
|
||||
for i in xrange(self.size):
|
||||
for i in range(self.size):
|
||||
self.stable.append(self.escapestr(readString(self.fo)))
|
||||
self.pos = 0
|
||||
def escapestr(self, str):
|
||||
str = str.replace('&','&')
|
||||
str = str.replace('<','<')
|
||||
str = str.replace('>','>')
|
||||
str = str.replace('=','=')
|
||||
str = str.replace(b'&',b'&')
|
||||
str = str.replace(b'<',b'<')
|
||||
str = str.replace(b'>',b'>')
|
||||
str = str.replace(b'=',b'=')
|
||||
return str
|
||||
def lookup(self,val):
|
||||
if ((val >= 0) and (val < self.size)) :
|
||||
@@ -131,7 +138,7 @@ class Dictionary(object):
|
||||
|
||||
class PageDimParser(object):
|
||||
def __init__(self, flatxml):
|
||||
self.flatdoc = flatxml.split('\n')
|
||||
self.flatdoc = flatxml.split(b'\n')
|
||||
# find tag if within pos to end inclusive
|
||||
def findinDoc(self, tagpath, pos, end) :
|
||||
result = None
|
||||
@@ -142,10 +149,10 @@ class PageDimParser(object):
|
||||
else:
|
||||
end = min(cnt,end)
|
||||
foundat = -1
|
||||
for j in xrange(pos, end):
|
||||
for j in range(pos, end):
|
||||
item = docList[j]
|
||||
if item.find('=') >= 0:
|
||||
(name, argres) = item.split('=')
|
||||
if item.find(b'=') >= 0:
|
||||
(name, argres) = item.split(b'=')
|
||||
else :
|
||||
name = item
|
||||
argres = ''
|
||||
@@ -155,8 +162,8 @@ class PageDimParser(object):
|
||||
break
|
||||
return foundat, result
|
||||
def process(self):
|
||||
(pos, sph) = self.findinDoc('page.h',0,-1)
|
||||
(pos, spw) = self.findinDoc('page.w',0,-1)
|
||||
(pos, sph) = self.findinDoc(b'page.h',0,-1)
|
||||
(pos, spw) = self.findinDoc(b'page.w',0,-1)
|
||||
if (sph == None): sph = '-1'
|
||||
if (spw == None): spw = '-1'
|
||||
return sph, spw
|
||||
@@ -169,21 +176,21 @@ def getPageDim(flatxml):
|
||||
|
||||
class GParser(object):
|
||||
def __init__(self, flatxml):
|
||||
self.flatdoc = flatxml.split('\n')
|
||||
self.flatdoc = flatxml.split(b'\n')
|
||||
self.dpi = 1440
|
||||
self.gh = self.getData('info.glyph.h')
|
||||
self.gw = self.getData('info.glyph.w')
|
||||
self.guse = self.getData('info.glyph.use')
|
||||
self.gh = self.getData(b'info.glyph.h')
|
||||
self.gw = self.getData(b'info.glyph.w')
|
||||
self.guse = self.getData(b'info.glyph.use')
|
||||
if self.guse :
|
||||
self.count = len(self.guse)
|
||||
else :
|
||||
self.count = 0
|
||||
self.gvtx = self.getData('info.glyph.vtx')
|
||||
self.glen = self.getData('info.glyph.len')
|
||||
self.gdpi = self.getData('info.glyph.dpi')
|
||||
self.vx = self.getData('info.vtx.x')
|
||||
self.vy = self.getData('info.vtx.y')
|
||||
self.vlen = self.getData('info.len.n')
|
||||
self.gvtx = self.getData(b'info.glyph.vtx')
|
||||
self.glen = self.getData(b'info.glyph.len')
|
||||
self.gdpi = self.getData(b'info.glyph.dpi')
|
||||
self.vx = self.getData(b'info.vtx.x')
|
||||
self.vy = self.getData(b'info.vtx.y')
|
||||
self.vlen = self.getData(b'info.len.n')
|
||||
if self.vlen :
|
||||
self.glen.append(len(self.vlen))
|
||||
elif self.glen:
|
||||
@@ -195,11 +202,11 @@ class GParser(object):
|
||||
def getData(self, path):
|
||||
result = None
|
||||
cnt = len(self.flatdoc)
|
||||
for j in xrange(cnt):
|
||||
for j in range(cnt):
|
||||
item = self.flatdoc[j]
|
||||
if item.find('=') >= 0:
|
||||
(name, argt) = item.split('=')
|
||||
argres = argt.split('|')
|
||||
if item.find(b'=') >= 0:
|
||||
(name, argt) = item.split(b'=')
|
||||
argres = argt.split(b'|')
|
||||
else:
|
||||
name = item
|
||||
argres = []
|
||||
@@ -207,7 +214,7 @@ class GParser(object):
|
||||
result = argres
|
||||
break
|
||||
if (len(argres) > 0) :
|
||||
for j in xrange(0,len(argres)):
|
||||
for j in range(0,len(argres)):
|
||||
argres[j] = int(argres[j])
|
||||
return result
|
||||
def getGlyphDim(self, gly):
|
||||
@@ -223,7 +230,7 @@ class GParser(object):
|
||||
tx = self.vx[self.gvtx[gly]:self.gvtx[gly+1]]
|
||||
ty = self.vy[self.gvtx[gly]:self.gvtx[gly+1]]
|
||||
p = 0
|
||||
for k in xrange(self.glen[gly], self.glen[gly+1]):
|
||||
for k in range(self.glen[gly], self.glen[gly+1]):
|
||||
if (p == 0):
|
||||
zx = tx[0:self.vlen[k]+1]
|
||||
zy = ty[0:self.vlen[k]+1]
|
||||
@@ -322,17 +329,17 @@ def generateBook(bookDir, raw, fixedimage):
|
||||
imgname = filename.replace('color','img')
|
||||
sfile = os.path.join(spath,filename)
|
||||
dfile = os.path.join(dpath,imgname)
|
||||
imgdata = file(sfile,'rb').read()
|
||||
file(dfile,'wb').write(imgdata)
|
||||
imgdata = open(sfile,'rb').read()
|
||||
open(dfile,'wb').write(imgdata)
|
||||
|
||||
print("Creating cover.jpg")
|
||||
isCover = False
|
||||
cpath = os.path.join(bookDir,'img')
|
||||
cpath = os.path.join(cpath,'img0000.jpg')
|
||||
if os.path.isfile(cpath):
|
||||
cover = file(cpath, 'rb').read()
|
||||
cover = open(cpath, 'rb').read()
|
||||
cpath = os.path.join(bookDir,'cover.jpg')
|
||||
file(cpath, 'wb').write(cover)
|
||||
open(cpath, 'wb').write(cover)
|
||||
isCover = True
|
||||
|
||||
|
||||
@@ -361,7 +368,7 @@ def generateBook(bookDir, raw, fixedimage):
|
||||
mlst.append('<meta name="' + key + '" content="' + meta_array[key] + '" />\n')
|
||||
metastr = "".join(mlst)
|
||||
mlst = None
|
||||
file(xname, 'wb').write(metastr)
|
||||
open(xname, 'wb').write(metastr)
|
||||
|
||||
print('Processing StyleSheet')
|
||||
|
||||
@@ -424,10 +431,10 @@ def generateBook(bookDir, raw, fixedimage):
|
||||
|
||||
# now get the css info
|
||||
cssstr , classlst = stylexml2css.convert2CSS(flat_xml, fontsize, ph, pw)
|
||||
file(xname, 'wb').write(cssstr)
|
||||
open(xname, 'w').write(cssstr)
|
||||
if buildXML:
|
||||
xname = os.path.join(xmlDir, 'other0000.xml')
|
||||
file(xname, 'wb').write(convert2xml.getXML(dict, otherFile))
|
||||
open(xname, 'wb').write(convert2xml.getXML(dict, otherFile))
|
||||
|
||||
print('Processing Glyphs')
|
||||
gd = GlyphDict()
|
||||
@@ -449,10 +456,10 @@ def generateBook(bookDir, raw, fixedimage):
|
||||
|
||||
if buildXML:
|
||||
xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
|
||||
file(xname, 'wb').write(convert2xml.getXML(dict, fname))
|
||||
open(xname, 'wb').write(convert2xml.getXML(dict, fname))
|
||||
|
||||
gp = GParser(flat_xml)
|
||||
for i in xrange(0, gp.count):
|
||||
for i in range(0, gp.count):
|
||||
path = gp.getPath(i)
|
||||
maxh, maxw = gp.getGlyphDim(i)
|
||||
fullpath = '<path id="gl%d" d="%s" fill="black" /><!-- width=%d height=%d -->\n' % (counter * 256 + i, path, maxw, maxh)
|
||||
@@ -507,7 +514,7 @@ def generateBook(bookDir, raw, fixedimage):
|
||||
|
||||
if buildXML:
|
||||
xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
|
||||
file(xname, 'wb').write(convert2xml.getXML(dict, fname))
|
||||
open(xname, 'wb').write(convert2xml.getXML(dict, fname))
|
||||
|
||||
# first get the html
|
||||
pagehtml, tocinfo = flatxml2html.convert2HTML(flat_xml, classlst, fname, bookDir, gd, fixedimage)
|
||||
@@ -518,7 +525,7 @@ def generateBook(bookDir, raw, fixedimage):
|
||||
hlst.append('</body>\n</html>\n')
|
||||
htmlstr = "".join(hlst)
|
||||
hlst = None
|
||||
file(os.path.join(bookDir, htmlFileName), 'wb').write(htmlstr)
|
||||
open(os.path.join(bookDir, htmlFileName), 'w').write(htmlstr)
|
||||
|
||||
print(" ")
|
||||
print('Extracting Table of Contents from Amazon OCR')
|
||||
@@ -564,7 +571,7 @@ def generateBook(bookDir, raw, fixedimage):
|
||||
tlst.append('</body>\n')
|
||||
tlst.append('</html>\n')
|
||||
tochtml = "".join(tlst)
|
||||
file(os.path.join(svgDir, 'toc.xhtml'), 'wb').write(tochtml)
|
||||
open(os.path.join(svgDir, 'toc.xhtml'), 'w').write(tochtml)
|
||||
|
||||
|
||||
# now create index_svg.xhtml that points to all required files
|
||||
@@ -601,7 +608,7 @@ def generateBook(bookDir, raw, fixedimage):
|
||||
flst = []
|
||||
for page in pagelst:
|
||||
flst.append(xmllst[page])
|
||||
flat_svg = "".join(flst)
|
||||
flat_svg = b"".join(flst)
|
||||
flst=None
|
||||
svgxml = flatxml2svg.convert2SVG(gd, flat_svg, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi)
|
||||
if (raw) :
|
||||
@@ -619,7 +626,7 @@ def generateBook(bookDir, raw, fixedimage):
|
||||
slst.append('</body>\n</html>\n')
|
||||
svgindex = "".join(slst)
|
||||
slst = None
|
||||
file(os.path.join(bookDir, 'index_svg.xhtml'), 'wb').write(svgindex)
|
||||
open(os.path.join(bookDir, 'index_svg.xhtml'), 'w').write(svgindex)
|
||||
|
||||
print(" ")
|
||||
|
||||
@@ -630,16 +637,16 @@ def generateBook(bookDir, raw, fixedimage):
|
||||
olst.append('<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n')
|
||||
# adding metadata
|
||||
olst.append(' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n')
|
||||
if 'GUID' in meta_array:
|
||||
olst.append(' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n')
|
||||
if 'ASIN' in meta_array:
|
||||
olst.append(' <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n')
|
||||
if 'oASIN' in meta_array:
|
||||
olst.append(' <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n')
|
||||
olst.append(' <dc:title>' + meta_array['Title'] + '</dc:title>\n')
|
||||
olst.append(' <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n')
|
||||
if b'GUID' in meta_array:
|
||||
olst.append(' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array[b'GUID'].decode('utf-8') + '</dc:identifier>\n')
|
||||
if b'ASIN' in meta_array:
|
||||
olst.append(' <dc:identifier opf:scheme="ASIN">' + meta_array[b'ASIN'].decode('utf-8') + '</dc:identifier>\n')
|
||||
if b'oASIN' in meta_array:
|
||||
olst.append(' <dc:identifier opf:scheme="oASIN">' + meta_array[b'oASIN'].decode('utf-8') + '</dc:identifier>\n')
|
||||
olst.append(' <dc:title>' + meta_array[b'Title'].decode('utf-8') + '</dc:title>\n')
|
||||
olst.append(' <dc:creator opf:role="aut">' + meta_array[b'Authors'].decode('utf-8') + '</dc:creator>\n')
|
||||
olst.append(' <dc:language>en</dc:language>\n')
|
||||
olst.append(' <dc:date>' + meta_array['UpdateTime'] + '</dc:date>\n')
|
||||
olst.append(' <dc:date>' + meta_array[b'UpdateTime'].decode('utf-8') + '</dc:date>\n')
|
||||
if isCover:
|
||||
olst.append(' <meta name="cover" content="bookcover"/>\n')
|
||||
olst.append(' </metadata>\n')
|
||||
@@ -668,7 +675,7 @@ def generateBook(bookDir, raw, fixedimage):
|
||||
olst.append('</package>\n')
|
||||
opfstr = "".join(olst)
|
||||
olst = None
|
||||
file(opfname, 'wb').write(opfstr)
|
||||
open(opfname, 'w').write(opfstr)
|
||||
|
||||
print('Processing Complete')
|
||||
|
||||
@@ -687,6 +694,8 @@ def usage():
|
||||
|
||||
|
||||
def main(argv):
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
bookDir = ''
|
||||
if len(argv) == 0:
|
||||
argv = sys.argv
|
||||
@@ -694,7 +703,7 @@ def main(argv):
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], "rh:",["fixed-image"])
|
||||
|
||||
except getopt.GetoptError, err:
|
||||
except getopt.GetoptError as err:
|
||||
print(str(err))
|
||||
usage()
|
||||
return 1
|
||||
|
||||
@@ -1,27 +1,13 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ignobleepub.pyw, version 4.1
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
# ignobleepub.py
|
||||
# Copyright © 2009-2020 by i♥cabbages, Apprentice Harper et al.
|
||||
|
||||
# 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–2017 by Apprentice Harper
|
||||
|
||||
# Windows users: Before running this program, you must first install Python 2.6
|
||||
# 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
|
||||
# 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
|
||||
# program from the command line (pythonw ineptepub.pyw) or by double-clicking
|
||||
# it when it has been associated with PythonLauncher.
|
||||
|
||||
# Revision history:
|
||||
# 1 - Initial release
|
||||
# 2 - Added OS X support by using OpenSSL when available
|
||||
@@ -37,18 +23,19 @@ from __future__ import with_statement
|
||||
# 3.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 4.0 - Work if TkInter is missing
|
||||
# 4.1 - Import tkFileDialog, don't assume something else will import it.
|
||||
# 5.0 - Python 3 for calibre 5.0
|
||||
|
||||
"""
|
||||
Decrypt Barnes & Noble encrypted ePub books.
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "4.1"
|
||||
__version__ = "5.0"
|
||||
|
||||
import sys
|
||||
import os
|
||||
import traceback
|
||||
import base64
|
||||
import zlib
|
||||
import zipfile
|
||||
from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED
|
||||
@@ -65,10 +52,10 @@ class SafeUnbuffered:
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
if isinstance(data,str):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
self.stream.buffer.write(data)
|
||||
self.stream.buffer.flush()
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
@@ -106,13 +93,11 @@ def unicode_argv():
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
return [u"ineptepub.py"]
|
||||
range(start, argc.value)]
|
||||
return ["ineptepub.py"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = "utf-8"
|
||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||
argvencoding = sys.stdin.encoding or "utf-8"
|
||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||
|
||||
|
||||
class IGNOBLEError(Exception):
|
||||
@@ -167,7 +152,7 @@ def _load_crypto_libcrypto():
|
||||
|
||||
def decrypt(self, data):
|
||||
out = create_string_buffer(len(data))
|
||||
iv = ("\x00" * self._blocksize)
|
||||
iv = (b'\x00' * self._blocksize)
|
||||
rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
|
||||
if rv == 0:
|
||||
raise IGNOBLEError('AES decryption failed')
|
||||
@@ -180,7 +165,7 @@ def _load_crypto_pycrypto():
|
||||
|
||||
class AES(object):
|
||||
def __init__(self, key):
|
||||
self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16)
|
||||
self._aes = _AES.new(key, _AES.MODE_CBC, b'\x00'*16)
|
||||
|
||||
def decrypt(self, data):
|
||||
return self._aes.decrypt(data)
|
||||
@@ -223,15 +208,15 @@ class Decryptor(object):
|
||||
def decompress(self, bytes):
|
||||
dc = zlib.decompressobj(-15)
|
||||
bytes = dc.decompress(bytes)
|
||||
ex = dc.decompress('Z') + dc.flush()
|
||||
ex = dc.decompress(b'Z') + dc.flush()
|
||||
if ex:
|
||||
bytes = bytes + ex
|
||||
return bytes
|
||||
|
||||
def decrypt(self, path, data):
|
||||
if path in self._encrypted:
|
||||
if bytes(path,'utf-8') in self._encrypted:
|
||||
data = self._aes.decrypt(data)[16:]
|
||||
data = data[:-ord(data[-1])]
|
||||
data = data[:-data[-1]]
|
||||
data = self.decompress(data)
|
||||
return data
|
||||
|
||||
@@ -256,14 +241,14 @@ def ignobleBook(inpath):
|
||||
|
||||
def decryptBook(keyb64, inpath, outpath):
|
||||
if AES is None:
|
||||
raise IGNOBLEError(u"PyCrypto or OpenSSL must be installed.")
|
||||
key = keyb64.decode('base64')[:16]
|
||||
raise IGNOBLEError("PyCrypto or OpenSSL must be installed.")
|
||||
key = base64.b64decode(keyb64)[:16]
|
||||
aes = AES(key)
|
||||
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
||||
namelist = set(inf.namelist())
|
||||
if 'META-INF/rights.xml' not in namelist or \
|
||||
'META-INF/encryption.xml' not in namelist:
|
||||
print(u"{0:s} is DRM-free.".format(os.path.basename(inpath)))
|
||||
print("{0:s} is DRM-free.".format(os.path.basename(inpath)))
|
||||
return 1
|
||||
for name in META_NAMES:
|
||||
namelist.remove(name)
|
||||
@@ -273,10 +258,10 @@ def decryptBook(keyb64, inpath, outpath):
|
||||
expr = './/%s' % (adept('encryptedKey'),)
|
||||
bookkey = ''.join(rights.findtext(expr))
|
||||
if len(bookkey) != 64:
|
||||
print(u"{0:s} is not a secure Barnes & Noble ePub.".format(os.path.basename(inpath)))
|
||||
print("{0:s} is not a secure Barnes & Noble ePub.".format(os.path.basename(inpath)))
|
||||
return 1
|
||||
bookkey = aes.decrypt(bookkey.decode('base64'))
|
||||
bookkey = bookkey[:-ord(bookkey[-1])]
|
||||
bookkey = aes.decrypt(base64.b64decode(bookkey))
|
||||
bookkey = bookkey[:-bookkey[-1]]
|
||||
encryption = inf.read('META-INF/encryption.xml')
|
||||
decryptor = Decryptor(bookkey[-16:], encryption)
|
||||
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
|
||||
@@ -316,7 +301,7 @@ def decryptBook(keyb64, inpath, outpath):
|
||||
pass
|
||||
outf.writestr(zi, decryptor.decrypt(path, data))
|
||||
except:
|
||||
print(u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc()))
|
||||
print("Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc()))
|
||||
return 2
|
||||
return 0
|
||||
|
||||
@@ -327,90 +312,90 @@ def cli_main():
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if len(argv) != 4:
|
||||
print(u"usage: {0} <keyfile.b64> <inbook.epub> <outbook.epub>".format(progname))
|
||||
print("usage: {0} <keyfile.b64> <inbook.epub> <outbook.epub>".format(progname))
|
||||
return 1
|
||||
keypath, inpath, outpath = argv[1:]
|
||||
userkey = open(keypath,'rb').read()
|
||||
result = decryptBook(userkey, inpath, outpath)
|
||||
if result == 0:
|
||||
print(u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)))
|
||||
print("Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)))
|
||||
return result
|
||||
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import tkMessageBox
|
||||
import tkinter
|
||||
import tkinter.constants
|
||||
import tkinter.filedialog
|
||||
import tkinter.messagebox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
class DecryptionDialog(tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = Tkinter.Label(self, text=u"Select files for decryption")
|
||||
self.status.pack(fill=Tkconstants.X, expand=1)
|
||||
body = Tkinter.Frame(self)
|
||||
body.pack(fill=Tkconstants.X, expand=1)
|
||||
sticky = Tkconstants.E + Tkconstants.W
|
||||
tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = tkinter.Label(self, text="Select files for decryption")
|
||||
self.status.pack(fill=tkinter.constants.X, expand=1)
|
||||
body = tkinter.Frame(self)
|
||||
body.pack(fill=tkinter.constants.X, expand=1)
|
||||
sticky = tkinter.constants.E + tkinter.constants.W
|
||||
body.grid_columnconfigure(1, weight=2)
|
||||
Tkinter.Label(body, text=u"Key file").grid(row=0)
|
||||
self.keypath = Tkinter.Entry(body, width=30)
|
||||
tkinter.Label(body, text="Key file").grid(row=0)
|
||||
self.keypath = tkinter.Entry(body, width=30)
|
||||
self.keypath.grid(row=0, column=1, sticky=sticky)
|
||||
if os.path.exists(u"bnepubkey.b64"):
|
||||
self.keypath.insert(0, u"bnepubkey.b64")
|
||||
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
|
||||
if os.path.exists("bnepubkey.b64"):
|
||||
self.keypath.insert(0, "bnepubkey.b64")
|
||||
button = tkinter.Button(body, text="...", command=self.get_keypath)
|
||||
button.grid(row=0, column=2)
|
||||
Tkinter.Label(body, text=u"Input file").grid(row=1)
|
||||
self.inpath = Tkinter.Entry(body, width=30)
|
||||
tkinter.Label(body, text="Input file").grid(row=1)
|
||||
self.inpath = tkinter.Entry(body, width=30)
|
||||
self.inpath.grid(row=1, column=1, sticky=sticky)
|
||||
button = Tkinter.Button(body, text=u"...", command=self.get_inpath)
|
||||
button = tkinter.Button(body, text="...", command=self.get_inpath)
|
||||
button.grid(row=1, column=2)
|
||||
Tkinter.Label(body, text=u"Output file").grid(row=2)
|
||||
self.outpath = Tkinter.Entry(body, width=30)
|
||||
tkinter.Label(body, text="Output file").grid(row=2)
|
||||
self.outpath = tkinter.Entry(body, width=30)
|
||||
self.outpath.grid(row=2, column=1, sticky=sticky)
|
||||
button = Tkinter.Button(body, text=u"...", command=self.get_outpath)
|
||||
button = tkinter.Button(body, text="...", command=self.get_outpath)
|
||||
button.grid(row=2, column=2)
|
||||
buttons = Tkinter.Frame(self)
|
||||
buttons = tkinter.Frame(self)
|
||||
buttons.pack()
|
||||
botton = Tkinter.Button(
|
||||
buttons, text=u"Decrypt", width=10, command=self.decrypt)
|
||||
botton.pack(side=Tkconstants.LEFT)
|
||||
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
||||
button = Tkinter.Button(
|
||||
buttons, text=u"Quit", width=10, command=self.quit)
|
||||
button.pack(side=Tkconstants.RIGHT)
|
||||
botton = tkinter.Button(
|
||||
buttons, text="Decrypt", width=10, command=self.decrypt)
|
||||
botton.pack(side=tkinter.constants.LEFT)
|
||||
tkinter.Frame(buttons, width=10).pack(side=tkinter.constants.LEFT)
|
||||
button = tkinter.Button(
|
||||
buttons, text="Quit", width=10, command=self.quit)
|
||||
button.pack(side=tkinter.constants.RIGHT)
|
||||
|
||||
def get_keypath(self):
|
||||
keypath = tkFileDialog.askopenfilename(
|
||||
parent=None, title=u"Select Barnes & Noble \'.b64\' key file",
|
||||
defaultextension=u".b64",
|
||||
keypath = tkinter.filedialog.askopenfilename(
|
||||
parent=None, title="Select Barnes & Noble \'.b64\' key file",
|
||||
defaultextension=".b64",
|
||||
filetypes=[('base64-encoded files', '.b64'),
|
||||
('All Files', '.*')])
|
||||
if keypath:
|
||||
keypath = os.path.normpath(keypath)
|
||||
self.keypath.delete(0, Tkconstants.END)
|
||||
self.keypath.delete(0, tkinter.constants.END)
|
||||
self.keypath.insert(0, keypath)
|
||||
return
|
||||
|
||||
def get_inpath(self):
|
||||
inpath = tkFileDialog.askopenfilename(
|
||||
parent=None, title=u"Select B&N-encrypted ePub file to decrypt",
|
||||
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
|
||||
inpath = tkinter.filedialog.askopenfilename(
|
||||
parent=None, title="Select B&N-encrypted ePub file to decrypt",
|
||||
defaultextension=".epub", filetypes=[('ePub files', '.epub')])
|
||||
if inpath:
|
||||
inpath = os.path.normpath(inpath)
|
||||
self.inpath.delete(0, Tkconstants.END)
|
||||
self.inpath.delete(0, tkinter.constants.END)
|
||||
self.inpath.insert(0, inpath)
|
||||
return
|
||||
|
||||
def get_outpath(self):
|
||||
outpath = tkFileDialog.asksaveasfilename(
|
||||
parent=None, title=u"Select unencrypted ePub file to produce",
|
||||
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
|
||||
outpath = tkinter.filedialog.asksaveasfilename(
|
||||
parent=None, title="Select unencrypted ePub file to produce",
|
||||
defaultextension=".epub", filetypes=[('ePub files', '.epub')])
|
||||
if outpath:
|
||||
outpath = os.path.normpath(outpath)
|
||||
self.outpath.delete(0, Tkconstants.END)
|
||||
self.outpath.delete(0, tkinter.constants.END)
|
||||
self.outpath.insert(0, outpath)
|
||||
return
|
||||
|
||||
@@ -419,34 +404,34 @@ def gui_main():
|
||||
inpath = self.inpath.get()
|
||||
outpath = self.outpath.get()
|
||||
if not keypath or not os.path.exists(keypath):
|
||||
self.status['text'] = u"Specified key file does not exist"
|
||||
self.status['text'] = "Specified key file does not exist"
|
||||
return
|
||||
if not inpath or not os.path.exists(inpath):
|
||||
self.status['text'] = u"Specified input file does not exist"
|
||||
self.status['text'] = "Specified input file does not exist"
|
||||
return
|
||||
if not outpath:
|
||||
self.status['text'] = u"Output file not specified"
|
||||
self.status['text'] = "Output file not specified"
|
||||
return
|
||||
if inpath == outpath:
|
||||
self.status['text'] = u"Must have different input and output files"
|
||||
self.status['text'] = "Must have different input and output files"
|
||||
return
|
||||
userkey = open(keypath,'rb').read()
|
||||
self.status['text'] = u"Decrypting..."
|
||||
self.status['text'] = "Decrypting..."
|
||||
try:
|
||||
decrypt_status = decryptBook(userkey, inpath, outpath)
|
||||
except Exception, e:
|
||||
self.status['text'] = u"Error: {0}".format(e.args[0])
|
||||
except Exception as e:
|
||||
self.status['text'] = "Error: {0}".format(e.args[0])
|
||||
return
|
||||
if decrypt_status == 0:
|
||||
self.status['text'] = u"File successfully decrypted"
|
||||
self.status['text'] = "File successfully decrypted"
|
||||
else:
|
||||
self.status['text'] = u"The was an error decrypting the file."
|
||||
self.status['text'] = "The was an error decrypting the file."
|
||||
|
||||
root = Tkinter.Tk()
|
||||
root.title(u"Barnes & Noble ePub Decrypter v.{0}".format(__version__))
|
||||
root = tkinter.Tk()
|
||||
root.title("Barnes & Noble ePub Decrypter v.{0}".format(__version__))
|
||||
root.resizable(True, False)
|
||||
root.minsize(300, 0)
|
||||
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
||||
DecryptionDialog(root).pack(fill=tkinter.constants.X, expand=1)
|
||||
root.mainloop()
|
||||
return 0
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ignoblekey.py
|
||||
# Copyright © 2015 Apprentice Alf and Apprentice Harper
|
||||
# Copyright © 2015-2020 Apprentice Alf, Apprentice Harper et al.
|
||||
|
||||
# Based on kindlekey.py, Copyright © 2010-2013 by some_updates and Apprentice Alf
|
||||
|
||||
@@ -14,14 +12,14 @@ from __future__ import with_statement
|
||||
# Revision history:
|
||||
# 1.0 - Initial release
|
||||
# 1.1 - remove duplicates and return last key as single key
|
||||
# 2.0 - Python 3 for calibre 5.0
|
||||
|
||||
"""
|
||||
Get Barnes & Noble EPUB user key from nook Studio log file
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "1.1"
|
||||
__version__ = "2.0"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -39,10 +37,11 @@ class SafeUnbuffered:
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
if isinstance(data, str):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
self.stream.buffer.write(data)
|
||||
self.stream.buffer.flush()
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
@@ -80,15 +79,13 @@ def unicode_argv():
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
range(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"ignoblekey.py"]
|
||||
return ["ignoblekey.py"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = "utf-8"
|
||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||
argvencoding = sys.stdin.encoding or "utf-8"
|
||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||
|
||||
class DrmException(Exception):
|
||||
pass
|
||||
@@ -98,22 +95,22 @@ def getNookLogFiles():
|
||||
logFiles = []
|
||||
found = False
|
||||
if iswindows:
|
||||
import _winreg as winreg
|
||||
import winreg
|
||||
|
||||
# some 64 bit machines do not have the proper registry key for some reason
|
||||
# or the python interface to the 32 vs 64 bit registry is broken
|
||||
paths = set()
|
||||
if 'LOCALAPPDATA' in os.environ.keys():
|
||||
# Python 2.x does not return unicode env. Use Python 3.x
|
||||
path = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
|
||||
path = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%")
|
||||
if os.path.isdir(path):
|
||||
paths.add(path)
|
||||
if 'USERPROFILE' in os.environ.keys():
|
||||
# Python 2.x does not return unicode env. Use Python 3.x
|
||||
path = winreg.ExpandEnvironmentStrings(u"%USERPROFILE%")+u"\\AppData\\Local"
|
||||
path = winreg.ExpandEnvironmentStrings("%USERPROFILE%")+"\\AppData\\Local"
|
||||
if os.path.isdir(path):
|
||||
paths.add(path)
|
||||
path = winreg.ExpandEnvironmentStrings(u"%USERPROFILE%")+u"\\AppData\\Roaming"
|
||||
path = winreg.ExpandEnvironmentStrings("%USERPROFILE%")+"\\AppData\\Roaming"
|
||||
if os.path.isdir(path):
|
||||
paths.add(path)
|
||||
# User Shell Folders show take precedent over Shell Folders if present
|
||||
@@ -199,7 +196,7 @@ def nookkeys(files = []):
|
||||
for file in files:
|
||||
fileKeys = getKeysFromLog(file)
|
||||
if fileKeys:
|
||||
print(u"Found {0} keys in the Nook Study log files".format(len(fileKeys)))
|
||||
print("Found {0} keys in the Nook Study log files".format(len(fileKeys)))
|
||||
keys.extend(fileKeys)
|
||||
return list(set(keys))
|
||||
|
||||
@@ -210,29 +207,29 @@ def getkey(outpath, files=[]):
|
||||
if len(keys) > 0:
|
||||
if not os.path.isdir(outpath):
|
||||
outfile = outpath
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
with open(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(keys[-1])
|
||||
print(u"Saved a key to {0}".format(outfile))
|
||||
print("Saved a key to {0}".format(outfile))
|
||||
else:
|
||||
keycount = 0
|
||||
for key in keys:
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(outpath,u"nookkey{0:d}.b64".format(keycount))
|
||||
outfile = os.path.join(outpath,"nookkey{0:d}.b64".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
with open(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(key)
|
||||
print(u"Saved a key to {0}".format(outfile))
|
||||
print("Saved a key to {0}".format(outfile))
|
||||
return True
|
||||
return False
|
||||
|
||||
def usage(progname):
|
||||
print(u"Finds the nook Study encryption keys.")
|
||||
print(u"Keys are saved to the current directory, or a specified output directory.")
|
||||
print(u"If a file name is passed instead of a directory, only the first key is saved, in that file.")
|
||||
print(u"Usage:")
|
||||
print(u" {0:s} [-h] [-k <logFile>] [<outpath>]".format(progname))
|
||||
print("Finds the nook Study encryption keys.")
|
||||
print("Keys are saved to the current directory, or a specified output directory.")
|
||||
print("If a file name is passed instead of a directory, only the first key is saved, in that file.")
|
||||
print("Usage:")
|
||||
print(" {0:s} [-h] [-k <logFile>] [<outpath>]".format(progname))
|
||||
|
||||
|
||||
def cli_main():
|
||||
@@ -240,12 +237,12 @@ def cli_main():
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print(u"{0} v{1}\nCopyright © 2015 Apprentice Alf".format(progname,__version__))
|
||||
print("{0} v{1}\nCopyright © 2015 Apprentice Alf".format(progname,__version__))
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], "hk:")
|
||||
except getopt.GetoptError, err:
|
||||
print(u"Error in options or arguments: {0}".format(err.args[0]))
|
||||
except getopt.GetoptError as err:
|
||||
print("Error in options or arguments: {0}".format(err.args[0]))
|
||||
usage(progname)
|
||||
sys.exit(2)
|
||||
|
||||
@@ -274,33 +271,33 @@ def cli_main():
|
||||
outpath = os.path.realpath(os.path.normpath(outpath))
|
||||
|
||||
if not getkey(outpath, files):
|
||||
print(u"Could not retrieve nook Study key.")
|
||||
print("Could not retrieve nook Study key.")
|
||||
return 0
|
||||
|
||||
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import tkinter
|
||||
import tkinter.constants
|
||||
import tkinter.messagebox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class ExceptionDialog(Tkinter.Frame):
|
||||
class ExceptionDialog(tkinter.Frame):
|
||||
def __init__(self, root, text):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
label = Tkinter.Label(self, text=u"Unexpected error:",
|
||||
anchor=Tkconstants.W, justify=Tkconstants.LEFT)
|
||||
label.pack(fill=Tkconstants.X, expand=0)
|
||||
self.text = Tkinter.Text(self)
|
||||
self.text.pack(fill=Tkconstants.BOTH, expand=1)
|
||||
tkinter.Frame.__init__(self, root, border=5)
|
||||
label = tkinter.Label(self, text="Unexpected error:",
|
||||
anchor=tkinter.constants.W, justify=tkinter.constants.LEFT)
|
||||
label.pack(fill=tkinter.constants.X, expand=0)
|
||||
self.text = tkinter.Text(self)
|
||||
self.text.pack(fill=tkinter.constants.BOTH, expand=1)
|
||||
|
||||
self.text.insert(Tkconstants.END, text)
|
||||
self.text.insert(tkinter.constants.END, text)
|
||||
|
||||
|
||||
argv=unicode_argv()
|
||||
root = Tkinter.Tk()
|
||||
root = tkinter.Tk()
|
||||
root.withdraw()
|
||||
progpath, progname = os.path.split(argv[0])
|
||||
success = False
|
||||
@@ -311,21 +308,21 @@ def gui_main():
|
||||
print(key)
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(progpath,u"nookkey{0:d}.b64".format(keycount))
|
||||
outfile = os.path.join(progpath,"nookkey{0:d}.b64".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
with open(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(key)
|
||||
success = True
|
||||
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
||||
except DrmException, e:
|
||||
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
|
||||
tkinter.messagebox.showinfo(progname, "Key successfully retrieved to {0}".format(outfile))
|
||||
except DrmException as e:
|
||||
tkinter.messagebox.showerror(progname, "Error: {0}".format(str(e)))
|
||||
except Exception:
|
||||
root.wm_state('normal')
|
||||
root.title(progname)
|
||||
text = traceback.format_exc()
|
||||
ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1)
|
||||
ExceptionDialog(root, text).pack(fill=tkinter.constants.BOTH, expand=1)
|
||||
root.mainloop()
|
||||
if not success:
|
||||
return 1
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ignoblekeyfetch.pyw, version 1.1
|
||||
# Copyright © 2015 Apprentice Harper
|
||||
# ignoblekeyfetch.py
|
||||
# Copyright © 2015-2020 Apprentice Harper et al.
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
@@ -24,14 +22,14 @@ from __future__ import with_statement
|
||||
# Revision history:
|
||||
# 1.0 - Initial version
|
||||
# 1.1 - Try second URL if first one fails
|
||||
# 2.0 - Python 3 for calibre 5.0
|
||||
|
||||
"""
|
||||
Fetch Barnes & Noble EPUB user key from B&N servers using email and password
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "1.1"
|
||||
__version__ = "2.0"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -46,10 +44,11 @@ class SafeUnbuffered:
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
if isinstance(data, str):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
self.stream.buffer.write(data)
|
||||
self.stream.buffer.flush()
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
@@ -87,15 +86,13 @@ def unicode_argv():
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
range(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"ignoblekeyfetch.py"]
|
||||
return ["ignoblekeyfetch.py"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = "utf-8"
|
||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||
argvencoding = sys.stdin.encoding or "utf-8"
|
||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||
|
||||
|
||||
class IGNOBLEError(Exception):
|
||||
@@ -103,26 +100,25 @@ class IGNOBLEError(Exception):
|
||||
|
||||
def fetch_key(email, password):
|
||||
# change email and password to utf-8 if unicode
|
||||
if type(email)==unicode:
|
||||
if type(email)==str:
|
||||
email = email.encode('utf-8')
|
||||
if type(password)==unicode:
|
||||
if type(password)==str:
|
||||
password = password.encode('utf-8')
|
||||
|
||||
import random
|
||||
random = "%030x" % random.randrange(16**30)
|
||||
|
||||
import urllib, urllib2, re
|
||||
import urllib.parse, urllib.request, re
|
||||
|
||||
# try the URL from nook for PC
|
||||
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
|
||||
fetch_url += urllib.quote(password,'')+"&devID=PC_BN_2.5.6.9575_"+random+"&emailAddress="
|
||||
fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
|
||||
fetch_url += urllib.parse.quote(password,'')+"&devID=PC_BN_2.5.6.9575_"+random+"&emailAddress="
|
||||
fetch_url += urllib.parse.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
|
||||
#print fetch_url
|
||||
|
||||
found = ''
|
||||
try:
|
||||
req = urllib2.Request(fetch_url)
|
||||
response = urllib2.urlopen(req)
|
||||
response = urllib.request.urlopen(fetch_url)
|
||||
the_page = response.read()
|
||||
#print the_page
|
||||
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
|
||||
@@ -131,14 +127,13 @@ def fetch_key(email, password):
|
||||
if len(found)!=28:
|
||||
# try the URL from android devices
|
||||
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
|
||||
fetch_url += urllib.quote(password,'')+"&devID=hobbes_9.3.50818_"+random+"&emailAddress="
|
||||
fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
|
||||
fetch_url += urllib.parse.quote(password,'')+"&devID=hobbes_9.3.50818_"+random+"&emailAddress="
|
||||
fetch_url += urllib.parse.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
|
||||
#print fetch_url
|
||||
|
||||
found = ''
|
||||
try:
|
||||
req = urllib2.Request(fetch_url)
|
||||
response = urllib2.urlopen(req)
|
||||
response = urllib.request.urlopen(fetch_url)
|
||||
the_page = response.read()
|
||||
#print the_page
|
||||
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
|
||||
@@ -156,67 +151,67 @@ def cli_main():
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if len(argv) != 4:
|
||||
print(u"usage: {0} <email> <password> <keyfileout.b64>".format(progname))
|
||||
print("usage: {0} <email> <password> <keyfileout.b64>".format(progname))
|
||||
return 1
|
||||
email, password, keypath = argv[1:]
|
||||
userkey = fetch_key(email, password)
|
||||
if len(userkey) == 28:
|
||||
open(keypath,'wb').write(userkey)
|
||||
return 0
|
||||
print(u"Failed to fetch key.")
|
||||
print("Failed to fetch key.")
|
||||
return 1
|
||||
|
||||
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import tkFileDialog
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import tkinter
|
||||
import tkinter.filedialog
|
||||
import tkinter.constants
|
||||
import tkinter.messagebox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
class DecryptionDialog(tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = Tkinter.Label(self, text=u"Enter parameters")
|
||||
self.status.pack(fill=Tkconstants.X, expand=1)
|
||||
body = Tkinter.Frame(self)
|
||||
body.pack(fill=Tkconstants.X, expand=1)
|
||||
sticky = Tkconstants.E + Tkconstants.W
|
||||
tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = tkinter.Label(self, text="Enter parameters")
|
||||
self.status.pack(fill=tkinter.constants.X, expand=1)
|
||||
body = tkinter.Frame(self)
|
||||
body.pack(fill=tkinter.constants.X, expand=1)
|
||||
sticky = tkinter.constants.E + tkinter.constants.W
|
||||
body.grid_columnconfigure(1, weight=2)
|
||||
Tkinter.Label(body, text=u"Account email address").grid(row=0)
|
||||
self.name = Tkinter.Entry(body, width=40)
|
||||
tkinter.Label(body, text="Account email address").grid(row=0)
|
||||
self.name = tkinter.Entry(body, width=40)
|
||||
self.name.grid(row=0, column=1, sticky=sticky)
|
||||
Tkinter.Label(body, text=u"Account password").grid(row=1)
|
||||
self.ccn = Tkinter.Entry(body, width=40)
|
||||
tkinter.Label(body, text="Account password").grid(row=1)
|
||||
self.ccn = tkinter.Entry(body, width=40)
|
||||
self.ccn.grid(row=1, column=1, sticky=sticky)
|
||||
Tkinter.Label(body, text=u"Output file").grid(row=2)
|
||||
self.keypath = Tkinter.Entry(body, width=40)
|
||||
tkinter.Label(body, text="Output file").grid(row=2)
|
||||
self.keypath = tkinter.Entry(body, width=40)
|
||||
self.keypath.grid(row=2, column=1, sticky=sticky)
|
||||
self.keypath.insert(2, u"bnepubkey.b64")
|
||||
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
|
||||
self.keypath.insert(2, "bnepubkey.b64")
|
||||
button = tkinter.Button(body, text="...", command=self.get_keypath)
|
||||
button.grid(row=2, column=2)
|
||||
buttons = Tkinter.Frame(self)
|
||||
buttons = tkinter.Frame(self)
|
||||
buttons.pack()
|
||||
botton = Tkinter.Button(
|
||||
buttons, text=u"Fetch", width=10, command=self.generate)
|
||||
botton.pack(side=Tkconstants.LEFT)
|
||||
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
||||
button = Tkinter.Button(
|
||||
buttons, text=u"Quit", width=10, command=self.quit)
|
||||
button.pack(side=Tkconstants.RIGHT)
|
||||
botton = tkinter.Button(
|
||||
buttons, text="Fetch", width=10, command=self.generate)
|
||||
botton.pack(side=tkinter.constants.LEFT)
|
||||
tkinter.Frame(buttons, width=10).pack(side=tkinter.constants.LEFT)
|
||||
button = tkinter.Button(
|
||||
buttons, text="Quit", width=10, command=self.quit)
|
||||
button.pack(side=tkinter.constants.RIGHT)
|
||||
|
||||
def get_keypath(self):
|
||||
keypath = tkFileDialog.asksaveasfilename(
|
||||
parent=None, title=u"Select B&N ePub key file to produce",
|
||||
defaultextension=u".b64",
|
||||
keypath = tkinter.filedialog.asksaveasfilename(
|
||||
parent=None, title="Select B&N ePub key file to produce",
|
||||
defaultextension=".b64",
|
||||
filetypes=[('base64-encoded files', '.b64'),
|
||||
('All Files', '.*')])
|
||||
if keypath:
|
||||
keypath = os.path.normpath(keypath)
|
||||
self.keypath.delete(0, Tkconstants.END)
|
||||
self.keypath.delete(0, tkinter.constants.END)
|
||||
self.keypath.insert(0, keypath)
|
||||
return
|
||||
|
||||
@@ -225,31 +220,31 @@ def gui_main():
|
||||
password = self.ccn.get()
|
||||
keypath = self.keypath.get()
|
||||
if not email:
|
||||
self.status['text'] = u"Email address not given"
|
||||
self.status['text'] = "Email address not given"
|
||||
return
|
||||
if not password:
|
||||
self.status['text'] = u"Account password not given"
|
||||
self.status['text'] = "Account password not given"
|
||||
return
|
||||
if not keypath:
|
||||
self.status['text'] = u"Output keyfile path not set"
|
||||
self.status['text'] = "Output keyfile path not set"
|
||||
return
|
||||
self.status['text'] = u"Fetching..."
|
||||
self.status['text'] = "Fetching..."
|
||||
try:
|
||||
userkey = fetch_key(email, password)
|
||||
except Exception, e:
|
||||
self.status['text'] = u"Error: {0}".format(e.args[0])
|
||||
except Exception as e:
|
||||
self.status['text'] = "Error: {0}".format(e.args[0])
|
||||
return
|
||||
if len(userkey) == 28:
|
||||
open(keypath,'wb').write(userkey)
|
||||
self.status['text'] = u"Keyfile fetched successfully"
|
||||
self.status['text'] = "Keyfile fetched successfully"
|
||||
else:
|
||||
self.status['text'] = u"Keyfile fetch failed."
|
||||
self.status['text'] = "Keyfile fetch failed."
|
||||
|
||||
root = Tkinter.Tk()
|
||||
root.title(u"Barnes & Noble ePub Keyfile Fetch v.{0}".format(__version__))
|
||||
root = tkinter.Tk()
|
||||
root.title("Barnes & Noble ePub Keyfile Fetch v.{0}".format(__version__))
|
||||
root.resizable(True, False)
|
||||
root.minsize(300, 0)
|
||||
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
||||
DecryptionDialog(root).pack(fill=tkinter.constants.X, expand=1)
|
||||
root.mainloop()
|
||||
return 0
|
||||
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ignoblekeygen.pyw, version 2.5
|
||||
# Copyright © 2009-2010 i♥cabbages
|
||||
# ignoblekeygen.py
|
||||
# Copyright © 2009-2020 i♥cabbages, Apprentice Harper et al.
|
||||
|
||||
# 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
|
||||
|
||||
# Windows users: Before running this program, you must first install Python.
|
||||
# We recommend ActiveState Python 2.7.X for Windows (x86) from
|
||||
# http://www.activestate.com/activepython/downloads.
|
||||
@@ -34,18 +30,19 @@ from __future__ import with_statement
|
||||
# 2.6 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 2.7 - Work if TkInter is missing
|
||||
# 2.8 - Fix bug in stand-alone use (import tkFileDialog)
|
||||
# 3.0 - Added Python 3 compatibility for calibre 5.0
|
||||
|
||||
"""
|
||||
Generate Barnes & Noble EPUB user key from name and credit card number.
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "2.8"
|
||||
__version__ = "3.0"
|
||||
|
||||
import sys
|
||||
import os
|
||||
import hashlib
|
||||
import base64
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
@@ -57,10 +54,11 @@ class SafeUnbuffered:
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
if isinstance(data, str):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
self.stream.buffer.write(data)
|
||||
self.stream.buffer.flush()
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
@@ -98,15 +96,13 @@ def unicode_argv():
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
range(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"ignoblekeygen.py"]
|
||||
return ["ignoblekeygen.py"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = "utf-8"
|
||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||
argvencoding = sys.stdin.encoding or "utf-8"
|
||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||
|
||||
|
||||
class IGNOBLEError(Exception):
|
||||
@@ -199,23 +195,24 @@ def normalize_name(name):
|
||||
|
||||
def generate_key(name, ccn):
|
||||
# remove spaces and case from name and CC numbers.
|
||||
if type(name)==unicode:
|
||||
name = normalize_name(name)
|
||||
ccn = normalize_name(ccn)
|
||||
|
||||
if type(name)==str:
|
||||
name = name.encode('utf-8')
|
||||
if type(ccn)==unicode:
|
||||
if type(ccn)==str:
|
||||
ccn = ccn.encode('utf-8')
|
||||
|
||||
name = normalize_name(name) + '\x00'
|
||||
ccn = normalize_name(ccn) + '\x00'
|
||||
name = name + b'\x00'
|
||||
ccn = ccn + b'\x00'
|
||||
|
||||
name_sha = hashlib.sha1(name).digest()[:16]
|
||||
ccn_sha = hashlib.sha1(ccn).digest()[:16]
|
||||
both_sha = hashlib.sha1(name + ccn).digest()
|
||||
aes = AES(ccn_sha, name_sha)
|
||||
crypt = aes.encrypt(both_sha + ('\x0c' * 0x0c))
|
||||
crypt = aes.encrypt(both_sha + (b'\x0c' * 0x0c))
|
||||
userkey = hashlib.sha1(crypt).digest()
|
||||
return userkey.encode('base64')
|
||||
|
||||
|
||||
return base64.b64encode(userkey)
|
||||
|
||||
|
||||
def cli_main():
|
||||
@@ -229,7 +226,7 @@ def cli_main():
|
||||
(progname,))
|
||||
return 1
|
||||
if len(argv) != 4:
|
||||
print(u"usage: {0} <Name> <CC#> <keyfileout.b64>".format(progname))
|
||||
print("usage: {0} <Name> <CC#> <keyfileout.b64>".format(progname))
|
||||
return 1
|
||||
name, ccn, keypath = argv[1:]
|
||||
userkey = generate_key(name, ccn)
|
||||
@@ -239,54 +236,54 @@ def cli_main():
|
||||
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import tkFileDialog
|
||||
import tkinter
|
||||
import tkinter.constants
|
||||
import tkinter.messagebox
|
||||
import tkinter.filedialog
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
class DecryptionDialog(tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = Tkinter.Label(self, text=u"Enter parameters")
|
||||
self.status.pack(fill=Tkconstants.X, expand=1)
|
||||
body = Tkinter.Frame(self)
|
||||
body.pack(fill=Tkconstants.X, expand=1)
|
||||
sticky = Tkconstants.E + Tkconstants.W
|
||||
tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = tkinter.Label(self, text="Enter parameters")
|
||||
self.status.pack(fill=tkinter.constants.X, expand=1)
|
||||
body = tkinter.Frame(self)
|
||||
body.pack(fill=tkinter.constants.X, expand=1)
|
||||
sticky = tkinter.constants.E + tkinter.constants.W
|
||||
body.grid_columnconfigure(1, weight=2)
|
||||
Tkinter.Label(body, text=u"Account Name").grid(row=0)
|
||||
self.name = Tkinter.Entry(body, width=40)
|
||||
tkinter.Label(body, text="Account Name").grid(row=0)
|
||||
self.name = tkinter.Entry(body, width=40)
|
||||
self.name.grid(row=0, column=1, sticky=sticky)
|
||||
Tkinter.Label(body, text=u"CC#").grid(row=1)
|
||||
self.ccn = Tkinter.Entry(body, width=40)
|
||||
tkinter.Label(body, text="CC#").grid(row=1)
|
||||
self.ccn = tkinter.Entry(body, width=40)
|
||||
self.ccn.grid(row=1, column=1, sticky=sticky)
|
||||
Tkinter.Label(body, text=u"Output file").grid(row=2)
|
||||
self.keypath = Tkinter.Entry(body, width=40)
|
||||
tkinter.Label(body, text="Output file").grid(row=2)
|
||||
self.keypath = tkinter.Entry(body, width=40)
|
||||
self.keypath.grid(row=2, column=1, sticky=sticky)
|
||||
self.keypath.insert(2, u"bnepubkey.b64")
|
||||
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
|
||||
self.keypath.insert(2, "bnepubkey.b64")
|
||||
button = tkinter.Button(body, text="...", command=self.get_keypath)
|
||||
button.grid(row=2, column=2)
|
||||
buttons = Tkinter.Frame(self)
|
||||
buttons = tkinter.Frame(self)
|
||||
buttons.pack()
|
||||
botton = Tkinter.Button(
|
||||
buttons, text=u"Generate", width=10, command=self.generate)
|
||||
botton.pack(side=Tkconstants.LEFT)
|
||||
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
||||
button = Tkinter.Button(
|
||||
buttons, text=u"Quit", width=10, command=self.quit)
|
||||
button.pack(side=Tkconstants.RIGHT)
|
||||
botton = tkinter.Button(
|
||||
buttons, text="Generate", width=10, command=self.generate)
|
||||
botton.pack(side=tkinter.constants.LEFT)
|
||||
tkinter.Frame(buttons, width=10).pack(side=tkinter.constants.LEFT)
|
||||
button = tkinter.Button(
|
||||
buttons, text="Quit", width=10, command=self.quit)
|
||||
button.pack(side=tkinter.constants.RIGHT)
|
||||
|
||||
def get_keypath(self):
|
||||
keypath = tkFileDialog.asksaveasfilename(
|
||||
parent=None, title=u"Select B&N ePub key file to produce",
|
||||
defaultextension=u".b64",
|
||||
keypath = tkinter.filedialog.asksaveasfilename(
|
||||
parent=None, title="Select B&N ePub key file to produce",
|
||||
defaultextension=".b64",
|
||||
filetypes=[('base64-encoded files', '.b64'),
|
||||
('All Files', '.*')])
|
||||
if keypath:
|
||||
keypath = os.path.normpath(keypath)
|
||||
self.keypath.delete(0, Tkconstants.END)
|
||||
self.keypath.delete(0, tkinter.constants.END)
|
||||
self.keypath.insert(0, keypath)
|
||||
return
|
||||
|
||||
@@ -295,35 +292,35 @@ def gui_main():
|
||||
ccn = self.ccn.get()
|
||||
keypath = self.keypath.get()
|
||||
if not name:
|
||||
self.status['text'] = u"Name not specified"
|
||||
self.status['text'] = "Name not specified"
|
||||
return
|
||||
if not ccn:
|
||||
self.status['text'] = u"Credit card number not specified"
|
||||
self.status['text'] = "Credit card number not specified"
|
||||
return
|
||||
if not keypath:
|
||||
self.status['text'] = u"Output keyfile path not specified"
|
||||
self.status['text'] = "Output keyfile path not specified"
|
||||
return
|
||||
self.status['text'] = u"Generating..."
|
||||
self.status['text'] = "Generating..."
|
||||
try:
|
||||
userkey = generate_key(name, ccn)
|
||||
except Exception, e:
|
||||
self.status['text'] = u"Error: (0}".format(e.args[0])
|
||||
except Exception as e:
|
||||
self.status['text'] = "Error: (0}".format(e.args[0])
|
||||
return
|
||||
open(keypath,'wb').write(userkey)
|
||||
self.status['text'] = u"Keyfile successfully generated"
|
||||
self.status['text'] = "Keyfile successfully generated"
|
||||
|
||||
root = Tkinter.Tk()
|
||||
root = tkinter.Tk()
|
||||
if AES is None:
|
||||
root.withdraw()
|
||||
tkMessageBox.showerror(
|
||||
tkinter.messagebox.showerror(
|
||||
"Ignoble EPUB Keyfile Generator",
|
||||
"This script requires OpenSSL or PyCrypto, which must be installed "
|
||||
"separately. Read the top-of-script comment for details.")
|
||||
return 1
|
||||
root.title(u"Barnes & Noble ePub Keyfile Generator v.{0}".format(__version__))
|
||||
root.title("Barnes & Noble ePub Keyfile Generator v.{0}".format(__version__))
|
||||
root.resizable(True, False)
|
||||
root.minsize(300, 0)
|
||||
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
||||
DecryptionDialog(root).pack(fill=tkinter.constants.X, expand=1)
|
||||
root.mainloop()
|
||||
return 0
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#! /usr/bin/python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ignoblepdf.py
|
||||
# Copyright © 2009-2020 by Apprentice Harper et al.
|
||||
@@ -14,6 +13,7 @@ from __future__ import with_statement
|
||||
|
||||
# Revision history:
|
||||
# 0.1 - Initial alpha testing release 2020 by Pu D. Pud
|
||||
# 0.2 - Python 3 for calibre 5.0 (in testing)
|
||||
|
||||
|
||||
"""
|
||||
@@ -21,7 +21,7 @@ Decrypts Barnes & Noble encrypted PDF files.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "0.1"
|
||||
__version__ = "0.2"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -43,10 +43,11 @@ class SafeUnbuffered:
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
if isinstance(data, str):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
self.stream.buffer.write(data)
|
||||
self.stream.buffer.flush()
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
@@ -81,13 +82,11 @@ def unicode_argv():
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
return [u"ignoblepdf.py"]
|
||||
range(start, argc.value)]
|
||||
return ["ignoblepdf.py"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = "utf-8"
|
||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||
argvencoding = sys.stdin.encoding or "utf-8"
|
||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||
|
||||
|
||||
class IGNOBLEError(Exception):
|
||||
@@ -237,10 +236,7 @@ def _load_crypto():
|
||||
ARC4, AES = _load_crypto()
|
||||
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
# Do we generate cross reference streams on output?
|
||||
@@ -428,7 +424,7 @@ class PSBaseParser(object):
|
||||
if not pos:
|
||||
pos = self.bufpos+self.charpos
|
||||
self.fp.seek(pos)
|
||||
##print >>sys.stderr, 'poll(%d): %r' % (pos, self.fp.read(n))
|
||||
# print('poll(%d): %r' % (pos, self.fp.read(n)), file=sys.stderr)
|
||||
self.fp.seek(pos0)
|
||||
return
|
||||
|
||||
@@ -545,7 +541,7 @@ class PSBaseParser(object):
|
||||
except ValueError:
|
||||
pass
|
||||
return (self.parse_main, j)
|
||||
|
||||
|
||||
def parse_decimal(self, s, i):
|
||||
m = END_NUMBER.search(s, i)
|
||||
if not m:
|
||||
@@ -753,7 +749,7 @@ class PSStackParser(PSBaseParser):
|
||||
'''
|
||||
while not self.results:
|
||||
(pos, token) = self.nexttoken()
|
||||
##print (pos,token), (self.curtype, self.curstack)
|
||||
# print((pos, token), (self.curtype, self.curstack))
|
||||
if (isinstance(token, int) or
|
||||
isinstance(token, Decimal) or
|
||||
isinstance(token, bool) or
|
||||
@@ -778,7 +774,7 @@ class PSStackParser(PSBaseParser):
|
||||
try:
|
||||
(pos, objs) = self.end_type('d')
|
||||
if len(objs) % 2 != 0:
|
||||
print "Incomplete dictionary construct"
|
||||
print("Incomplete dictionary construct")
|
||||
objs.append("") # this isn't necessary.
|
||||
# temporary fix. is this due to rental books?
|
||||
# raise PSSyntaxError(
|
||||
@@ -1003,7 +999,7 @@ class PDFStream(PDFObject):
|
||||
if 'Filter' not in self.dic:
|
||||
self.data = data
|
||||
self.rawdata = None
|
||||
##print self.dict
|
||||
##print(self.dict)
|
||||
return
|
||||
filters = self.dic['Filter']
|
||||
if not isinstance(filters, list):
|
||||
@@ -1013,7 +1009,7 @@ class PDFStream(PDFObject):
|
||||
# will get errors if the document is encrypted.
|
||||
data = zlib.decompress(data)
|
||||
elif f in LITERALS_LZW_DECODE:
|
||||
data = ''.join(LZWDecoder(StringIO(data)).run())
|
||||
data = ''.join(LZWDecoder(BytesIO(data)).run())
|
||||
elif f in LITERALS_ASCII85_DECODE:
|
||||
data = ascii85decode(data)
|
||||
elif f == LITERAL_CRYPT:
|
||||
@@ -1037,7 +1033,7 @@ class PDFStream(PDFObject):
|
||||
columns = int_value(params['Columns'])
|
||||
buf = ''
|
||||
ent0 = '\x00' * columns
|
||||
for i in xrange(0, len(data), columns+1):
|
||||
for i in range(0, len(data), columns+1):
|
||||
pred = data[i]
|
||||
ent1 = data[i+1:i+1+columns]
|
||||
if pred == '\x02':
|
||||
@@ -1119,7 +1115,7 @@ class PDFXRef(object):
|
||||
(start, nobjs) = map(int, f)
|
||||
except ValueError:
|
||||
raise PDFNoValidXRef('Invalid line: %r: line=%r' % (parser, line))
|
||||
for objid in xrange(start, start+nobjs):
|
||||
for objid in range(start, start+nobjs):
|
||||
try:
|
||||
(_, line) = parser.nextline()
|
||||
except PSEOF:
|
||||
@@ -1171,7 +1167,7 @@ class PDFXRefStream(object):
|
||||
|
||||
def objids(self):
|
||||
for first, size in self.index:
|
||||
for objid in xrange(first, first + size):
|
||||
for objid in range(first, first + size):
|
||||
yield objid
|
||||
|
||||
def load(self, parser, debug=0):
|
||||
@@ -1380,7 +1376,7 @@ class PDFDocument(object):
|
||||
hash.update('ffffffff'.decode('hex'))
|
||||
if 5 <= R:
|
||||
# 8
|
||||
for _ in xrange(50):
|
||||
for _ in range(50):
|
||||
hash = hashlib.md5(hash.digest()[:length/8])
|
||||
key = hash.digest()[:length/8]
|
||||
if R == 2:
|
||||
@@ -1391,7 +1387,7 @@ class PDFDocument(object):
|
||||
hash = hashlib.md5(self.PASSWORD_PADDING) # 2
|
||||
hash.update(docid[0]) # 3
|
||||
x = ARC4.new(key).decrypt(hash.digest()[:16]) # 4
|
||||
for i in xrange(1,19+1):
|
||||
for i in range(1,19+1):
|
||||
k = ''.join( chr(ord(c) ^ i) for c in key )
|
||||
x = ARC4.new(k).decrypt(x)
|
||||
u1 = x+x # 32bytes total
|
||||
@@ -1447,15 +1443,15 @@ class PDFDocument(object):
|
||||
V = ord(bookkey[0])
|
||||
bookkey = bookkey[1:]
|
||||
else:
|
||||
print "ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type)
|
||||
print "length is %d and len(bookkey) is %d" % (length, len(bookkey))
|
||||
print "bookkey[0] is %d" % ord(bookkey[0])
|
||||
print("ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type))
|
||||
print("length is %d and len(bookkey) is %d" % (length, len(bookkey)))
|
||||
print("bookkey[0] is %d" % ord(bookkey[0]))
|
||||
raise IGNOBLEError('error decrypting book session key - mismatched length')
|
||||
else:
|
||||
# proper length unknown try with whatever you have
|
||||
print "ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type)
|
||||
print "length is %d and len(bookkey) is %d" % (length, len(bookkey))
|
||||
print "bookkey[0] is %d" % ord(bookkey[0])
|
||||
print("ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type))
|
||||
print("length is %d and len(bookkey) is %d" % (length, len(bookkey)))
|
||||
print("bookkey[0] is %d" % ord(bookkey[0]))
|
||||
if ebx_V == 3:
|
||||
V = 3
|
||||
else:
|
||||
@@ -1500,7 +1496,7 @@ class PDFDocument(object):
|
||||
plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data)
|
||||
# remove pkcs#5 aes padding
|
||||
cutter = -1 * ord(plaintext[-1])
|
||||
#print cutter
|
||||
#print(cutter)
|
||||
plaintext = plaintext[:cutter]
|
||||
return plaintext
|
||||
|
||||
@@ -1511,7 +1507,7 @@ class PDFDocument(object):
|
||||
plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data)
|
||||
# remove pkcs#5 aes padding
|
||||
cutter = -1 * ord(plaintext[-1])
|
||||
#print cutter
|
||||
#print(cutter)
|
||||
plaintext = plaintext[:cutter]
|
||||
return plaintext
|
||||
|
||||
@@ -1779,7 +1775,7 @@ class PDFParser(PSStackParser):
|
||||
class PDFObjStrmParser(PDFParser):
|
||||
|
||||
def __init__(self, data, doc):
|
||||
PSStackParser.__init__(self, StringIO(data))
|
||||
PSStackParser.__init__(self, BytesIO(data))
|
||||
self.doc = doc
|
||||
return
|
||||
|
||||
@@ -1854,7 +1850,7 @@ class PDFSerializer(object):
|
||||
if not gen_xref_stm:
|
||||
self.write('xref\n')
|
||||
self.write('0 %d\n' % (maxobj + 1,))
|
||||
for objid in xrange(0, maxobj + 1):
|
||||
for objid in range(0, maxobj + 1):
|
||||
if objid in xrefs:
|
||||
# force the genno to be 0
|
||||
self.write("%010d 00000 n \n" % xrefs[objid][0])
|
||||
@@ -2005,20 +2001,20 @@ class PDFSerializer(object):
|
||||
|
||||
def decryptBook(userkey, inpath, outpath):
|
||||
if AES is None:
|
||||
raise IGNOBLEError(u"PyCrypto or OpenSSL must be installed.")
|
||||
raise IGNOBLEError("PyCrypto or OpenSSL must be installed.")
|
||||
with open(inpath, 'rb') as inf:
|
||||
#try:
|
||||
serializer = PDFSerializer(inf, userkey)
|
||||
#except:
|
||||
# print u"Error serializing pdf {0}. Probably wrong key.".format(os.path.basename(inpath))
|
||||
# print("Error serializing pdf {0}. Probably wrong key.".format(os.path.basename(inpath)))
|
||||
# return 2
|
||||
# hope this will fix the 'bad file descriptor' problem
|
||||
with open(outpath, 'wb') as outf:
|
||||
# help construct to make sure the method runs to the end
|
||||
try:
|
||||
serializer.dump(outf)
|
||||
except Exception, e:
|
||||
print u"error writing pdf: {0}".format(e.args[0])
|
||||
except Exception as e:
|
||||
print("error writing pdf: {0}".format(e.args[0]))
|
||||
return 2
|
||||
return 0
|
||||
|
||||
@@ -2029,91 +2025,91 @@ def cli_main():
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if len(argv) != 4:
|
||||
print u"usage: {0} <keyfile.b64> <inbook.pdf> <outbook.pdf>".format(progname)
|
||||
print("usage: {0} <keyfile.b64> <inbook.pdf> <outbook.pdf>".format(progname))
|
||||
return 1
|
||||
keypath, inpath, outpath = argv[1:]
|
||||
userkey = open(keypath,'rb').read()
|
||||
result = decryptBook(userkey, inpath, outpath)
|
||||
if result == 0:
|
||||
print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath))
|
||||
print("Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)))
|
||||
return result
|
||||
|
||||
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import tkMessageBox
|
||||
import tkinter
|
||||
import tkinter.constants
|
||||
import tkinter.filedialog
|
||||
import tkinter.messagebox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
class DecryptionDialog(tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = Tkinter.Label(self, text=u"Select files for decryption")
|
||||
self.status.pack(fill=Tkconstants.X, expand=1)
|
||||
body = Tkinter.Frame(self)
|
||||
body.pack(fill=Tkconstants.X, expand=1)
|
||||
sticky = Tkconstants.E + Tkconstants.W
|
||||
tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = tkinter.Label(self, text="Select files for decryption")
|
||||
self.status.pack(fill=tkinter.constants.X, expand=1)
|
||||
body = tkinter.Frame(self)
|
||||
body.pack(fill=tkinter.constants.X, expand=1)
|
||||
sticky = tkinter.constants.E + tkinter.constants.W
|
||||
body.grid_columnconfigure(1, weight=2)
|
||||
Tkinter.Label(body, text=u"Key file").grid(row=0)
|
||||
self.keypath = Tkinter.Entry(body, width=30)
|
||||
tkinter.Label(body, text="Key file").grid(row=0)
|
||||
self.keypath = tkinter.Entry(body, width=30)
|
||||
self.keypath.grid(row=0, column=1, sticky=sticky)
|
||||
if os.path.exists(u"bnpdfkey.b64"):
|
||||
self.keypath.insert(0, u"bnpdfkey.b64")
|
||||
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
|
||||
if os.path.exists("bnpdfkey.b64"):
|
||||
self.keypath.insert(0, "bnpdfkey.b64")
|
||||
button = tkinter.Button(body, text="...", command=self.get_keypath)
|
||||
button.grid(row=0, column=2)
|
||||
Tkinter.Label(body, text=u"Input file").grid(row=1)
|
||||
self.inpath = Tkinter.Entry(body, width=30)
|
||||
tkinter.Label(body, text="Input file").grid(row=1)
|
||||
self.inpath = tkinter.Entry(body, width=30)
|
||||
self.inpath.grid(row=1, column=1, sticky=sticky)
|
||||
button = Tkinter.Button(body, text=u"...", command=self.get_inpath)
|
||||
button = tkinter.Button(body, text="...", command=self.get_inpath)
|
||||
button.grid(row=1, column=2)
|
||||
Tkinter.Label(body, text=u"Output file").grid(row=2)
|
||||
self.outpath = Tkinter.Entry(body, width=30)
|
||||
tkinter.Label(body, text="Output file").grid(row=2)
|
||||
self.outpath = tkinter.Entry(body, width=30)
|
||||
self.outpath.grid(row=2, column=1, sticky=sticky)
|
||||
button = Tkinter.Button(body, text=u"...", command=self.get_outpath)
|
||||
button = tkinter.Button(body, text="...", command=self.get_outpath)
|
||||
button.grid(row=2, column=2)
|
||||
buttons = Tkinter.Frame(self)
|
||||
buttons = tkinter.Frame(self)
|
||||
buttons.pack()
|
||||
botton = Tkinter.Button(
|
||||
buttons, text=u"Decrypt", width=10, command=self.decrypt)
|
||||
botton.pack(side=Tkconstants.LEFT)
|
||||
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
||||
button = Tkinter.Button(
|
||||
buttons, text=u"Quit", width=10, command=self.quit)
|
||||
button.pack(side=Tkconstants.RIGHT)
|
||||
botton = tkinter.Button(
|
||||
buttons, text="Decrypt", width=10, command=self.decrypt)
|
||||
botton.pack(side=tkinter.constants.LEFT)
|
||||
tkinter.Frame(buttons, width=10).pack(side=tkinter.constants.LEFT)
|
||||
button = tkinter.Button(
|
||||
buttons, text="Quit", width=10, command=self.quit)
|
||||
button.pack(side=tkinter.constants.RIGHT)
|
||||
|
||||
def get_keypath(self):
|
||||
keypath = tkFileDialog.askopenfilename(
|
||||
parent=None, title=u"Select Barnes & Noble \'.b64\' key file",
|
||||
defaultextension=u".b64",
|
||||
keypath = tkinter.filedialog.askopenfilename(
|
||||
parent=None, title="Select Barnes & Noble \'.b64\' key file",
|
||||
defaultextension=".b64",
|
||||
filetypes=[('base64-encoded files', '.b64'),
|
||||
('All Files', '.*')])
|
||||
if keypath:
|
||||
keypath = os.path.normpath(keypath)
|
||||
self.keypath.delete(0, Tkconstants.END)
|
||||
self.keypath.delete(0, tkinter.constants.END)
|
||||
self.keypath.insert(0, keypath)
|
||||
return
|
||||
|
||||
def get_inpath(self):
|
||||
inpath = tkFileDialog.askopenfilename(
|
||||
parent=None, title=u"Select B&N-encrypted PDF file to decrypt",
|
||||
defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')])
|
||||
inpath = tkinter.filedialog.askopenfilename(
|
||||
parent=None, title="Select B&N-encrypted PDF file to decrypt",
|
||||
defaultextension=".pdf", filetypes=[('PDF files', '.pdf')])
|
||||
if inpath:
|
||||
inpath = os.path.normpath(inpath)
|
||||
self.inpath.delete(0, Tkconstants.END)
|
||||
self.inpath.delete(0, tkinter.constants.END)
|
||||
self.inpath.insert(0, inpath)
|
||||
return
|
||||
|
||||
def get_outpath(self):
|
||||
outpath = tkFileDialog.asksaveasfilename(
|
||||
parent=None, title=u"Select unencrypted PDF file to produce",
|
||||
defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')])
|
||||
outpath = tkinter.filedialog.asksaveasfilename(
|
||||
parent=None, title="Select unencrypted PDF file to produce",
|
||||
defaultextension=".pdf", filetypes=[('PDF files', '.pdf')])
|
||||
if outpath:
|
||||
outpath = os.path.normpath(outpath)
|
||||
self.outpath.delete(0, Tkconstants.END)
|
||||
self.outpath.delete(0, tkinter.constants.END)
|
||||
self.outpath.insert(0, outpath)
|
||||
return
|
||||
|
||||
@@ -2122,42 +2118,42 @@ def gui_main():
|
||||
inpath = self.inpath.get()
|
||||
outpath = self.outpath.get()
|
||||
if not keypath or not os.path.exists(keypath):
|
||||
self.status['text'] = u"Specified key file does not exist"
|
||||
self.status['text'] = "Specified key file does not exist"
|
||||
return
|
||||
if not inpath or not os.path.exists(inpath):
|
||||
self.status['text'] = u"Specified input file does not exist"
|
||||
self.status['text'] = "Specified input file does not exist"
|
||||
return
|
||||
if not outpath:
|
||||
self.status['text'] = u"Output file not specified"
|
||||
self.status['text'] = "Output file not specified"
|
||||
return
|
||||
if inpath == outpath:
|
||||
self.status['text'] = u"Must have different input and output files"
|
||||
self.status['text'] = "Must have different input and output files"
|
||||
return
|
||||
userkey = open(keypath,'rb').read()
|
||||
self.status['text'] = u"Decrypting..."
|
||||
self.status['text'] = "Decrypting..."
|
||||
try:
|
||||
decrypt_status = decryptBook(userkey, inpath, outpath)
|
||||
except Exception, e:
|
||||
self.status['text'] = u"Error; {0}".format(e.args[0])
|
||||
except Exception as e:
|
||||
self.status['text'] = "Error; {0}".format(e.args[0])
|
||||
return
|
||||
if decrypt_status == 0:
|
||||
self.status['text'] = u"File successfully decrypted"
|
||||
self.status['text'] = "File successfully decrypted"
|
||||
else:
|
||||
self.status['text'] = u"The was an error decrypting the file."
|
||||
self.status['text'] = "The was an error decrypting the file."
|
||||
|
||||
|
||||
root = Tkinter.Tk()
|
||||
root = tkinter.Tk()
|
||||
if AES is None:
|
||||
root.withdraw()
|
||||
tkMessageBox.showerror(
|
||||
tkinter.messagebox.showerror(
|
||||
"IGNOBLE PDF",
|
||||
"This script requires OpenSSL or PyCrypto, which must be installed "
|
||||
"separately. Read the top-of-script comment for details.")
|
||||
return 1
|
||||
root.title(u"Barnes & Noble PDF Decrypter v.{0}".format(__version__))
|
||||
root.title("Barnes & Noble PDF Decrypter v.{0}".format(__version__))
|
||||
root.resizable(True, False)
|
||||
root.minsize(370, 0)
|
||||
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
||||
DecryptionDialog(root).pack(fill=tkinter.constants.X, expand=1)
|
||||
root.mainloop()
|
||||
return 0
|
||||
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
||||
# ineptepub.pyw, version 6.6
|
||||
# Copyright © 2009-2020 by Apprentice Harper et al.
|
||||
# ineptepub.py
|
||||
# Copyright © 2009-2020 by i♥cabbages, Apprentice Harper et al.
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Original script by i♥cabbages
|
||||
# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf
|
||||
# Modified 2015–2020 by Apprentice Harper et al.
|
||||
|
||||
# Revision history:
|
||||
# 1 - Initial release
|
||||
@@ -36,17 +29,16 @@ from __future__ import print_function
|
||||
# 6.4 - Remove erroneous check on DER file sanity
|
||||
# 6.5 - Completely remove erroneous check on DER file sanity
|
||||
# 6.6 - Import tkFileDialog, don't assume something else will import it.
|
||||
# 6.7 - Add Python 3 compatibility while still working with Python 2.7
|
||||
# 7.0 - Add Python 3 compatibility for calibre 5.0
|
||||
|
||||
"""
|
||||
Decrypt Adobe Digital Editions encrypted ePub books.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "6.7"
|
||||
__version__ = "7.0"
|
||||
|
||||
import six
|
||||
from six.moves import range
|
||||
import codecs
|
||||
import sys
|
||||
import os
|
||||
import traceback
|
||||
@@ -55,7 +47,6 @@ import zipfile
|
||||
from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED
|
||||
from contextlib import closing
|
||||
import xml.etree.ElementTree as etree
|
||||
import base64
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
@@ -67,10 +58,11 @@ class SafeUnbuffered:
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,six.text_type):
|
||||
if isinstance(data, str):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
self.stream.buffer.write(data)
|
||||
self.stream.buffer.flush()
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
@@ -109,12 +101,10 @@ def unicode_argv():
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
range(start, argc.value)]
|
||||
return [u"ineptepub.py"]
|
||||
return ["ineptepub.py"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = "utf-8"
|
||||
return [arg if (type(arg) == six.text_type) else six.text_type(arg,argvencoding) for arg in sys.argv]
|
||||
argvencoding = sys.stdin.encoding or "utf-8"
|
||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||
|
||||
|
||||
class ADEPTError(Exception):
|
||||
@@ -213,6 +203,7 @@ def _load_crypto_libcrypto():
|
||||
def _load_crypto_pycrypto():
|
||||
from Crypto.Cipher import AES as _AES
|
||||
from Crypto.PublicKey import RSA as _RSA
|
||||
from Crypto.Cipher import PKCS1_v1_5 as _PKCS1_v1_5
|
||||
|
||||
# ASN.1 parsing code from tlslite
|
||||
class ASN1Error(Exception):
|
||||
@@ -304,14 +295,14 @@ def _load_crypto_pycrypto():
|
||||
|
||||
class AES(object):
|
||||
def __init__(self, key):
|
||||
self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16)
|
||||
self._aes = _AES.new(key, _AES.MODE_CBC, b'\x00'*16)
|
||||
|
||||
def decrypt(self, data):
|
||||
return self._aes.decrypt(data)
|
||||
|
||||
class RSA(object):
|
||||
def __init__(self, der):
|
||||
key = ASN1Parser([ord(x) for x in der])
|
||||
key = ASN1Parser([x for x in der])
|
||||
key = [key.getChild(x).value for x in range(1, 4)]
|
||||
key = [self.bytesToNumber(v) for v in key]
|
||||
self._rsa = _RSA.construct(key)
|
||||
@@ -323,7 +314,7 @@ def _load_crypto_pycrypto():
|
||||
return total
|
||||
|
||||
def decrypt(self, data):
|
||||
return self._rsa.decrypt(data)
|
||||
return _PKCS1_v1_5.new(self._rsa).decrypt(data, 172)
|
||||
|
||||
return (AES, RSA)
|
||||
|
||||
@@ -362,12 +353,16 @@ class Decryptor(object):
|
||||
|
||||
def decompress(self, bytes):
|
||||
dc = zlib.decompressobj(-15)
|
||||
bytes = dc.decompress(bytes)
|
||||
ex = dc.decompress(b'Z') + dc.flush()
|
||||
if ex:
|
||||
bytes = bytes + ex
|
||||
return bytes
|
||||
|
||||
try:
|
||||
decompressed_bytes = dc.decompress(bytes)
|
||||
ex = dc.decompress(b'Z') + dc.flush()
|
||||
if ex:
|
||||
decompressed_bytes = decompressed_bytes + ex
|
||||
except:
|
||||
# possibly not compressed by zip - just return bytes
|
||||
return bytes
|
||||
return decompressed_bytes
|
||||
|
||||
def decrypt(self, path, data):
|
||||
if path.encode('utf-8') in self._encrypted:
|
||||
data = self._aes.decrypt(data)[16:]
|
||||
@@ -400,13 +395,13 @@ def adeptBook(inpath):
|
||||
|
||||
def decryptBook(userkey, inpath, outpath):
|
||||
if AES is None:
|
||||
raise ADEPTError(u"PyCrypto or OpenSSL must be installed.")
|
||||
raise ADEPTError("PyCrypto or OpenSSL must be installed.")
|
||||
rsa = RSA(userkey)
|
||||
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
||||
namelist = set(inf.namelist())
|
||||
if 'META-INF/rights.xml' not in namelist or \
|
||||
'META-INF/encryption.xml' not in namelist:
|
||||
print(u"{0:s} is DRM-free.".format(os.path.basename(inpath)))
|
||||
print("{0:s} is DRM-free.".format(os.path.basename(inpath)))
|
||||
return 1
|
||||
for name in META_NAMES:
|
||||
namelist.remove(name)
|
||||
@@ -416,17 +411,18 @@ def decryptBook(userkey, inpath, outpath):
|
||||
expr = './/%s' % (adept('encryptedKey'),)
|
||||
bookkey = ''.join(rights.findtext(expr))
|
||||
if len(bookkey) != 172:
|
||||
print(u"{0:s} is not a secure Adobe Adept ePub.".format(os.path.basename(inpath)))
|
||||
print("{0:s} is not a secure Adobe Adept ePub.".format(os.path.basename(inpath)))
|
||||
return 1
|
||||
bookkey = bookkey.encode('ascii')
|
||||
bookkey = base64.b64decode(bookkey)
|
||||
bookkey = rsa.decrypt(bookkey)
|
||||
bookkey = rsa.decrypt(codecs.decode(bookkey.encode('ascii'), 'base64'))
|
||||
# Padded as per RSAES-PKCS1-v1_5
|
||||
if bookkey[-17] != '\x00' and bookkey[-17] != 0:
|
||||
print(u"Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath)))
|
||||
return 2
|
||||
if len(bookkey) != 16:
|
||||
if bookkey[-17] != '\x00' and bookkey[-17] != 0:
|
||||
print("Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath)))
|
||||
return 2
|
||||
else:
|
||||
bookkey = bookkey[-16:]
|
||||
encryption = inf.read('META-INF/encryption.xml')
|
||||
decryptor = Decryptor(bookkey[-16:], encryption)
|
||||
decryptor = Decryptor(bookkey, encryption)
|
||||
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
|
||||
with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
|
||||
zi = ZipInfo('mimetype')
|
||||
@@ -464,7 +460,7 @@ def decryptBook(userkey, inpath, outpath):
|
||||
pass
|
||||
outf.writestr(zi, decryptor.decrypt(path, data))
|
||||
except:
|
||||
print(u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc()))
|
||||
print("Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc()))
|
||||
return 2
|
||||
return 0
|
||||
|
||||
@@ -475,90 +471,90 @@ def cli_main():
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if len(argv) != 4:
|
||||
print(u"usage: {0} <keyfile.der> <inbook.epub> <outbook.epub>".format(progname))
|
||||
print("usage: {0} <keyfile.der> <inbook.epub> <outbook.epub>".format(progname))
|
||||
return 1
|
||||
keypath, inpath, outpath = argv[1:]
|
||||
userkey = open(keypath,'rb').read()
|
||||
result = decryptBook(userkey, inpath, outpath)
|
||||
if result == 0:
|
||||
print(u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)))
|
||||
print("Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)))
|
||||
return result
|
||||
|
||||
def gui_main():
|
||||
try:
|
||||
import six.moves.tkinter
|
||||
import six.moves.tkinter_constants
|
||||
import six.moves.tkinter_filedialog
|
||||
import six.moves.tkinter_messagebox
|
||||
import tkinter
|
||||
import tkinter.constants
|
||||
import tkinter.filedialog
|
||||
import tkinter.messagebox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(six.moves.tkinter.Frame):
|
||||
class DecryptionDialog(tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
six.moves.tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = six.moves.tkinter.Label(self, text=u"Select files for decryption")
|
||||
self.status.pack(fill=six.moves.tkinter_constants.X, expand=1)
|
||||
body = six.moves.tkinter.Frame(self)
|
||||
body.pack(fill=six.moves.tkinter_constants.X, expand=1)
|
||||
sticky = six.moves.tkinter_constants.E + six.moves.tkinter_constants.W
|
||||
tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = tkinter.Label(self, text="Select files for decryption")
|
||||
self.status.pack(fill=tkinter.constants.X, expand=1)
|
||||
body = tkinter.Frame(self)
|
||||
body.pack(fill=tkinter.constants.X, expand=1)
|
||||
sticky = tkinter.constants.E + tkinter.constants.W
|
||||
body.grid_columnconfigure(1, weight=2)
|
||||
six.moves.tkinter.Label(body, text=u"Key file").grid(row=0)
|
||||
self.keypath = six.moves.tkinter.Entry(body, width=30)
|
||||
tkinter.Label(body, text="Key file").grid(row=0)
|
||||
self.keypath = tkinter.Entry(body, width=30)
|
||||
self.keypath.grid(row=0, column=1, sticky=sticky)
|
||||
if os.path.exists(u"adeptkey.der"):
|
||||
self.keypath.insert(0, u"adeptkey.der")
|
||||
button = six.moves.tkinter.Button(body, text=u"...", command=self.get_keypath)
|
||||
if os.path.exists("adeptkey.der"):
|
||||
self.keypath.insert(0, "adeptkey.der")
|
||||
button = tkinter.Button(body, text="...", command=self.get_keypath)
|
||||
button.grid(row=0, column=2)
|
||||
six.moves.tkinter.Label(body, text=u"Input file").grid(row=1)
|
||||
self.inpath = six.moves.tkinter.Entry(body, width=30)
|
||||
tkinter.Label(body, text="Input file").grid(row=1)
|
||||
self.inpath = tkinter.Entry(body, width=30)
|
||||
self.inpath.grid(row=1, column=1, sticky=sticky)
|
||||
button = six.moves.tkinter.Button(body, text=u"...", command=self.get_inpath)
|
||||
button = tkinter.Button(body, text="...", command=self.get_inpath)
|
||||
button.grid(row=1, column=2)
|
||||
six.moves.tkinter.Label(body, text=u"Output file").grid(row=2)
|
||||
self.outpath = six.moves.tkinter.Entry(body, width=30)
|
||||
tkinter.Label(body, text="Output file").grid(row=2)
|
||||
self.outpath = tkinter.Entry(body, width=30)
|
||||
self.outpath.grid(row=2, column=1, sticky=sticky)
|
||||
button = six.moves.tkinter.Button(body, text=u"...", command=self.get_outpath)
|
||||
button = tkinter.Button(body, text="...", command=self.get_outpath)
|
||||
button.grid(row=2, column=2)
|
||||
buttons = six.moves.tkinter.Frame(self)
|
||||
buttons = tkinter.Frame(self)
|
||||
buttons.pack()
|
||||
botton = six.moves.tkinter.Button(
|
||||
buttons, text=u"Decrypt", width=10, command=self.decrypt)
|
||||
botton.pack(side=six.moves.tkinter_constants.LEFT)
|
||||
six.moves.tkinter.Frame(buttons, width=10).pack(side=six.moves.tkinter_constants.LEFT)
|
||||
button = six.moves.tkinter.Button(
|
||||
buttons, text=u"Quit", width=10, command=self.quit)
|
||||
button.pack(side=six.moves.tkinter_constants.RIGHT)
|
||||
botton = tkinter.Button(
|
||||
buttons, text="Decrypt", width=10, command=self.decrypt)
|
||||
botton.pack(side=tkinter.constants.LEFT)
|
||||
tkinter.Frame(buttons, width=10).pack(side=tkinter.constants.LEFT)
|
||||
button = tkinter.Button(
|
||||
buttons, text="Quit", width=10, command=self.quit)
|
||||
button.pack(side=tkinter.constants.RIGHT)
|
||||
|
||||
def get_keypath(self):
|
||||
keypath = six.moves.tkinter_filedialog.askopenfilename(
|
||||
parent=None, title=u"Select Adobe Adept \'.der\' key file",
|
||||
defaultextension=u".der",
|
||||
keypath = tkinter.filedialog.askopenfilename(
|
||||
parent=None, title="Select Adobe Adept \'.der\' key file",
|
||||
defaultextension=".der",
|
||||
filetypes=[('Adobe Adept DER-encoded files', '.der'),
|
||||
('All Files', '.*')])
|
||||
if keypath:
|
||||
keypath = os.path.normpath(keypath)
|
||||
self.keypath.delete(0, six.moves.tkinter_constants.END)
|
||||
self.keypath.delete(0, tkinter.constants.END)
|
||||
self.keypath.insert(0, keypath)
|
||||
return
|
||||
|
||||
def get_inpath(self):
|
||||
inpath = six.moves.tkinter_filedialog.askopenfilename(
|
||||
parent=None, title=u"Select ADEPT-encrypted ePub file to decrypt",
|
||||
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
|
||||
inpath = tkinter.filedialog.askopenfilename(
|
||||
parent=None, title="Select ADEPT-encrypted ePub file to decrypt",
|
||||
defaultextension=".epub", filetypes=[('ePub files', '.epub')])
|
||||
if inpath:
|
||||
inpath = os.path.normpath(inpath)
|
||||
self.inpath.delete(0, six.moves.tkinter_constants.END)
|
||||
self.inpath.delete(0, tkinter.constants.END)
|
||||
self.inpath.insert(0, inpath)
|
||||
return
|
||||
|
||||
def get_outpath(self):
|
||||
outpath = six.moves.tkinter_filedialog.asksaveasfilename(
|
||||
parent=None, title=u"Select unencrypted ePub file to produce",
|
||||
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
|
||||
outpath = tkinter.filedialog.asksaveasfilename(
|
||||
parent=None, title="Select unencrypted ePub file to produce",
|
||||
defaultextension=".epub", filetypes=[('ePub files', '.epub')])
|
||||
if outpath:
|
||||
outpath = os.path.normpath(outpath)
|
||||
self.outpath.delete(0, six.moves.tkinter_constants.END)
|
||||
self.outpath.delete(0, tkinter.constants.END)
|
||||
self.outpath.insert(0, outpath)
|
||||
return
|
||||
|
||||
@@ -567,34 +563,34 @@ def gui_main():
|
||||
inpath = self.inpath.get()
|
||||
outpath = self.outpath.get()
|
||||
if not keypath or not os.path.exists(keypath):
|
||||
self.status['text'] = u"Specified key file does not exist"
|
||||
self.status['text'] = "Specified key file does not exist"
|
||||
return
|
||||
if not inpath or not os.path.exists(inpath):
|
||||
self.status['text'] = u"Specified input file does not exist"
|
||||
self.status['text'] = "Specified input file does not exist"
|
||||
return
|
||||
if not outpath:
|
||||
self.status['text'] = u"Output file not specified"
|
||||
self.status['text'] = "Output file not specified"
|
||||
return
|
||||
if inpath == outpath:
|
||||
self.status['text'] = u"Must have different input and output files"
|
||||
self.status['text'] = "Must have different input and output files"
|
||||
return
|
||||
userkey = open(keypath,'rb').read()
|
||||
self.status['text'] = u"Decrypting..."
|
||||
self.status['text'] = "Decrypting..."
|
||||
try:
|
||||
decrypt_status = decryptBook(userkey, inpath, outpath)
|
||||
except Exception as e:
|
||||
self.status['text'] = u"Error: {0}".format(e.args[0])
|
||||
self.status['text'] = "Error: {0}".format(e.args[0])
|
||||
return
|
||||
if decrypt_status == 0:
|
||||
self.status['text'] = u"File successfully decrypted"
|
||||
self.status['text'] = "File successfully decrypted"
|
||||
else:
|
||||
self.status['text'] = u"The was an error decrypting the file."
|
||||
self.status['text'] = "There was an error decrypting the file."
|
||||
|
||||
root = six.moves.tkinter.Tk()
|
||||
root.title(u"Adobe Adept ePub Decrypter v.{0}".format(__version__))
|
||||
root = tkinter.Tk()
|
||||
root.title("Adobe Adept ePub Decrypter v.{0}".format(__version__))
|
||||
root.resizable(True, False)
|
||||
root.minsize(300, 0)
|
||||
DecryptionDialog(root).pack(fill=six.moves.tkinter_constants.X, expand=1)
|
||||
DecryptionDialog(root).pack(fill=tkinter.constants.X, expand=1)
|
||||
root.mainloop()
|
||||
return 0
|
||||
|
||||
|
||||
584
DeDRM_plugin/ineptpdf.py
Normal file → Executable file
584
DeDRM_plugin/ineptpdf.py
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,11 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ion.py
|
||||
# Copyright © 2013-2020 Apprentice Harper et al.
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '2.0'
|
||||
__version__ = '3.0'
|
||||
|
||||
# Revision history:
|
||||
# Pascal implementation by lulzkabulz.
|
||||
@@ -17,7 +15,7 @@ __version__ = '2.0'
|
||||
# 1.2 - Added pylzma import fallback
|
||||
# 1.3 - Fixed lzma support for calibre 4.6+
|
||||
# 2.0 - VoucherEnvelope v2/v3 support by apprenticesakuya.
|
||||
|
||||
# 3.0 - Added Python 3 compatibility for calibre 5.0
|
||||
|
||||
"""
|
||||
Decrypt Kindle KFX files.
|
||||
@@ -30,13 +28,10 @@ import os
|
||||
import os.path
|
||||
import struct
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
from io import BytesIO
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.py3compat import bchr, bord
|
||||
from Crypto.Util.py3compat import bchr
|
||||
|
||||
try:
|
||||
# lzma library from calibre 4.6.0 or later
|
||||
@@ -94,7 +89,7 @@ LEN_IS_VAR_LEN = 0xE
|
||||
LEN_IS_NULL = 0xF
|
||||
|
||||
|
||||
VERSION_MARKER = b"\x01\x00\xEA"
|
||||
VERSION_MARKER = [b"\x01", b"\x00", b"\xEA"]
|
||||
|
||||
|
||||
# asserts must always raise exceptions for proper functioning
|
||||
@@ -352,7 +347,7 @@ class BinaryIonParser(object):
|
||||
b = self.stream.read(1)
|
||||
if len(b) < 1:
|
||||
return -1
|
||||
b = bord(b)
|
||||
b = ord(b)
|
||||
result = b >> 4
|
||||
ln = b & 0xF
|
||||
|
||||
@@ -377,13 +372,13 @@ class BinaryIonParser(object):
|
||||
return result
|
||||
|
||||
def readvarint(self):
|
||||
b = bord(self.read())
|
||||
b = ord(self.read())
|
||||
negative = ((b & 0x40) != 0)
|
||||
result = (b & 0x3F)
|
||||
|
||||
i = 0
|
||||
while (b & 0x80) == 0 and i < 4:
|
||||
b = bord(self.read())
|
||||
b = ord(self.read())
|
||||
result = (result << 7) | (b & 0x7F)
|
||||
i += 1
|
||||
|
||||
@@ -394,12 +389,12 @@ class BinaryIonParser(object):
|
||||
return result
|
||||
|
||||
def readvaruint(self):
|
||||
b = bord(self.read())
|
||||
b = ord(self.read())
|
||||
result = (b & 0x7F)
|
||||
|
||||
i = 0
|
||||
while (b & 0x80) == 0 and i < 4:
|
||||
b = bord(self.read())
|
||||
b = ord(self.read())
|
||||
result = (result << 7) | (b & 0x7F)
|
||||
i += 1
|
||||
|
||||
@@ -419,7 +414,7 @@ class BinaryIonParser(object):
|
||||
_assert(self.localremaining <= 8, "Decimal overflow")
|
||||
|
||||
signed = False
|
||||
b = [bord(x) for x in self.read(self.localremaining)]
|
||||
b = [ord(x) for x in self.read(self.localremaining)]
|
||||
if (b[0] & 0x80) != 0:
|
||||
b[0] = b[0] & 0x7F
|
||||
signed = True
|
||||
@@ -584,7 +579,7 @@ class BinaryIonParser(object):
|
||||
_assert(self.valuelen <= 4, "int too long: %d" % self.valuelen)
|
||||
v = 0
|
||||
for i in range(self.valuelen - 1, -1, -1):
|
||||
v = (v | (bord(self.read()) << (i * 8)))
|
||||
v = (v | (ord(self.read()) << (i * 8)))
|
||||
|
||||
if self.valuetid == TID_NEGINT:
|
||||
self.value = -v
|
||||
@@ -654,7 +649,7 @@ class BinaryIonParser(object):
|
||||
|
||||
result = ""
|
||||
for i in b:
|
||||
result += ("%02x " % bord(i))
|
||||
result += ("%02x " % ord(i))
|
||||
|
||||
if len(result) > 0:
|
||||
result = result[:-1]
|
||||
@@ -753,7 +748,8 @@ def pkcs7pad(msg, blocklen):
|
||||
def pkcs7unpad(msg, blocklen):
|
||||
_assert(len(msg) % blocklen == 0)
|
||||
|
||||
paddinglen = bord(msg[-1])
|
||||
paddinglen = msg[-1]
|
||||
|
||||
_assert(paddinglen > 0 and paddinglen <= blocklen, "Incorrect padding - Wrong key")
|
||||
_assert(msg[-paddinglen:] == bchr(paddinglen) * paddinglen, "Incorrect padding - Wrong key")
|
||||
|
||||
@@ -811,7 +807,7 @@ class DrmIonVoucher(object):
|
||||
secretkey = b""
|
||||
|
||||
def __init__(self, voucherenv, dsn, secret):
|
||||
self.dsn,self.secret = dsn,secret
|
||||
self.dsn, self.secret = dsn, secret
|
||||
|
||||
self.lockparams = []
|
||||
|
||||
@@ -832,12 +828,12 @@ class DrmIonVoucher(object):
|
||||
|
||||
sharedsecret = obfuscate(shared.encode('ASCII'), self.version)
|
||||
|
||||
key = hmac.new(sharedsecret, "PIDv3", digestmod=hashlib.sha256).digest()
|
||||
key = hmac.new(sharedsecret, b"PIDv3", digestmod=hashlib.sha256).digest()
|
||||
aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
|
||||
b = aes.decrypt(self.ciphertext)
|
||||
b = pkcs7unpad(b, 16)
|
||||
|
||||
self.drmkey = BinaryIonParser(StringIO(b))
|
||||
self.drmkey = BinaryIonParser(BytesIO(b))
|
||||
addprottable(self.drmkey)
|
||||
|
||||
_assert(self.drmkey.hasnext() and self.drmkey.next() == TID_LIST and self.drmkey.gettypename() == "com.amazon.drm.KeySet@1.0",
|
||||
@@ -876,7 +872,7 @@ class DrmIonVoucher(object):
|
||||
self.envelope.next()
|
||||
field = self.envelope.getfieldname()
|
||||
if field == "voucher":
|
||||
self.voucher = BinaryIonParser(StringIO(self.envelope.lobvalue()))
|
||||
self.voucher = BinaryIonParser(BytesIO(self.envelope.lobvalue()))
|
||||
addprottable(self.voucher)
|
||||
continue
|
||||
elif field != "strategy":
|
||||
@@ -1029,7 +1025,7 @@ class DrmIon(object):
|
||||
outpages.write(msg)
|
||||
return
|
||||
|
||||
_assert(msg[0] == b"\x00", "LZMA UseFilter not supported")
|
||||
_assert(msg[0] == 0, "LZMA UseFilter not supported")
|
||||
|
||||
if calibre_lzma is not None:
|
||||
with calibre_lzma.decompress(msg[1:], bufsize=0x1000000) as f:
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# k4mobidedrm.py
|
||||
# Copyright © 2008-2019 by Apprentice Harper et al.
|
||||
# Copyright © 2008-2020 by Apprentice Harper et al.
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '5.7'
|
||||
__version__ = '6.0'
|
||||
|
||||
# Engine to remove drm from Kindle and Mobipocket ebooks
|
||||
# for personal use for archiving and converting your ebooks
|
||||
@@ -62,6 +60,8 @@ __version__ = '5.7'
|
||||
# 5.5 - Added GPL v3 licence explicitly.
|
||||
# 5.6 - Invoke KFXZipBook to handle zipped KFX files
|
||||
# 5.7 - Revamp cleanup_name
|
||||
# 6.0 - Added Python 3 compatibility for calibre 5.0
|
||||
|
||||
|
||||
import sys, os, re
|
||||
import csv
|
||||
@@ -69,7 +69,7 @@ import getopt
|
||||
import re
|
||||
import traceback
|
||||
import time
|
||||
import htmlentitydefs
|
||||
import html.entities
|
||||
import json
|
||||
|
||||
class DrmException(Exception):
|
||||
@@ -103,10 +103,11 @@ class SafeUnbuffered:
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
if isinstance(data, str):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
self.stream.buffer.write(data)
|
||||
self.stream.buffer.flush()
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
@@ -141,15 +142,13 @@ def unicode_argv():
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
range(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"mobidedrm.py"]
|
||||
return ["mobidedrm.py"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = "utf-8"
|
||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||
argvencoding = sys.stdin.encoding or "utf-8"
|
||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||
|
||||
# cleanup unicode filenames
|
||||
# borrowed from calibre from calibre/src/calibre/__init__.py
|
||||
@@ -159,60 +158,60 @@ def unicode_argv():
|
||||
# and some improvements suggested by jhaisley
|
||||
def cleanup_name(name):
|
||||
# substitute filename unfriendly characters
|
||||
name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" – ").replace(u": ",u" – ").replace(u":",u"—").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'").replace(u"*",u"_").replace(u"?",u"")
|
||||
name = name.replace("<","[").replace(">","]").replace(" : "," – ").replace(": "," – ").replace(":","—").replace("/","_").replace("\\","_").replace("|","_").replace("\"","\'").replace("*","_").replace("?","")
|
||||
# white space to single space, delete leading and trailing while space
|
||||
name = re.sub(ur"\s", u" ", name).strip()
|
||||
name = re.sub(r"\s", " ", name).strip()
|
||||
# delete control characters
|
||||
name = u"".join(char for char in name if ord(char)>=32)
|
||||
name = "".join(char for char in name if ord(char)>=32)
|
||||
# delete non-ascii characters
|
||||
name = u"".join(char for char in name if ord(char)<=126)
|
||||
name = "".join(char for char in name if ord(char)<=126)
|
||||
# remove leading dots
|
||||
while len(name)>0 and name[0] == u".":
|
||||
while len(name)>0 and name[0] == ".":
|
||||
name = name[1:]
|
||||
# remove trailing dots (Windows doesn't like them)
|
||||
while name.endswith(u'.'):
|
||||
while name.endswith("."):
|
||||
name = name[:-1]
|
||||
if len(name)==0:
|
||||
name=u"DecryptedBook"
|
||||
name="DecryptedBook"
|
||||
return name
|
||||
|
||||
# must be passed unicode
|
||||
def unescape(text):
|
||||
def fixup(m):
|
||||
text = m.group(0)
|
||||
if text[:2] == u"&#":
|
||||
if text[:2] == "&#":
|
||||
# character reference
|
||||
try:
|
||||
if text[:3] == u"&#x":
|
||||
return unichr(int(text[3:-1], 16))
|
||||
if text[:3] == "&#x":
|
||||
return chr(int(text[3:-1], 16))
|
||||
else:
|
||||
return unichr(int(text[2:-1]))
|
||||
return chr(int(text[2:-1]))
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
# named entity
|
||||
try:
|
||||
text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
|
||||
text = chr(htmlentitydefs.name2codepoint[text[1:-1]])
|
||||
except KeyError:
|
||||
pass
|
||||
return text # leave as is
|
||||
return re.sub(u"&#?\w+;", fixup, text)
|
||||
return re.sub("&#?\\w+;", fixup, text)
|
||||
|
||||
def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime = time.time()):
|
||||
# handle the obvious cases at the beginning
|
||||
if not os.path.isfile(infile):
|
||||
raise DrmException(u"Input file does not exist.")
|
||||
raise DrmException("Input file does not exist.")
|
||||
|
||||
mobi = True
|
||||
magic8 = open(infile,'rb').read(8)
|
||||
if magic8 == '\xeaDRMION\xee':
|
||||
raise DrmException(u"The .kfx DRMION file cannot be decrypted by itself. A .kfx-zip archive containing a DRM voucher is required.")
|
||||
if magic8 == b'\xeaDRMION\xee':
|
||||
raise DrmException("The .kfx DRMION file cannot be decrypted by itself. A .kfx-zip archive containing a DRM voucher is required.")
|
||||
|
||||
magic3 = magic8[:3]
|
||||
if magic3 == 'TPZ':
|
||||
if magic3 == b'TPZ':
|
||||
mobi = False
|
||||
|
||||
if magic8[:4] == 'PK\x03\x04':
|
||||
if magic8[:4] == b'PK\x03\x04':
|
||||
mb = kfxdedrm.KFXZipBook(infile)
|
||||
elif mobi:
|
||||
mb = mobidedrm.MobiBook(infile)
|
||||
@@ -220,7 +219,7 @@ def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime
|
||||
mb = topazextract.TopazBook(infile)
|
||||
|
||||
bookname = unescape(mb.getBookTitle())
|
||||
print u"Decrypting {1} ebook: {0}".format(bookname, mb.getBookType())
|
||||
print("Decrypting {1} ebook: {0}".format(bookname, mb.getBookType()))
|
||||
|
||||
# copy list of pids
|
||||
totalpids = list(pids)
|
||||
@@ -232,7 +231,7 @@ def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime
|
||||
totalpids.extend(kgenpids.getPidList(md1, md2, serials, kDatabases))
|
||||
# remove any duplicates
|
||||
totalpids = list(set(totalpids))
|
||||
print u"Found {1:d} keys to try after {0:.1f} seconds".format(time.time()-starttime, len(totalpids))
|
||||
print("Found {1:d} keys to try after {0:.1f} seconds".format(time.time()-starttime, len(totalpids)))
|
||||
#print totalpids
|
||||
|
||||
try:
|
||||
@@ -241,7 +240,7 @@ def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime
|
||||
mb.cleanup
|
||||
raise
|
||||
|
||||
print u"Decryption succeeded after {0:.1f} seconds".format(time.time()-starttime)
|
||||
print("Decryption succeeded after {0:.1f} seconds".format(time.time()-starttime))
|
||||
return mb
|
||||
|
||||
|
||||
@@ -255,16 +254,16 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
|
||||
with open(dbfile, 'r') as keyfilein:
|
||||
kindleDatabase = json.loads(keyfilein.read())
|
||||
kDatabases.append([dbfile,kindleDatabase])
|
||||
except Exception, e:
|
||||
print u"Error getting database from file {0:s}: {1:s}".format(dbfile,e)
|
||||
except Exception as e:
|
||||
print("Error getting database from file {0:s}: {1:s}".format(dbfile,e))
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
|
||||
try:
|
||||
book = GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime)
|
||||
except Exception, e:
|
||||
print u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime)
|
||||
except Exception as e:
|
||||
print("Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime))
|
||||
traceback.print_exc()
|
||||
return 1
|
||||
|
||||
@@ -275,7 +274,7 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
|
||||
re.match('^{0-9A-F-}{36}$', orig_fn_root)
|
||||
): # Kindle for PC / Mac / Android / Fire / iOS
|
||||
clean_title = cleanup_name(book.getBookTitle())
|
||||
outfilename = u'{}_{}'.format(orig_fn_root, clean_title)
|
||||
outfilename = "{}_{}".format(orig_fn_root, clean_title)
|
||||
else: # E Ink Kindle, which already uses a reasonable name
|
||||
outfilename = orig_fn_root
|
||||
|
||||
@@ -283,16 +282,16 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
|
||||
if len(outfilename)>150:
|
||||
outfilename = outfilename[:99]+"--"+outfilename[-49:]
|
||||
|
||||
outfilename = outfilename+u"_nodrm"
|
||||
outfilename = outfilename+"_nodrm"
|
||||
outfile = os.path.join(outdir, outfilename + book.getBookExtension())
|
||||
|
||||
book.getFile(outfile)
|
||||
print u"Saved decrypted book {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename)
|
||||
print("Saved decrypted book {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename))
|
||||
|
||||
if book.getBookType()==u"Topaz":
|
||||
zipname = os.path.join(outdir, outfilename + u"_SVG.zip")
|
||||
if book.getBookType()=="Topaz":
|
||||
zipname = os.path.join(outdir, outfilename + "_SVG.zip")
|
||||
book.getSVGZip(zipname)
|
||||
print u"Saved SVG ZIP Archive for {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename)
|
||||
print("Saved SVG ZIP Archive for {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename))
|
||||
|
||||
# remove internal temporary directory of Topaz pieces
|
||||
book.cleanup()
|
||||
@@ -300,9 +299,9 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
|
||||
|
||||
|
||||
def usage(progname):
|
||||
print u"Removes DRM protection from Mobipocket, Amazon KF8, Amazon Print Replica and Amazon Topaz ebooks"
|
||||
print u"Usage:"
|
||||
print u" {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] [ -a <AmazonSecureStorage.xml|backup.ab> ] <infile> <outdir>".format(progname)
|
||||
print("Removes DRM protection from Mobipocket, Amazon KF8, Amazon Print Replica and Amazon Topaz ebooks")
|
||||
print("Usage:")
|
||||
print(" {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] [ -a <AmazonSecureStorage.xml|backup.ab> ] <infile> <outdir>".format(progname))
|
||||
|
||||
#
|
||||
# Main
|
||||
@@ -310,12 +309,12 @@ def usage(progname):
|
||||
def cli_main():
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print u"K4MobiDeDrm v{0}.\nCopyright © 2008-2017 Apprentice Harper et al.".format(__version__)
|
||||
print("K4MobiDeDrm v{0}.\nCopyright © 2008-2020 Apprentice Harper et al.".format(__version__))
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], "k:p:s:a:")
|
||||
except getopt.GetoptError, err:
|
||||
print u"Error in options or arguments: {0}".format(err.args[0])
|
||||
opts, args = getopt.getopt(argv[1:], "k:p:s:a:h")
|
||||
except getopt.GetoptError as err:
|
||||
print("Error in options or arguments: {0}".format(err.args[0]))
|
||||
usage(progname)
|
||||
sys.exit(2)
|
||||
if len(args)<2:
|
||||
@@ -330,6 +329,9 @@ def cli_main():
|
||||
pids = []
|
||||
|
||||
for o, a in opts:
|
||||
if o == "-h":
|
||||
usage(progname)
|
||||
sys.exit(0)
|
||||
if o == "-k":
|
||||
if a == None :
|
||||
raise DrmException("Invalid parameter for -k")
|
||||
@@ -337,7 +339,7 @@ def cli_main():
|
||||
if o == "-p":
|
||||
if a == None :
|
||||
raise DrmException("Invalid parameter for -p")
|
||||
pids = a.split(',')
|
||||
pids = a.encode('utf-8').split(b',')
|
||||
if o == "-s":
|
||||
if a == None :
|
||||
raise DrmException("Invalid parameter for -s")
|
||||
@@ -347,9 +349,6 @@ def cli_main():
|
||||
raise DrmException("Invalid parameter for -a")
|
||||
androidFiles.append(a)
|
||||
|
||||
# try with built in Kindle Info files if not on Linux
|
||||
k4 = not sys.platform.startswith('linux')
|
||||
|
||||
return decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids)
|
||||
|
||||
|
||||
|
||||
@@ -1,28 +1,24 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
from __future__ import print_function
|
||||
|
||||
# Engine to remove drm from Kindle KFX ebooks
|
||||
|
||||
# 2.0 - Python 3 for calibre 5.0
|
||||
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import zipfile
|
||||
|
||||
from io import BytesIO
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
try:
|
||||
from calibre_plugins.dedrm import ion
|
||||
except ImportError:
|
||||
import ion
|
||||
from ion import DrmIon, DrmIonVoucher
|
||||
except:
|
||||
from calibre_plugins.dedrm.ion import DrmIon, DrmIonVoucher
|
||||
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '1.0'
|
||||
__version__ = '2.0'
|
||||
|
||||
|
||||
class KFXZipBook:
|
||||
@@ -39,34 +35,34 @@ class KFXZipBook:
|
||||
for filename in zf.namelist():
|
||||
with zf.open(filename) as fh:
|
||||
data = fh.read(8)
|
||||
if data != '\xeaDRMION\xee':
|
||||
if data != b'\xeaDRMION\xee':
|
||||
continue
|
||||
data += fh.read()
|
||||
if self.voucher is None:
|
||||
self.decrypt_voucher(totalpids)
|
||||
print(u'Decrypting KFX DRMION: {0}'.format(filename))
|
||||
outfile = StringIO()
|
||||
ion.DrmIon(StringIO(data[8:-8]), lambda name: self.voucher).parse(outfile)
|
||||
print("Decrypting KFX DRMION: {0}".format(filename))
|
||||
outfile = BytesIO()
|
||||
DrmIon(BytesIO(data[8:-8]), lambda name: self.voucher).parse(outfile)
|
||||
self.decrypted[filename] = outfile.getvalue()
|
||||
|
||||
if not self.decrypted:
|
||||
print(u'The .kfx-zip archive does not contain an encrypted DRMION file')
|
||||
print("The .kfx-zip archive does not contain an encrypted DRMION file")
|
||||
|
||||
def decrypt_voucher(self, totalpids):
|
||||
with zipfile.ZipFile(self.infile, 'r') as zf:
|
||||
for info in zf.infolist():
|
||||
with zf.open(info.filename) as fh:
|
||||
data = fh.read(4)
|
||||
if data != '\xe0\x01\x00\xea':
|
||||
if data != b'\xe0\x01\x00\xea':
|
||||
continue
|
||||
|
||||
data += fh.read()
|
||||
if 'ProtectedData' in data:
|
||||
if b'ProtectedData' in data:
|
||||
break # found DRM voucher
|
||||
else:
|
||||
raise Exception(u'The .kfx-zip archive contains an encrypted DRMION file without a DRM voucher')
|
||||
raise Exception("The .kfx-zip archive contains an encrypted DRMION file without a DRM voucher")
|
||||
|
||||
print(u'Decrypting KFX DRM voucher: {0}'.format(info.filename))
|
||||
print("Decrypting KFX DRM voucher: {0}".format(info.filename))
|
||||
|
||||
for pid in [''] + totalpids:
|
||||
for dsn_len,secret_len in [(0,0), (16,0), (16,40), (32,40), (40,0), (40,40)]:
|
||||
@@ -76,20 +72,20 @@ class KFXZipBook:
|
||||
continue
|
||||
|
||||
try:
|
||||
voucher = ion.DrmIonVoucher(StringIO(data), pid[:dsn_len], pid[dsn_len:])
|
||||
voucher = DrmIonVoucher(BytesIO(data), pid[:dsn_len], pid[dsn_len:])
|
||||
voucher.parse()
|
||||
voucher.decryptvoucher()
|
||||
break
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
raise Exception(u'Failed to decrypt KFX DRM voucher with any key')
|
||||
raise Exception("Failed to decrypt KFX DRM voucher with any key")
|
||||
|
||||
print(u'KFX DRM voucher successfully decrypted')
|
||||
print("KFX DRM voucher successfully decrypted")
|
||||
|
||||
license_type = voucher.getlicensetype()
|
||||
if license_type != "Purchase":
|
||||
raise Exception((u'This book is licensed as {0}. '
|
||||
raise Exception(("This book is licensed as {0}. "
|
||||
'These tools are intended for use on purchased books.').format(license_type))
|
||||
|
||||
self.voucher = voucher
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
from __future__ import print_function
|
||||
|
||||
# kgenpids.py
|
||||
# Copyright © 2008-2017 Apprentice Harper et al.
|
||||
# Copyright © 2008-2020 Apprentice Harper et al.
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '2.1'
|
||||
__version__ = '3.0'
|
||||
|
||||
# Revision history:
|
||||
# 2.0 - Fix for non-ascii Windows user names
|
||||
# 2.1 - Actual fix for non-ascii WIndows user names.
|
||||
# x.x - Return information needed for KFX decryption
|
||||
# 2.2 - Return information needed for KFX decryption
|
||||
# 3.0 - Python 3 for calibre 5.0
|
||||
|
||||
|
||||
import sys
|
||||
import os, csv
|
||||
@@ -31,9 +30,9 @@ global charMap3
|
||||
global charMap4
|
||||
|
||||
|
||||
charMap1 = 'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
|
||||
charMap3 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
charMap4 = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
|
||||
charMap1 = b'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
|
||||
charMap3 = b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
charMap4 = b'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
|
||||
|
||||
# crypto digestroutines
|
||||
import hashlib
|
||||
@@ -50,14 +49,15 @@ def SHA1(message):
|
||||
|
||||
|
||||
# Encode the bytes in data with the characters in map
|
||||
# data and map should be byte arrays
|
||||
def encode(data, map):
|
||||
result = ''
|
||||
result = b''
|
||||
for char in data:
|
||||
value = ord(char)
|
||||
value = char
|
||||
Q = (value ^ 0x80) // len(map)
|
||||
R = value % len(map)
|
||||
result += map[Q]
|
||||
result += map[R]
|
||||
result += bytes([map[Q]])
|
||||
result += bytes([map[R]])
|
||||
return result
|
||||
|
||||
# Hash the bytes in data and then encode the digest with the characters in map
|
||||
@@ -84,7 +84,7 @@ def decode(data,map):
|
||||
def getTwoBitsFromBitField(bitField,offset):
|
||||
byteNumber = offset // 4
|
||||
bitPosition = 6 - 2*(offset % 4)
|
||||
return ord(bitField[byteNumber]) >> bitPosition & 3
|
||||
return bitField[byteNumber] >> bitPosition & 3
|
||||
|
||||
# Returns the six bits at offset from a bit field
|
||||
def getSixBitsFromBitField(bitField,offset):
|
||||
@@ -95,9 +95,9 @@ def getSixBitsFromBitField(bitField,offset):
|
||||
# 8 bits to six bits encoding from hash to generate PID string
|
||||
def encodePID(hash):
|
||||
global charMap3
|
||||
PID = ''
|
||||
PID = b''
|
||||
for position in range (0,8):
|
||||
PID += charMap3[getSixBitsFromBitField(hash,position)]
|
||||
PID += bytes([charMap3[getSixBitsFromBitField(hash,position)]])
|
||||
return PID
|
||||
|
||||
# Encryption table used to generate the device PID
|
||||
@@ -118,7 +118,7 @@ def generatePidEncryptionTable() :
|
||||
def generatePidSeed(table,dsn) :
|
||||
value = 0
|
||||
for counter in range (0,4) :
|
||||
index = (ord(dsn[counter]) ^ value) &0xFF
|
||||
index = (dsn[counter] ^ value) & 0xFF
|
||||
value = (value >> 8) ^ table[index]
|
||||
return value
|
||||
|
||||
@@ -126,15 +126,15 @@ def generatePidSeed(table,dsn) :
|
||||
def generateDevicePID(table,dsn,nbRoll):
|
||||
global charMap4
|
||||
seed = generatePidSeed(table,dsn)
|
||||
pidAscii = ''
|
||||
pidAscii = b''
|
||||
pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF]
|
||||
index = 0
|
||||
for counter in range (0,nbRoll):
|
||||
pid[index] = pid[index] ^ ord(dsn[counter])
|
||||
pid[index] = pid[index] ^ dsn[counter]
|
||||
index = (index+1) %8
|
||||
for counter in range (0,8):
|
||||
index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
|
||||
pidAscii += charMap4[index]
|
||||
pidAscii += bytes([charMap4[index]])
|
||||
return pidAscii
|
||||
|
||||
def crc32(s):
|
||||
@@ -150,7 +150,7 @@ def checksumPid(s):
|
||||
for i in (0,1):
|
||||
b = crc & 0xff
|
||||
pos = (b // l) ^ (b % l)
|
||||
res += charMap4[pos%l]
|
||||
res += bytes([charMap4[pos%l]])
|
||||
crc >>= 8
|
||||
return res
|
||||
|
||||
@@ -160,15 +160,15 @@ def pidFromSerial(s, l):
|
||||
global charMap4
|
||||
crc = crc32(s)
|
||||
arr1 = [0]*l
|
||||
for i in xrange(len(s)):
|
||||
arr1[i%l] ^= ord(s[i])
|
||||
for i in range(len(s)):
|
||||
arr1[i%l] ^= s[i]
|
||||
crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
|
||||
for i in xrange(l):
|
||||
for i in range(l):
|
||||
arr1[i] ^= crc_bytes[i&3]
|
||||
pid = ""
|
||||
for i in xrange(l):
|
||||
pid = b""
|
||||
for i in range(l):
|
||||
b = arr1[i] & 0xff
|
||||
pid+=charMap4[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]
|
||||
pid += bytes([charMap4[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]])
|
||||
return pid
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ def getKindlePids(rec209, token, serialnum):
|
||||
|
||||
pids=[]
|
||||
|
||||
if isinstance(serialnum,unicode):
|
||||
if isinstance(serialnum,str):
|
||||
serialnum = serialnum.encode('utf-8')
|
||||
|
||||
# Compute book PID
|
||||
@@ -189,7 +189,7 @@ def getKindlePids(rec209, token, serialnum):
|
||||
pids.append(bookPID)
|
||||
|
||||
# compute fixed pid for old pre 2.5 firmware update pid as well
|
||||
kindlePID = pidFromSerial(serialnum, 7) + "*"
|
||||
kindlePID = pidFromSerial(serialnum, 7) + b"*"
|
||||
kindlePID = checksumPid(kindlePID)
|
||||
pids.append(kindlePID)
|
||||
|
||||
@@ -206,7 +206,7 @@ def getK4Pids(rec209, token, kindleDatabase):
|
||||
|
||||
try:
|
||||
# Get the kindle account token, if present
|
||||
kindleAccountToken = (kindleDatabase[1])['kindle.account.tokens'].decode('hex')
|
||||
kindleAccountToken = bytearray.fromhex((kindleDatabase[1])['kindle.account.tokens'])
|
||||
|
||||
except KeyError:
|
||||
kindleAccountToken=""
|
||||
@@ -214,44 +214,44 @@ def getK4Pids(rec209, token, kindleDatabase):
|
||||
|
||||
try:
|
||||
# Get the DSN token, if present
|
||||
DSN = (kindleDatabase[1])['DSN'].decode('hex')
|
||||
print(u"Got DSN key from database {0}".format(kindleDatabase[0]))
|
||||
DSN = bytearray.fromhex((kindleDatabase[1])['DSN'])
|
||||
print("Got DSN key from database {0}".format(kindleDatabase[0]))
|
||||
except KeyError:
|
||||
# See if we have the info to generate the DSN
|
||||
try:
|
||||
# Get the Mazama Random number
|
||||
MazamaRandomNumber = (kindleDatabase[1])['MazamaRandomNumber'].decode('hex')
|
||||
#print u"Got MazamaRandomNumber from database {0}".format(kindleDatabase[0])
|
||||
MazamaRandomNumber = bytearray.fromhex((kindleDatabase[1])['MazamaRandomNumber'])
|
||||
#print "Got MazamaRandomNumber from database {0}".format(kindleDatabase[0])
|
||||
|
||||
try:
|
||||
# Get the SerialNumber token, if present
|
||||
IDString = (kindleDatabase[1])['SerialNumber'].decode('hex')
|
||||
print(u"Got SerialNumber from database {0}".format(kindleDatabase[0]))
|
||||
IDString = bytearray.fromhex((kindleDatabase[1])['SerialNumber'])
|
||||
print("Got SerialNumber from database {0}".format(kindleDatabase[0]))
|
||||
except KeyError:
|
||||
# Get the IDString we added
|
||||
IDString = (kindleDatabase[1])['IDString'].decode('hex')
|
||||
IDString = bytearray.fromhex((kindleDatabase[1])['IDString'])
|
||||
|
||||
try:
|
||||
# Get the UsernameHash token, if present
|
||||
encodedUsername = (kindleDatabase[1])['UsernameHash'].decode('hex')
|
||||
print(u"Got UsernameHash from database {0}".format(kindleDatabase[0]))
|
||||
encodedUsername = bytearray.fromhex((kindleDatabase[1])['UsernameHash'])
|
||||
print("Got UsernameHash from database {0}".format(kindleDatabase[0]))
|
||||
except KeyError:
|
||||
# Get the UserName we added
|
||||
UserName = (kindleDatabase[1])['UserName'].decode('hex')
|
||||
UserName = bytearray.fromhex((kindleDatabase[1])['UserName'])
|
||||
# encode it
|
||||
encodedUsername = encodeHash(UserName,charMap1)
|
||||
#print u"encodedUsername",encodedUsername.encode('hex')
|
||||
#print "encodedUsername",encodedUsername.encode('hex')
|
||||
except KeyError:
|
||||
print(u"Keys not found in the database {0}.".format(kindleDatabase[0]))
|
||||
print("Keys not found in the database {0}.".format(kindleDatabase[0]))
|
||||
return pids
|
||||
|
||||
# Get the ID string used
|
||||
encodedIDString = encodeHash(IDString,charMap1)
|
||||
#print u"encodedIDString",encodedIDString.encode('hex')
|
||||
#print "encodedIDString",encodedIDString.encode('hex')
|
||||
|
||||
# concat, hash and encode to calculate the DSN
|
||||
DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
|
||||
#print u"DSN",DSN.encode('hex')
|
||||
#print "DSN",DSN.encode('hex')
|
||||
pass
|
||||
|
||||
if rec209 is None:
|
||||
@@ -296,16 +296,16 @@ def getPidList(md1, md2, serials=[], kDatabases=[]):
|
||||
|
||||
for kDatabase in kDatabases:
|
||||
try:
|
||||
pidlst.extend(getK4Pids(md1, md2, kDatabase))
|
||||
except Exception, e:
|
||||
print(u"Error getting PIDs from database {0}: {1}".format(kDatabase[0],e.args[0]))
|
||||
pidlst.extend(map(bytes,getK4Pids(md1, md2, kDatabase)))
|
||||
except Exception as e:
|
||||
print("Error getting PIDs from database {0}: {1}".format(kDatabase[0],e.args[0]))
|
||||
traceback.print_exc()
|
||||
|
||||
for serialnum in serials:
|
||||
try:
|
||||
pidlst.extend(getKindlePids(md1, md2, serialnum))
|
||||
except Exception, e:
|
||||
print(u"Error getting PIDs from serial number {0}: {1}".format(serialnum ,e.args[0]))
|
||||
pidlst.extend(map(bytes,getKindlePids(md1, md2, serialnum)))
|
||||
except Exception as e:
|
||||
print("Error getting PIDs from serial number {0}: {1}".format(serialnum ,e.args[0]))
|
||||
traceback.print_exc()
|
||||
|
||||
return pidlst
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# kindlekey.py
|
||||
# Copyright © 2008-2020 Apprentice Harper et al.
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '2.8'
|
||||
__version__ = '3.0'
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - Kindle info file decryption, extracted from k4mobidedrm, etc.
|
||||
@@ -31,6 +29,7 @@ __version__ = '2.8'
|
||||
# 2.6 - Start adding support for Kindle 1.25+ .kinf2018 file
|
||||
# 2.7 - Finish .kinf2018 support, PC & Mac by Apprentice Sakuya
|
||||
# 2.8 - Fix for Mac OS X Big Sur
|
||||
# 3.0 - Python 3 for calibre 5.0
|
||||
|
||||
|
||||
"""
|
||||
@@ -38,9 +37,11 @@ Retrieve Kindle for PC/Mac user key.
|
||||
"""
|
||||
|
||||
import sys, os, re
|
||||
from struct import pack, unpack
|
||||
import codecs
|
||||
from struct import pack, unpack, unpack_from
|
||||
import json
|
||||
import getopt
|
||||
import traceback
|
||||
|
||||
try:
|
||||
RegError
|
||||
@@ -60,10 +61,11 @@ class SafeUnbuffered:
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
if isinstance(data, str):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
self.stream.buffer.write(data)
|
||||
self.stream.buffer.flush()
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
@@ -101,15 +103,13 @@ def unicode_argv():
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
range(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"kindlekey.py"]
|
||||
return ["kindlekey.py"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = "utf-8"
|
||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||
argvencoding = sys.stdin.encoding or "utf-8"
|
||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||
|
||||
class DrmException(Exception):
|
||||
pass
|
||||
@@ -156,14 +156,15 @@ def primes(n):
|
||||
return primeList
|
||||
|
||||
# Encode the bytes in data with the characters in map
|
||||
# data and map should be byte arrays
|
||||
def encode(data, map):
|
||||
result = ''
|
||||
result = b''
|
||||
for char in data:
|
||||
value = ord(char)
|
||||
value = char
|
||||
Q = (value ^ 0x80) // len(map)
|
||||
R = value % len(map)
|
||||
result += map[Q]
|
||||
result += map[R]
|
||||
result += bytes([map[Q]])
|
||||
result += bytes([map[R]])
|
||||
return result
|
||||
|
||||
# Hash the bytes in data and then encode the digest with the characters in map
|
||||
@@ -172,7 +173,7 @@ def encodeHash(data,map):
|
||||
|
||||
# Decode the string in data with the characters in map. Returns the decoded bytes
|
||||
def decode(data,map):
|
||||
result = ''
|
||||
result = b''
|
||||
for i in range (0,len(data)-1,2):
|
||||
high = map.find(data[i])
|
||||
low = map.find(data[i+1])
|
||||
@@ -188,7 +189,7 @@ if iswindows:
|
||||
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
|
||||
string_at, Structure, c_void_p, cast
|
||||
|
||||
import _winreg as winreg
|
||||
import winreg
|
||||
MAX_PATH = 255
|
||||
kernel32 = windll.kernel32
|
||||
advapi32 = windll.advapi32
|
||||
@@ -232,20 +233,12 @@ if iswindows:
|
||||
class DecryptNotBlockAlignedError(DecryptError):
|
||||
""" Error in decryption processing """
|
||||
|
||||
def xorS(a,b):
|
||||
""" XOR two strings """
|
||||
assert len(a)==len(b)
|
||||
x = []
|
||||
for i in range(len(a)):
|
||||
x.append( chr(ord(a[i])^ord(b[i])))
|
||||
return ''.join(x)
|
||||
|
||||
def xor(a,b):
|
||||
""" XOR two strings """
|
||||
""" XOR two byte arrays, to lesser length """
|
||||
x = []
|
||||
for i in range(min(len(a),len(b))):
|
||||
x.append( chr(ord(a[i])^ord(b[i])))
|
||||
return ''.join(x)
|
||||
x.append( a[i] ^ b[i])
|
||||
return bytes(x)
|
||||
|
||||
"""
|
||||
Base 'BlockCipher' and Pad classes for cipher instances.
|
||||
@@ -264,10 +257,10 @@ if iswindows:
|
||||
self.resetDecrypt()
|
||||
def resetEncrypt(self):
|
||||
self.encryptBlockCount = 0
|
||||
self.bytesToEncrypt = ''
|
||||
self.bytesToEncrypt = b''
|
||||
def resetDecrypt(self):
|
||||
self.decryptBlockCount = 0
|
||||
self.bytesToDecrypt = ''
|
||||
self.bytesToDecrypt = b''
|
||||
|
||||
def encrypt(self, plainText, more = None):
|
||||
""" Encrypt a string and return a binary string """
|
||||
@@ -300,14 +293,14 @@ if iswindows:
|
||||
numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize)
|
||||
if more == None: # no more calls to decrypt, should have all the data
|
||||
if numExtraBytes != 0:
|
||||
raise DecryptNotBlockAlignedError, 'Data not block aligned on decrypt'
|
||||
raise DecryptNotBlockAlignedError('Data not block aligned on decrypt')
|
||||
|
||||
# hold back some bytes in case last decrypt has zero len
|
||||
if (more != None) and (numExtraBytes == 0) and (numBlocks >0) :
|
||||
numBlocks -= 1
|
||||
numExtraBytes = self.blockSize
|
||||
|
||||
plainText = ''
|
||||
plainText = b''
|
||||
for i in range(numBlocks):
|
||||
bStart = i*self.blockSize
|
||||
ptBlock = self.decryptBlock(self.bytesToDecrypt[bStart : bStart+self.blockSize])
|
||||
@@ -342,7 +335,7 @@ if iswindows:
|
||||
def removePad(self, paddedBinaryString, blockSize):
|
||||
""" Remove padding from a binary string """
|
||||
if not(0<len(paddedBinaryString)):
|
||||
raise DecryptNotBlockAlignedError, 'Expected More Data'
|
||||
raise DecryptNotBlockAlignedError('Expected More Data')
|
||||
return paddedBinaryString[:-ord(paddedBinaryString[-1])]
|
||||
|
||||
class noPadding(Pad):
|
||||
@@ -372,11 +365,11 @@ if iswindows:
|
||||
self.blockSize = blockSize # blockSize is in bytes
|
||||
self.padding = padding # change default to noPadding() to get normal ECB behavior
|
||||
|
||||
assert( keySize%4==0 and NrTable[4].has_key(keySize/4)),'key size must be 16,20,24,29 or 32 bytes'
|
||||
assert( blockSize%4==0 and NrTable.has_key(blockSize/4)), 'block size must be 16,20,24,29 or 32 bytes'
|
||||
assert( keySize%4==0 and (keySize//4) in NrTable[4]),'key size must be 16,20,24,29 or 32 bytes'
|
||||
assert( blockSize%4==0 and (blockSize//4) in NrTable), 'block size must be 16,20,24,29 or 32 bytes'
|
||||
|
||||
self.Nb = self.blockSize/4 # Nb is number of columns of 32 bit words
|
||||
self.Nk = keySize/4 # Nk is the key length in 32-bit words
|
||||
self.Nb = self.blockSize//4 # Nb is number of columns of 32 bit words
|
||||
self.Nk = keySize//4 # Nk is the key length in 32-bit words
|
||||
self.Nr = NrTable[self.Nb][self.Nk] # The number of rounds (Nr) is a function of
|
||||
# the block (Nb) and key (Nk) sizes.
|
||||
if key != None:
|
||||
@@ -420,15 +413,15 @@ if iswindows:
|
||||
def _toBlock(self, bs):
|
||||
""" Convert binary string to array of bytes, state[col][row]"""
|
||||
assert ( len(bs) == 4*self.Nb ), 'Rijndarl blocks must be of size blockSize'
|
||||
return [[ord(bs[4*i]),ord(bs[4*i+1]),ord(bs[4*i+2]),ord(bs[4*i+3])] for i in range(self.Nb)]
|
||||
return [[bs[4*i],bs[4*i+1],bs[4*i+2],bs[4*i+3]] for i in range(self.Nb)]
|
||||
|
||||
def _toBString(self, block):
|
||||
""" Convert block (array of bytes) to binary string """
|
||||
l = []
|
||||
for col in block:
|
||||
for rowElement in col:
|
||||
l.append(chr(rowElement))
|
||||
return ''.join(l)
|
||||
l.append(rowElement)
|
||||
return bytes(l)
|
||||
#-------------------------------------
|
||||
""" Number of rounds Nr = NrTable[Nb][Nk]
|
||||
|
||||
@@ -440,17 +433,16 @@ if iswindows:
|
||||
7: {4:13, 5:13, 6:13, 7:13, 8:14},
|
||||
8: {4:14, 5:14, 6:14, 7:14, 8:14}}
|
||||
#-------------------------------------
|
||||
def keyExpansion(algInstance, keyString):
|
||||
""" Expand a string of size keySize into a larger array """
|
||||
def keyExpansion(algInstance, keyArray):
|
||||
""" Expand a byte array of size keySize into a larger array """
|
||||
Nk, Nb, Nr = algInstance.Nk, algInstance.Nb, algInstance.Nr # for readability
|
||||
key = [ord(byte) for byte in keyString] # convert string to list
|
||||
w = [[key[4*i],key[4*i+1],key[4*i+2],key[4*i+3]] for i in range(Nk)]
|
||||
w = [[keyArray[4*i],keyArray[4*i+1],keyArray[4*i+2],keyArray[4*i+3]] for i in range(Nk)]
|
||||
for i in range(Nk,Nb*(Nr+1)):
|
||||
temp = w[i-1] # a four byte column
|
||||
if (i%Nk) == 0 :
|
||||
temp = temp[1:]+[temp[0]] # RotWord(temp)
|
||||
temp = [ Sbox[byte] for byte in temp ]
|
||||
temp[0] ^= Rcon[i/Nk]
|
||||
temp[0] ^= Rcon[i//Nk]
|
||||
elif Nk > 6 and i%Nk == 4 :
|
||||
temp = [ Sbox[byte] for byte in temp ] # SubWord(temp)
|
||||
w.append( [ w[i-Nk][byte]^temp[byte] for byte in range(4) ] )
|
||||
@@ -650,7 +642,7 @@ if iswindows:
|
||||
def __init__(self, key = None, padding = padWithPadLen(), keySize=16):
|
||||
""" Initialize AES, keySize is in bytes """
|
||||
if not (keySize == 16 or keySize == 24 or keySize == 32) :
|
||||
raise BadKeySizeError, 'Illegal AES key size, must be 16, 24, or 32 bytes'
|
||||
raise BadKeySizeError('Illegal AES key size, must be 16, 24, or 32 bytes')
|
||||
|
||||
Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 )
|
||||
|
||||
@@ -742,7 +734,7 @@ if iswindows:
|
||||
if self.decryptBlockCount == 0: # first call, process IV
|
||||
if self.iv == None: # auto decrypt IV?
|
||||
self.prior_CT_block = encryptedBlock
|
||||
return ''
|
||||
return b''
|
||||
else:
|
||||
assert(len(self.iv)==self.blockSize),"Bad IV size on CBC decryption"
|
||||
self.prior_CT_block = self.iv
|
||||
@@ -790,10 +782,10 @@ if iswindows:
|
||||
# [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
|
||||
def pbkdf2(self, passwd, salt, iter, keylen):
|
||||
|
||||
def xorstr( a, b ):
|
||||
def xorbytes( a, b ):
|
||||
if len(a) != len(b):
|
||||
raise Exception("xorstr(): lengths differ")
|
||||
return ''.join((chr(ord(x)^ord(y)) for x, y in zip(a, b)))
|
||||
raise Exception("xorbytes(): lengths differ")
|
||||
return bytes([x ^ y for x, y in zip(a, b)])
|
||||
|
||||
def prf( h, data ):
|
||||
hm = h.copy()
|
||||
@@ -805,24 +797,24 @@ if iswindows:
|
||||
T = U
|
||||
for i in range(2, itercount+1):
|
||||
U = prf( h, U )
|
||||
T = xorstr( T, U )
|
||||
T = xorbytes( T, U )
|
||||
return T
|
||||
|
||||
sha = hashlib.sha1
|
||||
digest_size = sha().digest_size
|
||||
# l - number of output blocks to produce
|
||||
l = keylen / digest_size
|
||||
l = keylen // digest_size
|
||||
if keylen % digest_size != 0:
|
||||
l += 1
|
||||
h = hmac.new( passwd, None, sha )
|
||||
T = ""
|
||||
T = b""
|
||||
for i in range(1, l+1):
|
||||
T += pbkdf2_F( h, salt, iter, i )
|
||||
return T[0: keylen]
|
||||
|
||||
def UnprotectHeaderData(encryptedData):
|
||||
passwdData = 'header_key_data'
|
||||
salt = 'HEADER.2011'
|
||||
passwdData = b'header_key_data'
|
||||
salt = b'HEADER.2011'
|
||||
iter = 0x80
|
||||
keylen = 0x100
|
||||
key_iv = KeyIVGen().pbkdf2(passwdData, salt, iter, keylen)
|
||||
@@ -835,12 +827,12 @@ if iswindows:
|
||||
|
||||
# Various character maps used to decrypt kindle info values.
|
||||
# Probably supposed to act as obfuscation
|
||||
charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
|
||||
charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
|
||||
charMap2 = b"AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
|
||||
charMap5 = b"AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
|
||||
# New maps in K4PC 1.9.0
|
||||
testMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
||||
testMap6 = "9YzAb0Cd1Ef2n5Pr6St7Uvh3Jk4M8WxG"
|
||||
testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
|
||||
testMap1 = b"n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
||||
testMap6 = b"9YzAb0Cd1Ef2n5Pr6St7Uvh3Jk4M8WxG"
|
||||
testMap8 = b"YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
|
||||
|
||||
# interface with Windows OS Routines
|
||||
class DataBlob(Structure):
|
||||
@@ -904,12 +896,12 @@ if iswindows:
|
||||
size.value = len(buffer)
|
||||
|
||||
# replace any non-ASCII values with 0xfffd
|
||||
for i in xrange(0,len(buffer)):
|
||||
if buffer[i]>u"\u007f":
|
||||
#print u"swapping char "+str(i)+" ("+buffer[i]+")"
|
||||
buffer[i] = u"\ufffd"
|
||||
for i in range(0,len(buffer)):
|
||||
if buffer[i]>"\u007f":
|
||||
#print "swapping char "+str(i)+" ("+buffer[i]+")"
|
||||
buffer[i] = "\ufffd"
|
||||
# return utf-8 encoding of modified username
|
||||
#print u"modified username:"+buffer.value
|
||||
#print "modified username:"+buffer.value
|
||||
return buffer.value.encode('utf-8')
|
||||
return GetUserName
|
||||
GetUserName = GetUserName()
|
||||
@@ -928,19 +920,19 @@ if iswindows:
|
||||
if not _CryptUnprotectData(byref(indata), None, byref(entropy),
|
||||
None, None, flags, byref(outdata)):
|
||||
# raise DrmException("Failed to Unprotect Data")
|
||||
return 'failed'
|
||||
return b'failed'
|
||||
return string_at(outdata.pbData, outdata.cbData)
|
||||
return CryptUnprotectData
|
||||
CryptUnprotectData = CryptUnprotectData()
|
||||
|
||||
# Returns Environmental Variables that contain unicode
|
||||
# name must be unicode string, not byte string.
|
||||
def getEnvironmentVariable(name):
|
||||
import ctypes
|
||||
name = unicode(name) # make sure string argument is unicode
|
||||
n = ctypes.windll.kernel32.GetEnvironmentVariableW(name, None, 0)
|
||||
if n == 0:
|
||||
return None
|
||||
buf = ctypes.create_unicode_buffer(u'\0'*n)
|
||||
buf = ctypes.create_unicode_buffer("\0"*n)
|
||||
ctypes.windll.kernel32.GetEnvironmentVariableW(name, buf, n)
|
||||
return buf.value
|
||||
|
||||
@@ -952,7 +944,7 @@ if iswindows:
|
||||
path = ""
|
||||
if 'LOCALAPPDATA' in os.environ.keys():
|
||||
# Python 2.x does not return unicode env. Use Python 3.x
|
||||
path = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
|
||||
path = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%")
|
||||
# this is just another alternative.
|
||||
# path = getEnvironmentVariable('LOCALAPPDATA')
|
||||
if not os.path.isdir(path):
|
||||
@@ -980,20 +972,20 @@ if iswindows:
|
||||
print ('Could not find the folder in which to look for kinfoFiles.')
|
||||
else:
|
||||
# Probably not the best. To Fix (shouldn't ignore in encoding) or use utf-8
|
||||
print(u'searching for kinfoFiles in ' + path.encode('ascii', 'ignore'))
|
||||
print("searching for kinfoFiles in " + path)
|
||||
|
||||
# look for (K4PC 1.25.1 and later) .kinf2018 file
|
||||
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2018'
|
||||
if os.path.isfile(kinfopath):
|
||||
found = True
|
||||
print('Found K4PC 1.25+ kinf2018 file: ' + kinfopath.encode('ascii','ignore'))
|
||||
print('Found K4PC 1.25+ kinf2018 file: ' + kinfopath)
|
||||
kInfoFiles.append(kinfopath)
|
||||
|
||||
# look for (K4PC 1.9.0 and later) .kinf2011 file
|
||||
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
|
||||
if os.path.isfile(kinfopath):
|
||||
found = True
|
||||
print('Found K4PC 1.9+ kinf2011 file: ' + kinfopath.encode('ascii','ignore'))
|
||||
print('Found K4PC 1.9+ kinf2011 file: ' + kinfopath)
|
||||
kInfoFiles.append(kinfopath)
|
||||
|
||||
# look for (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
|
||||
@@ -1026,29 +1018,31 @@ if iswindows:
|
||||
# database of keynames and values
|
||||
def getDBfromFile(kInfoFile):
|
||||
names = [\
|
||||
'kindle.account.tokens',\
|
||||
'kindle.cookie.item',\
|
||||
'eulaVersionAccepted',\
|
||||
'login_date',\
|
||||
'kindle.token.item',\
|
||||
'login',\
|
||||
'kindle.key.item',\
|
||||
'kindle.name.info',\
|
||||
'kindle.device.info',\
|
||||
'MazamaRandomNumber',\
|
||||
'max_date',\
|
||||
'SIGVERIF',\
|
||||
'build_version',\
|
||||
'SerialNumber',\
|
||||
'UsernameHash',\
|
||||
'kindle.directedid.info',\
|
||||
'DSN',\
|
||||
'kindle.accounttype.info',\
|
||||
'krx.flashcardsplugin.data.encryption_key',\
|
||||
'krx.notebookexportplugin.data.encryption_key',\
|
||||
'proxy.http.password',\
|
||||
'proxy.http.username'
|
||||
b'kindle.account.tokens',\
|
||||
b'kindle.cookie.item',\
|
||||
b'eulaVersionAccepted',\
|
||||
b'login_date',\
|
||||
b'kindle.token.item',\
|
||||
b'login',\
|
||||
b'kindle.key.item',\
|
||||
b'kindle.name.info',\
|
||||
b'kindle.device.info',\
|
||||
b'MazamaRandomNumber',\
|
||||
b'max_date',\
|
||||
b'SIGVERIF',\
|
||||
b'build_version',\
|
||||
b'SerialNumber',\
|
||||
b'UsernameHash',\
|
||||
b'kindle.directedid.info',\
|
||||
b'DSN',\
|
||||
b'kindle.accounttype.info',\
|
||||
b'krx.flashcardsplugin.data.encryption_key',\
|
||||
b'krx.notebookexportplugin.data.encryption_key',\
|
||||
b'proxy.http.password',\
|
||||
b'proxy.http.username'
|
||||
]
|
||||
namehashmap = {encodeHash(n,testMap8):n for n in names}
|
||||
# print(namehashmap)
|
||||
DB = {}
|
||||
with open(kInfoFile, 'rb') as infoReader:
|
||||
data = infoReader.read()
|
||||
@@ -1056,7 +1050,7 @@ if iswindows:
|
||||
# the .kinf file uses "/" to separate it into records
|
||||
# so remove the trailing "/" to make it easy to use split
|
||||
data = data[:-1]
|
||||
items = data.split('/')
|
||||
items = data.split(b'/')
|
||||
|
||||
# starts with an encoded and encrypted header blob
|
||||
headerblob = items.pop(0)
|
||||
@@ -1064,7 +1058,7 @@ if iswindows:
|
||||
cleartext = UnprotectHeaderData(encryptedValue)
|
||||
#print "header cleartext:",cleartext
|
||||
# now extract the pieces that form the added entropy
|
||||
pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
|
||||
pattern = re.compile(br'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
|
||||
for m in re.finditer(pattern, cleartext):
|
||||
version = int(m.group(1))
|
||||
build = m.group(2)
|
||||
@@ -1073,8 +1067,8 @@ if iswindows:
|
||||
if version == 5: # .kinf2011
|
||||
added_entropy = build + guid
|
||||
elif version == 6: # .kinf2018
|
||||
salt = str(0x6d8 * int(build)) + guid
|
||||
sp = GetUserName() + '+@#$%+' + GetIDString()
|
||||
salt = str(0x6d8 * int(build)).encode('utf-8') + guid
|
||||
sp = GetUserName() + b'+@#$%+' + GetIDString().encode('utf-8')
|
||||
passwd = encode(SHA256(sp), charMap5)
|
||||
key = KeyIVGen().pbkdf2(passwd, salt, 10000, 0x400)[:32] # this is very slow
|
||||
|
||||
@@ -1098,18 +1092,15 @@ if iswindows:
|
||||
# read and store in rcnt records of data
|
||||
# that make up the contents value
|
||||
edlst = []
|
||||
for i in xrange(rcnt):
|
||||
for i in range(rcnt):
|
||||
item = items.pop(0)
|
||||
edlst.append(item)
|
||||
|
||||
# key names now use the new testMap8 encoding
|
||||
keyname = "unknown"
|
||||
for name in names:
|
||||
if encodeHash(name,testMap8) == keyhash:
|
||||
keyname = name
|
||||
#print "keyname found from hash:",keyname
|
||||
break
|
||||
if keyname == "unknown":
|
||||
if keyhash in namehashmap:
|
||||
keyname=namehashmap[keyhash]
|
||||
#print "keyname found from hash:",keyname
|
||||
else:
|
||||
keyname = keyhash
|
||||
#print "keyname not found, hash is:",keyname
|
||||
|
||||
@@ -1126,7 +1117,7 @@ if iswindows:
|
||||
# move first offsets chars to end to align for decode by testMap8
|
||||
# by moving noffset chars from the start of the
|
||||
# string to the end of the string
|
||||
encdata = "".join(edlst)
|
||||
encdata = b"".join(edlst)
|
||||
#print "encrypted data:",encdata
|
||||
contlen = len(encdata)
|
||||
noffset = contlen - primes(int(contlen/3))[-1]
|
||||
@@ -1165,11 +1156,11 @@ if iswindows:
|
||||
|
||||
if len(DB)>6:
|
||||
# store values used in decryption
|
||||
DB['IDString'] = GetIDString()
|
||||
DB['UserName'] = GetUserName()
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().encode('hex'))
|
||||
DB[b'IDString'] = GetIDString().encode('utf-8')
|
||||
DB[b'UserName'] = GetUserName()
|
||||
print("Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().decode('utf-8')))
|
||||
else:
|
||||
print u"Couldn't decrypt file."
|
||||
print("Couldn't decrypt file.")
|
||||
DB = {}
|
||||
return DB
|
||||
elif isosx:
|
||||
@@ -1188,8 +1179,7 @@ elif isosx:
|
||||
try:
|
||||
libcrypto = CDLL(libcrypto)
|
||||
except Exception as e:
|
||||
raise DrmException(u"libcrypto not found: " % e)
|
||||
|
||||
raise DrmException("libcrypto not found: " % e)
|
||||
|
||||
# From OpenSSL's crypto aes header
|
||||
#
|
||||
@@ -1246,14 +1236,14 @@ elif isosx:
|
||||
def set_decrypt_key(self, userkey, iv):
|
||||
self._blocksize = len(userkey)
|
||||
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
||||
raise DrmException(u"AES improper key used")
|
||||
raise DrmException("AES improper key used")
|
||||
return
|
||||
keyctx = self._keyctx = AES_KEY()
|
||||
self._iv = iv
|
||||
self._userkey = userkey
|
||||
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
|
||||
if rv < 0:
|
||||
raise DrmException(u"Failed to initialize AES key")
|
||||
raise DrmException("Failed to initialize AES key")
|
||||
|
||||
def decrypt(self, data):
|
||||
out = create_string_buffer(len(data))
|
||||
@@ -1261,7 +1251,7 @@ elif isosx:
|
||||
keyctx = self._keyctx
|
||||
rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0)
|
||||
if rv == 0:
|
||||
raise DrmException(u"AES decryption failed")
|
||||
raise DrmException("AES decryption failed")
|
||||
return out.raw
|
||||
|
||||
def keyivgen(self, passwd, salt, iter, keylen):
|
||||
@@ -1283,8 +1273,8 @@ elif isosx:
|
||||
LibCrypto = _load_crypto()
|
||||
|
||||
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
|
||||
charMap1 = 'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
|
||||
charMap2 = 'ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM'
|
||||
charMap1 = b'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
|
||||
charMap2 = b'ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM'
|
||||
|
||||
# For kinf approach of K4Mac 1.6.X or later
|
||||
# On K4PC charMap5 = 'AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE'
|
||||
@@ -1292,7 +1282,7 @@ elif isosx:
|
||||
charMap5 = charMap2
|
||||
|
||||
# new in K4M 1.9.X
|
||||
testMap8 = 'YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD'
|
||||
testMap8 = b'YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD'
|
||||
|
||||
# uses a sub process to get the Hard Drive Serial Number using ioreg
|
||||
# returns serial numbers of all internal hard drive drives
|
||||
@@ -1306,11 +1296,11 @@ elif isosx:
|
||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
out1, out2 = p.communicate()
|
||||
#print out1
|
||||
reslst = out1.split('\n')
|
||||
reslst = out1.split(b'\n')
|
||||
cnt = len(reslst)
|
||||
for j in xrange(cnt):
|
||||
for j in range(cnt):
|
||||
resline = reslst[j]
|
||||
pp = resline.find('\"Serial Number\" = \"')
|
||||
pp = resline.find(b'\"Serial Number\" = \"')
|
||||
if pp >= 0:
|
||||
sernum = resline[pp+19:-1]
|
||||
sernums.append(sernum.strip())
|
||||
@@ -1322,12 +1312,12 @@ elif isosx:
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
out1, out2 = p.communicate()
|
||||
reslst = out1.split('\n')
|
||||
reslst = out1.split(b'\n')
|
||||
cnt = len(reslst)
|
||||
for j in xrange(cnt):
|
||||
for j in range(cnt):
|
||||
resline = reslst[j]
|
||||
if resline.startswith('/dev'):
|
||||
(devpart, mpath) = resline.split(' on ')[:2]
|
||||
if resline.startswith(b'/dev'):
|
||||
(devpart, mpath) = resline.split(b' on ')[:2]
|
||||
dpart = devpart[5:]
|
||||
names.append(dpart)
|
||||
return names
|
||||
@@ -1343,11 +1333,11 @@ elif isosx:
|
||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
out1, out2 = p.communicate()
|
||||
#print out1
|
||||
reslst = out1.split('\n')
|
||||
reslst = out1.split(b'\n')
|
||||
cnt = len(reslst)
|
||||
for j in xrange(cnt):
|
||||
for j in range(cnt):
|
||||
resline = reslst[j]
|
||||
pp = resline.find('\"UUID\" = \"')
|
||||
pp = resline.find(b'\"UUID\" = \"')
|
||||
if pp >= 0:
|
||||
uuidnum = resline[pp+10:-1]
|
||||
uuidnum = uuidnum.strip()
|
||||
@@ -1363,16 +1353,16 @@ elif isosx:
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
out1, out2 = p.communicate()
|
||||
reslst = out1.split('\n')
|
||||
reslst = out1.split(b'\n')
|
||||
cnt = len(reslst)
|
||||
for j in xrange(cnt):
|
||||
for j in range(cnt):
|
||||
resline = reslst[j]
|
||||
pp = resline.find('Ethernet Address: ')
|
||||
pp = resline.find(b'Ethernet Address: ')
|
||||
if pp >= 0:
|
||||
#print resline
|
||||
macnum = resline[pp+18:]
|
||||
macnum = macnum.strip()
|
||||
maclst = macnum.split(':')
|
||||
maclst = macnum.split(b':')
|
||||
n = len(maclst)
|
||||
if n != 6:
|
||||
continue
|
||||
@@ -1380,7 +1370,7 @@ elif isosx:
|
||||
# now munge it up the way Kindle app does
|
||||
# by xoring it with 0xa5 and swapping elements 3 and 4
|
||||
for i in range(6):
|
||||
maclst[i] = int('0x' + maclst[i], 0)
|
||||
maclst[i] = int(b'0x' + maclst[i], 0)
|
||||
mlst = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
||||
mlst[5] = maclst[5] ^ 0xa5
|
||||
mlst[4] = maclst[3] ^ 0xa5
|
||||
@@ -1388,7 +1378,7 @@ elif isosx:
|
||||
mlst[2] = maclst[2] ^ 0xa5
|
||||
mlst[1] = maclst[1] ^ 0xa5
|
||||
mlst[0] = maclst[0] ^ 0xa5
|
||||
macnum = '%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x' % (mlst[0], mlst[1], mlst[2], mlst[3], mlst[4], mlst[5])
|
||||
macnum = b'%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x' % (mlst[0], mlst[1], mlst[2], mlst[3], mlst[4], mlst[5])
|
||||
#print 'munged mac', macnum
|
||||
macnums.append(macnum)
|
||||
return macnums
|
||||
@@ -1398,7 +1388,7 @@ elif isosx:
|
||||
def GetUserName():
|
||||
username = os.getenv('USER')
|
||||
#print "Username:",username
|
||||
return username
|
||||
return username.encode('utf-8')
|
||||
|
||||
def GetIDStrings():
|
||||
# Return all possible ID Strings
|
||||
@@ -1407,7 +1397,7 @@ elif isosx:
|
||||
strings.extend(GetVolumesSerialNumbers())
|
||||
strings.extend(GetDiskPartitionNames())
|
||||
strings.extend(GetDiskPartitionUUIDs())
|
||||
strings.append('9999999999')
|
||||
strings.append(b'9999999999')
|
||||
#print "ID Strings:\n",strings
|
||||
return strings
|
||||
|
||||
@@ -1415,8 +1405,8 @@ elif isosx:
|
||||
# unprotect the new header blob in .kinf2011
|
||||
# used in Kindle for Mac Version >= 1.9.0
|
||||
def UnprotectHeaderData(encryptedData):
|
||||
passwdData = 'header_key_data'
|
||||
salt = 'HEADER.2011'
|
||||
passwdData = b'header_key_data'
|
||||
salt = b'HEADER.2011'
|
||||
iter = 0x80
|
||||
keylen = 0x100
|
||||
crp = LibCrypto()
|
||||
@@ -1431,7 +1421,7 @@ elif isosx:
|
||||
# implements an Pseudo Mac Version of Windows built-in Crypto routine
|
||||
class CryptUnprotectData(object):
|
||||
def __init__(self, entropy, IDString):
|
||||
sp = GetUserName() + '+@#$%+' + IDString
|
||||
sp = GetUserName() + b'+@#$%+' + IDString
|
||||
passwdData = encode(SHA256(sp),charMap2)
|
||||
salt = entropy
|
||||
self.crp = LibCrypto()
|
||||
@@ -1510,59 +1500,79 @@ elif isosx:
|
||||
# database of keynames and values
|
||||
def getDBfromFile(kInfoFile):
|
||||
names = [\
|
||||
'kindle.account.tokens',\
|
||||
'kindle.cookie.item',\
|
||||
'eulaVersionAccepted',\
|
||||
'login_date',\
|
||||
'kindle.token.item',\
|
||||
'login',\
|
||||
'kindle.key.item',\
|
||||
'kindle.name.info',\
|
||||
'kindle.device.info',\
|
||||
'MazamaRandomNumber',\
|
||||
'max_date',\
|
||||
'SIGVERIF',\
|
||||
'build_version',\
|
||||
'SerialNumber',\
|
||||
'UsernameHash',\
|
||||
'kindle.directedid.info',\
|
||||
'DSN'
|
||||
b'kindle.account.tokens',\
|
||||
b'kindle.cookie.item',\
|
||||
b'eulaVersionAccepted',\
|
||||
b'login_date',\
|
||||
b'kindle.token.item',\
|
||||
b'login',\
|
||||
b'kindle.key.item',\
|
||||
b'kindle.name.info',\
|
||||
b'kindle.device.info',\
|
||||
b'MazamaRandomNumber',\
|
||||
b'max_date',\
|
||||
b'SIGVERIF',\
|
||||
b'build_version',\
|
||||
b'SerialNumber',\
|
||||
b'UsernameHash',\
|
||||
b'kindle.directedid.info',\
|
||||
b'DSN'
|
||||
b'kindle.accounttype.info',\
|
||||
b'krx.flashcardsplugin.data.encryption_key',\
|
||||
b'krx.notebookexportplugin.data.encryption_key',\
|
||||
b'proxy.http.password',\
|
||||
b'proxy.http.username'
|
||||
]
|
||||
with open(kInfoFile, 'rb') as infoReader:
|
||||
filedata = infoReader.read()
|
||||
|
||||
data = filedata[:-1]
|
||||
items = data.split('/')
|
||||
items = data.split(b'/')
|
||||
IDStrings = GetIDStrings()
|
||||
print ("trying username ", GetUserName(), " on file ", kInfoFile)
|
||||
for IDString in IDStrings:
|
||||
#print "trying IDString:",IDString
|
||||
print ("trying IDString:",IDString)
|
||||
try:
|
||||
DB = {}
|
||||
items = data.split('/')
|
||||
items = data.split(b'/')
|
||||
|
||||
# the headerblob is the encrypted information needed to build the entropy string
|
||||
headerblob = items.pop(0)
|
||||
#print ("headerblob: ",headerblob)
|
||||
encryptedValue = decode(headerblob, charMap1)
|
||||
#print ("encryptedvalue: ",encryptedValue)
|
||||
cleartext = UnprotectHeaderData(encryptedValue)
|
||||
#print ("cleartext: ",cleartext)
|
||||
|
||||
# now extract the pieces in the same way
|
||||
pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
|
||||
pattern = re.compile(br'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
|
||||
for m in re.finditer(pattern, cleartext):
|
||||
version = int(m.group(1))
|
||||
build = m.group(2)
|
||||
guid = m.group(4)
|
||||
|
||||
#print ("version",version)
|
||||
#print ("build",build)
|
||||
#print ("guid",guid,"\n")
|
||||
|
||||
if version == 5: # .kinf2011: identical to K4PC, except the build number gets multiplied
|
||||
entropy = str(0x2df * int(build)) + guid
|
||||
entropy = str(0x2df * int(build)).encode('utf-8') + guid
|
||||
cud = CryptUnprotectData(entropy,IDString)
|
||||
#print ("entropy",entropy)
|
||||
#print ("cud",cud)
|
||||
|
||||
elif version == 6: # .kinf2018: identical to K4PC
|
||||
salt = str(0x6d8 * int(build)) + guid
|
||||
sp = GetUserName() + '+@#$%+' + IDString
|
||||
salt = str(0x6d8 * int(build)).encode('utf-8') + guid
|
||||
sp = GetUserName() + b'+@#$%+' + IDString
|
||||
passwd = encode(SHA256(sp), charMap5)
|
||||
key = LibCrypto().keyivgen(passwd, salt, 10000, 0x400)[:32]
|
||||
|
||||
# loop through the item records until all are processed
|
||||
#print ("salt",salt)
|
||||
#print ("sp",sp)
|
||||
#print ("passwd",passwd)
|
||||
#print ("key",key)
|
||||
|
||||
# loop through the item records until all are processed
|
||||
while len(items) > 0:
|
||||
|
||||
# get the first item record
|
||||
@@ -1571,7 +1581,7 @@ elif isosx:
|
||||
# the first 32 chars of the first record of a group
|
||||
# is the MD5 hash of the key name encoded by charMap5
|
||||
keyhash = item[0:32]
|
||||
keyname = 'unknown'
|
||||
keyname = b'unknown'
|
||||
|
||||
# unlike K4PC the keyhash is not used in generating entropy
|
||||
# entropy = SHA1(keyhash) + added_entropy
|
||||
@@ -1587,16 +1597,16 @@ elif isosx:
|
||||
# read and store in rcnt records of data
|
||||
# that make up the contents value
|
||||
edlst = []
|
||||
for i in xrange(rcnt):
|
||||
for i in range(rcnt):
|
||||
item = items.pop(0)
|
||||
edlst.append(item)
|
||||
|
||||
keyname = 'unknown'
|
||||
keyname = b'unknown'
|
||||
for name in names:
|
||||
if encodeHash(name,testMap8) == keyhash:
|
||||
keyname = name
|
||||
break
|
||||
if keyname == 'unknown':
|
||||
if keyname == b'unknown':
|
||||
keyname = keyhash
|
||||
|
||||
# the testMap8 encoded contents data has had a length
|
||||
@@ -1610,7 +1620,7 @@ elif isosx:
|
||||
# (in other words split 'about' 2/3rds of the way through)
|
||||
|
||||
# move first offsets chars to end to align for decode by testMap8
|
||||
encdata = ''.join(edlst)
|
||||
encdata = b''.join(edlst)
|
||||
contlen = len(encdata)
|
||||
|
||||
# now properly split and recombine
|
||||
@@ -1650,20 +1660,22 @@ elif isosx:
|
||||
|
||||
if len(DB)>6:
|
||||
break
|
||||
except:
|
||||
|
||||
except Exception:
|
||||
print (traceback.format_exc())
|
||||
pass
|
||||
if len(DB)>6:
|
||||
# store values used in decryption
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(IDString, GetUserName())
|
||||
DB['IDString'] = IDString
|
||||
DB['UserName'] = GetUserName()
|
||||
print("Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(IDString.decode('utf-8'), GetUserName().decode('utf-8')))
|
||||
DB[b'IDString'] = IDString
|
||||
DB[b'UserName'] = GetUserName()
|
||||
else:
|
||||
print u"Couldn't decrypt file."
|
||||
print("Couldn't decrypt file.")
|
||||
DB = {}
|
||||
return DB
|
||||
else:
|
||||
def getDBfromFile(kInfoFile):
|
||||
raise DrmException(u"This script only runs under Windows or Mac OS X.")
|
||||
raise DrmException("This script only runs under Windows or Mac OS X.")
|
||||
return {}
|
||||
|
||||
def kindlekeys(files = []):
|
||||
@@ -1674,9 +1686,11 @@ def kindlekeys(files = []):
|
||||
key = getDBfromFile(file)
|
||||
if key:
|
||||
# convert all values to hex, just in case.
|
||||
for keyname in key:
|
||||
key[keyname]=key[keyname].encode('hex')
|
||||
keys.append(key)
|
||||
n_key = {}
|
||||
for k,v in key.items():
|
||||
n_key[k.decode()]=codecs.encode(v, 'hex_codec').decode()
|
||||
# key = {k.decode():v.decode() for k,v in key.items()}
|
||||
keys.append(n_key)
|
||||
return keys
|
||||
|
||||
# interface for Python DeDRM
|
||||
@@ -1686,29 +1700,29 @@ def getkey(outpath, files=[]):
|
||||
if len(keys) > 0:
|
||||
if not os.path.isdir(outpath):
|
||||
outfile = outpath
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
with open(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(json.dumps(keys[0]))
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
print("Saved a key to {0}".format(outfile))
|
||||
else:
|
||||
keycount = 0
|
||||
for key in keys:
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(outpath,u"kindlekey{0:d}.k4i".format(keycount))
|
||||
outfile = os.path.join(outpath,"kindlekey{0:d}.k4i".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
with open(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(json.dumps(key))
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
print("Saved a key to {0}".format(outfile))
|
||||
return True
|
||||
return False
|
||||
|
||||
def usage(progname):
|
||||
print u"Finds, decrypts and saves the default Kindle For Mac/PC encryption keys."
|
||||
print u"Keys are saved to the current directory, or a specified output directory."
|
||||
print u"If a file name is passed instead of a directory, only the first key is saved, in that file."
|
||||
print u"Usage:"
|
||||
print u" {0:s} [-h] [-k <kindle.info>] [<outpath>]".format(progname)
|
||||
print("Finds, decrypts and saves the default Kindle For Mac/PC encryption keys.")
|
||||
print("Keys are saved to the current directory, or a specified output directory.")
|
||||
print("If a file name is passed instead of a directory, only the first key is saved, in that file.")
|
||||
print("Usage:")
|
||||
print(" {0:s} [-h] [-k <kindle.info>] [<outpath>]".format(progname))
|
||||
|
||||
|
||||
def cli_main():
|
||||
@@ -1716,12 +1730,12 @@ def cli_main():
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print u"{0} v{1}\nCopyright © 2010-2016 by some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__)
|
||||
print("{0} v{1}\nCopyright © 2010-2020 by some_updates, Apprentice Harper et al.".format(progname,__version__))
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], "hk:")
|
||||
except getopt.GetoptError, err:
|
||||
print u"Error in options or arguments: {0}".format(err.args[0])
|
||||
except getopt.GetoptError as err:
|
||||
print("Error in options or arguments: {0}".format(err.args[0]))
|
||||
usage(progname)
|
||||
sys.exit(2)
|
||||
|
||||
@@ -1750,33 +1764,33 @@ def cli_main():
|
||||
outpath = os.path.realpath(os.path.normpath(outpath))
|
||||
|
||||
if not getkey(outpath, files):
|
||||
print u"Could not retrieve Kindle for Mac/PC key."
|
||||
print("Could not retrieve Kindle for Mac/PC key.")
|
||||
return 0
|
||||
|
||||
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import tkinter
|
||||
import tkinter.constants
|
||||
import tkinter.messagebox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class ExceptionDialog(Tkinter.Frame):
|
||||
class ExceptionDialog(tkinter.Frame):
|
||||
def __init__(self, root, text):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
label = Tkinter.Label(self, text=u"Unexpected error:",
|
||||
anchor=Tkconstants.W, justify=Tkconstants.LEFT)
|
||||
label.pack(fill=Tkconstants.X, expand=0)
|
||||
self.text = Tkinter.Text(self)
|
||||
self.text.pack(fill=Tkconstants.BOTH, expand=1)
|
||||
tkinter.Frame.__init__(self, root, border=5)
|
||||
label = tkinter.Label(self, text="Unexpected error:",
|
||||
anchor=tkinter.constants.W, justify=tkinter.constants.LEFT)
|
||||
label.pack(fill=tkinter.constants.X, expand=0)
|
||||
self.text = tkinter.Text(self)
|
||||
self.text.pack(fill=tkinter.constants.BOTH, expand=1)
|
||||
|
||||
self.text.insert(Tkconstants.END, text)
|
||||
self.text.insert(tkinter.constants.END, text)
|
||||
|
||||
|
||||
argv=unicode_argv()
|
||||
root = Tkinter.Tk()
|
||||
root = tkinter.Tk()
|
||||
root.withdraw()
|
||||
progpath, progname = os.path.split(argv[0])
|
||||
success = False
|
||||
@@ -1786,21 +1800,21 @@ def gui_main():
|
||||
for key in keys:
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(progpath,u"kindlekey{0:d}.k4i".format(keycount))
|
||||
outfile = os.path.join(progpath,"kindlekey{0:d}.k4i".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
with open(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(json.dumps(key))
|
||||
success = True
|
||||
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
||||
except DrmException, e:
|
||||
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
|
||||
tkinter.messagebox.showinfo(progname, "Key successfully retrieved to {0}".format(outfile))
|
||||
except DrmException as e:
|
||||
tkinter.messagebox.showerror(progname, "Error: {0}".format(str(e)))
|
||||
except Exception:
|
||||
root.wm_state('normal')
|
||||
root.title(progname)
|
||||
text = traceback.format_exc()
|
||||
ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1)
|
||||
ExceptionDialog(root, text).pack(fill=tkinter.constants.BOTH, expand=1)
|
||||
root.mainloop()
|
||||
if not success:
|
||||
return 1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Mobipocket PID calculator v0.4 for Amazon Kindle.
|
||||
@@ -10,8 +10,9 @@
|
||||
# 0.3 updated for unicode
|
||||
# 0.4 Added support for serial numbers starting with '9', fixed unicode bugs.
|
||||
# 0.5 moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 1.0 Python 3 for calibre 5.0
|
||||
|
||||
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
import binascii
|
||||
|
||||
@@ -25,10 +26,11 @@ class SafeUnbuffered:
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
if isinstance(data, str):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
self.stream.buffer.write(data)
|
||||
self.stream.buffer.flush()
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
@@ -63,19 +65,13 @@ def unicode_argv():
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
range(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"kindlepid.py"]
|
||||
return ["kindlepid.py"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = "utf-8"
|
||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||
|
||||
if sys.hexversion >= 0x3000000:
|
||||
print('This script is incompatible with Python 3.x. Please install Python 2.7.x.')
|
||||
sys.exit(2)
|
||||
argvencoding = sys.stdin.encoding or "utf-8"
|
||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||
|
||||
letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
|
||||
|
||||
@@ -83,7 +79,7 @@ def crc32(s):
|
||||
return (~binascii.crc32(s,-1))&0xFFFFFFFF
|
||||
|
||||
def checksumPid(s):
|
||||
crc = crc32(s)
|
||||
crc = crc32(s.encode('ascii'))
|
||||
crc = crc ^ (crc >> 16)
|
||||
res = s
|
||||
l = len(letters)
|
||||
@@ -99,43 +95,43 @@ def pidFromSerial(s, l):
|
||||
crc = crc32(s)
|
||||
|
||||
arr1 = [0]*l
|
||||
for i in xrange(len(s)):
|
||||
arr1[i%l] ^= ord(s[i])
|
||||
for i in range(len(s)):
|
||||
arr1[i%l] ^= s[i]
|
||||
|
||||
crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
|
||||
for i in xrange(l):
|
||||
for i in range(l):
|
||||
arr1[i] ^= crc_bytes[i&3]
|
||||
|
||||
pid = ''
|
||||
for i in xrange(l):
|
||||
for i in range(l):
|
||||
b = arr1[i] & 0xff
|
||||
pid+=letters[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]
|
||||
|
||||
return pid
|
||||
|
||||
def cli_main():
|
||||
print(u"Mobipocket PID calculator for Amazon Kindle. Copyright © 2007, 2009 Igor Skochinsky")
|
||||
print("Mobipocket PID calculator for Amazon Kindle. Copyright © 2007, 2009 Igor Skochinsky")
|
||||
argv=unicode_argv()
|
||||
if len(argv)==2:
|
||||
serial = argv[1]
|
||||
else:
|
||||
print(u"Usage: kindlepid.py <Kindle Serial Number>/<iPhone/iPod Touch UDID>")
|
||||
print("Usage: kindlepid.py <Kindle Serial Number>/<iPhone/iPod Touch UDID>")
|
||||
return 1
|
||||
if len(serial)==16:
|
||||
if serial.startswith("B") or serial.startswith("9"):
|
||||
print(u"Kindle serial number detected")
|
||||
print("Kindle serial number detected")
|
||||
else:
|
||||
print(u"Warning: unrecognized serial number. Please recheck input.")
|
||||
print("Warning: unrecognized serial number. Please recheck input.")
|
||||
return 1
|
||||
pid = pidFromSerial(serial.encode("utf-8"),7)+'*'
|
||||
print(u"Mobipocket PID for Kindle serial#{0} is {1}".format(serial,checksumPid(pid)))
|
||||
print("Mobipocket PID for Kindle serial#{0} is {1}".format(serial,checksumPid(pid)))
|
||||
return 0
|
||||
elif len(serial)==40:
|
||||
print(u"iPhone serial number (UDID) detected")
|
||||
print("iPhone serial number (UDID) detected")
|
||||
pid = pidFromSerial(serial.encode("utf-8"),8)
|
||||
print(u"Mobipocket PID for iPhone serial#{0} is {1}".format(serial,checksumPid(pid)))
|
||||
print("Mobipocket PID for iPhone serial#{0} is {1}".format(serial,checksumPid(pid)))
|
||||
return 0
|
||||
print(u"Warning: unrecognized serial number. Please recheck input.")
|
||||
print("Warning: unrecognized serial number. Please recheck input.")
|
||||
return 1
|
||||
|
||||
|
||||
|
||||
199
DeDRM_plugin/mobidedrm.py
Normal file → Executable file
199
DeDRM_plugin/mobidedrm.py
Normal file → Executable file
@@ -1,13 +1,13 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# mobidedrm.py
|
||||
# Copyright © 2008 The Dark Reverser
|
||||
# Portions © 2008–2017 Apprentice Harper et al.
|
||||
# Portions © 2008–2020 Apprentice Harper et al.
|
||||
|
||||
from __future__ import print_function
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = u"0.42"
|
||||
__version__ = "1.0"
|
||||
|
||||
# This is a python script. You need a Python interpreter to run it.
|
||||
# For example, ActiveState Python, which exists for windows.
|
||||
@@ -73,6 +73,7 @@ __version__ = u"0.42"
|
||||
# 0.40 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 0.41 - Fixed potential unicode problem in command line calls
|
||||
# 0.42 - Added GPL v3 licence. updated/removed some print statements
|
||||
# 1.0 - Python 3 compatibility for calibre 5.0
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -81,7 +82,7 @@ import binascii
|
||||
try:
|
||||
from alfcrypto import Pukall_Cipher
|
||||
except:
|
||||
print(u"AlfCrypto not found. Using python PC1 implementation.")
|
||||
print("AlfCrypto not found. Using python PC1 implementation.")
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
@@ -93,10 +94,11 @@ class SafeUnbuffered:
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
if isinstance(data, str):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
self.stream.buffer.write(data)
|
||||
self.stream.buffer.flush()
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
@@ -131,15 +133,13 @@ def unicode_argv():
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
range(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"mobidedrm.py"]
|
||||
return ["mobidedrm.py"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = 'utf-8'
|
||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||
argvencoding = sys.stdin.encoding or "utf-8"
|
||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||
|
||||
|
||||
class DrmException(Exception):
|
||||
@@ -165,35 +165,36 @@ def PC1(key, src, decryption=True):
|
||||
sum2 = 0;
|
||||
keyXorVal = 0;
|
||||
if len(key)!=16:
|
||||
DrmException (u"PC1: Bad key length")
|
||||
DrmException ("PC1: Bad key length")
|
||||
wkey = []
|
||||
for i in xrange(8):
|
||||
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
|
||||
dst = ""
|
||||
for i in xrange(len(src)):
|
||||
for i in range(8):
|
||||
wkey.append(key[i*2]<<8 | key[i*2+1])
|
||||
dst = b''
|
||||
for i in range(len(src)):
|
||||
temp1 = 0;
|
||||
byteXorVal = 0;
|
||||
for j in xrange(8):
|
||||
for j in range(8):
|
||||
temp1 ^= wkey[j]
|
||||
sum2 = (sum2+j)*20021 + sum1
|
||||
sum1 = (temp1*346)&0xFFFF
|
||||
sum2 = (sum2+sum1)&0xFFFF
|
||||
temp1 = (temp1*20021+1)&0xFFFF
|
||||
byteXorVal ^= temp1 ^ sum2
|
||||
curByte = ord(src[i])
|
||||
curByte = src[i]
|
||||
if not decryption:
|
||||
keyXorVal = curByte * 257;
|
||||
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
|
||||
if decryption:
|
||||
keyXorVal = curByte * 257;
|
||||
for j in xrange(8):
|
||||
for j in range(8):
|
||||
wkey[j] ^= keyXorVal;
|
||||
dst+=chr(curByte)
|
||||
dst+=bytes([curByte])
|
||||
return dst
|
||||
|
||||
# accepts unicode returns unicode
|
||||
def checksumPid(s):
|
||||
letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
|
||||
crc = (~binascii.crc32(s,-1))&0xFFFFFFFF
|
||||
crc = (~binascii.crc32(s.encode('utf-8'),-1))&0xFFFFFFFF
|
||||
crc = crc ^ (crc >> 16)
|
||||
res = s
|
||||
l = len(letters)
|
||||
@@ -204,13 +205,14 @@ def checksumPid(s):
|
||||
crc >>= 8
|
||||
return res
|
||||
|
||||
# expects bytearray
|
||||
def getSizeOfTrailingDataEntries(ptr, size, flags):
|
||||
def getSizeOfTrailingDataEntry(ptr, size):
|
||||
bitpos, result = 0, 0
|
||||
if size <= 0:
|
||||
return result
|
||||
while True:
|
||||
v = ord(ptr[size-1])
|
||||
v = ptr[size-1]
|
||||
result |= (v & 0x7F) << bitpos
|
||||
bitpos += 7
|
||||
size -= 1
|
||||
@@ -226,7 +228,7 @@ def getSizeOfTrailingDataEntries(ptr, size, flags):
|
||||
# if multibyte data is included in the encryped data, we'll
|
||||
# have already cleared this flag.
|
||||
if flags & 1:
|
||||
num += (ord(ptr[size - num - 1]) & 0x3) + 1
|
||||
num += (ptr[size - num - 1] & 0x3) + 1
|
||||
return num
|
||||
|
||||
|
||||
@@ -245,26 +247,26 @@ class MobiBook:
|
||||
pass
|
||||
|
||||
def __init__(self, infile):
|
||||
print(u"MobiDeDrm v{0:s}.\nCopyright © 2008-2017 The Dark Reverser, Apprentice Harper et al.".format(__version__))
|
||||
print("MobiDeDrm v{0:s}.\nCopyright © 2008-2020 The Dark Reverser, Apprentice Harper et al.".format(__version__))
|
||||
|
||||
try:
|
||||
from alfcrypto import Pukall_Cipher
|
||||
except:
|
||||
print(u"AlfCrypto not found. Using python PC1 implementation.")
|
||||
print("AlfCrypto not found. Using python PC1 implementation.")
|
||||
|
||||
# initial sanity check on file
|
||||
self.data_file = file(infile, 'rb').read()
|
||||
self.data_file = open(infile, 'rb').read()
|
||||
self.mobi_data = ''
|
||||
self.header = self.data_file[0:78]
|
||||
if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd':
|
||||
raise DrmException(u"Invalid file format")
|
||||
if self.header[0x3C:0x3C+8] != b'BOOKMOBI' and self.header[0x3C:0x3C+8] != b'TEXtREAd':
|
||||
raise DrmException("Invalid file format")
|
||||
self.magic = self.header[0x3C:0x3C+8]
|
||||
self.crypto_type = -1
|
||||
|
||||
# build up section offset and flag info
|
||||
self.num_sections, = struct.unpack('>H', self.header[76:78])
|
||||
self.sections = []
|
||||
for i in xrange(self.num_sections):
|
||||
for i in range(self.num_sections):
|
||||
offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.data_file[78+i*8:78+i*8+8])
|
||||
flags, val = a1, a2<<16|a3<<8|a4
|
||||
self.sections.append( (offset, flags, val) )
|
||||
@@ -282,17 +284,17 @@ class MobiBook:
|
||||
self.mobi_codepage = 1252
|
||||
self.mobi_version = -1
|
||||
|
||||
if self.magic == 'TEXtREAd':
|
||||
print(u"PalmDoc format book detected.")
|
||||
if self.magic == b'TEXtREAd':
|
||||
print("PalmDoc format book detected.")
|
||||
return
|
||||
|
||||
self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
|
||||
self.mobi_codepage, = struct.unpack('>L',self.sect[0x1c:0x20])
|
||||
self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C])
|
||||
#print u"MOBI header version {0:d}, header length {1:d}".format(self.mobi_version, self.mobi_length)
|
||||
#print "MOBI header version {0:d}, header length {1:d}".format(self.mobi_version, self.mobi_length)
|
||||
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
|
||||
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
|
||||
#print u"Extra Data Flags: {0:d}".format(self.extra_data_flags)
|
||||
#print "Extra Data Flags: {0:d}".format(self.extra_data_flags)
|
||||
if (self.compression != 17480):
|
||||
# multibyte utf8 data is included in the encryption for PalmDoc compression
|
||||
# so clear that byte so that we leave it to be decrypted.
|
||||
@@ -301,36 +303,37 @@ class MobiBook:
|
||||
# if exth region exists parse it for metadata array
|
||||
try:
|
||||
exth_flag, = struct.unpack('>L', self.sect[0x80:0x84])
|
||||
exth = ''
|
||||
exth = b''
|
||||
if exth_flag & 0x40:
|
||||
exth = self.sect[16 + self.mobi_length:]
|
||||
if (len(exth) >= 12) and (exth[:4] == 'EXTH'):
|
||||
if (len(exth) >= 12) and (exth[:4] == b'EXTH'):
|
||||
nitems, = struct.unpack('>I', exth[8:12])
|
||||
pos = 12
|
||||
for i in xrange(nitems):
|
||||
for i in range(nitems):
|
||||
type, size = struct.unpack('>II', exth[pos: pos + 8])
|
||||
content = exth[pos + 8: pos + size]
|
||||
self.meta_array[type] = content
|
||||
# reset the text to speech flag and clipping limit, if present
|
||||
if type == 401 and size == 9:
|
||||
# set clipping limit to 100%
|
||||
self.patchSection(0, '\144', 16 + self.mobi_length + pos + 8)
|
||||
self.patchSection(0, b'\144', 16 + self.mobi_length + pos + 8)
|
||||
elif type == 404 and size == 9:
|
||||
# make sure text to speech is enabled
|
||||
self.patchSection(0, '\0', 16 + self.mobi_length + pos + 8)
|
||||
self.patchSection(0, b'\0', 16 + self.mobi_length + pos + 8)
|
||||
# print type, size, content, content.encode('hex')
|
||||
pos += size
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
print("Cannot set meta_array: Error: {:s}".format(e.args[0]))
|
||||
|
||||
#returns unicode
|
||||
def getBookTitle(self):
|
||||
codec_map = {
|
||||
1252 : 'windows-1252',
|
||||
65001 : 'utf-8',
|
||||
}
|
||||
title = ''
|
||||
title = b''
|
||||
codec = 'windows-1252'
|
||||
if self.magic == 'BOOKMOBI':
|
||||
if self.magic == b'BOOKMOBI':
|
||||
if 503 in self.meta_array:
|
||||
title = self.meta_array[503]
|
||||
else:
|
||||
@@ -339,29 +342,31 @@ class MobiBook:
|
||||
title = self.sect[toff:tend]
|
||||
if self.mobi_codepage in codec_map.keys():
|
||||
codec = codec_map[self.mobi_codepage]
|
||||
if title == '':
|
||||
if title == b'':
|
||||
title = self.header[:32]
|
||||
title = title.split('\0')[0]
|
||||
return unicode(title, codec)
|
||||
title = title.split(b'\0')[0]
|
||||
return title.decode(codec)
|
||||
|
||||
def getPIDMetaInfo(self):
|
||||
rec209 = ''
|
||||
token = ''
|
||||
rec209 = b''
|
||||
token = b''
|
||||
if 209 in self.meta_array:
|
||||
rec209 = self.meta_array[209]
|
||||
data = rec209
|
||||
# The 209 data comes in five byte groups. Interpret the last four bytes
|
||||
# of each group as a big endian unsigned integer to get a key value
|
||||
# if that key exists in the meta_array, append its contents to the token
|
||||
for i in xrange(0,len(data),5):
|
||||
for i in range(0,len(data),5):
|
||||
val, = struct.unpack('>I',data[i+1:i+5])
|
||||
sval = self.meta_array.get(val,'')
|
||||
sval = self.meta_array.get(val,b'')
|
||||
token += sval
|
||||
return rec209, token
|
||||
|
||||
# new must be byte array
|
||||
def patch(self, off, new):
|
||||
self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
|
||||
|
||||
# new must be byte array
|
||||
def patchSection(self, section, new, in_off = 0):
|
||||
if (section + 1 == self.num_sections):
|
||||
endoff = len(self.data_file)
|
||||
@@ -371,15 +376,16 @@ class MobiBook:
|
||||
assert off + in_off + len(new) <= endoff
|
||||
self.patch(off + in_off, new)
|
||||
|
||||
# pids in pidlist must be unicode, returned key is byte array, pid is unicode
|
||||
def parseDRM(self, data, count, pidlist):
|
||||
found_key = None
|
||||
keyvec1 = '\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96'
|
||||
keyvec1 = b'\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96'
|
||||
for pid in pidlist:
|
||||
bigpid = pid.ljust(16,'\0')
|
||||
bigpid = pid.encode('utf-8').ljust(16,b'\0')
|
||||
temp_key = PC1(keyvec1, bigpid, False)
|
||||
temp_key_sum = sum(map(ord,temp_key)) & 0xff
|
||||
temp_key_sum = sum(temp_key) & 0xff
|
||||
found_key = None
|
||||
for i in xrange(count):
|
||||
for i in range(count):
|
||||
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
|
||||
if cksum == temp_key_sum:
|
||||
cookie = PC1(temp_key, cookie)
|
||||
@@ -393,8 +399,8 @@ class MobiBook:
|
||||
# Then try the default encoding that doesn't require a PID
|
||||
pid = '00000000'
|
||||
temp_key = keyvec1
|
||||
temp_key_sum = sum(map(ord,temp_key)) & 0xff
|
||||
for i in xrange(count):
|
||||
temp_key_sum = sum(temp_key) & 0xff
|
||||
for i in range(count):
|
||||
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
|
||||
if cksum == temp_key_sum:
|
||||
cookie = PC1(temp_key, cookie)
|
||||
@@ -405,56 +411,62 @@ class MobiBook:
|
||||
return [found_key,pid]
|
||||
|
||||
def getFile(self, outpath):
|
||||
file(outpath,'wb').write(self.mobi_data)
|
||||
open(outpath,'wb').write(self.mobi_data)
|
||||
|
||||
def getBookType(self):
|
||||
if self.print_replica:
|
||||
return u"Print Replica"
|
||||
return "Print Replica"
|
||||
if self.mobi_version >= 8:
|
||||
return u"Kindle Format 8"
|
||||
return "Kindle Format 8"
|
||||
if self.mobi_version >= 0:
|
||||
return u"Mobipocket {0:d}".format(self.mobi_version)
|
||||
return u"PalmDoc"
|
||||
return "Mobipocket {0:d}".format(self.mobi_version)
|
||||
return "PalmDoc"
|
||||
|
||||
def getBookExtension(self):
|
||||
if self.print_replica:
|
||||
return u".azw4"
|
||||
return ".azw4"
|
||||
if self.mobi_version >= 8:
|
||||
return u".azw3"
|
||||
return u".mobi"
|
||||
return ".azw3"
|
||||
return ".mobi"
|
||||
|
||||
# pids in pidlist may be unicode or bytearrays or bytes
|
||||
def processBook(self, pidlist):
|
||||
crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
|
||||
print(u"Crypto Type is: {0:d}".format(crypto_type))
|
||||
print("Crypto Type is: {0:d}".format(crypto_type))
|
||||
self.crypto_type = crypto_type
|
||||
if crypto_type == 0:
|
||||
print(u"This book is not encrypted.")
|
||||
print("This book is not encrypted.")
|
||||
# we must still check for Print Replica
|
||||
self.print_replica = (self.loadSection(1)[0:4] == '%MOP')
|
||||
self.mobi_data = self.data_file
|
||||
return
|
||||
if crypto_type != 2 and crypto_type != 1:
|
||||
raise DrmException(u"Cannot decode unknown Mobipocket encryption type {0:d}".format(crypto_type))
|
||||
raise DrmException("Cannot decode unknown Mobipocket encryption type {0:d}".format(crypto_type))
|
||||
if 406 in self.meta_array:
|
||||
data406 = self.meta_array[406]
|
||||
val406, = struct.unpack('>Q',data406)
|
||||
if val406 != 0:
|
||||
raise DrmException(u"Cannot decode library or rented ebooks.")
|
||||
raise DrmException("Cannot decode library or rented ebooks.")
|
||||
|
||||
goodpids = []
|
||||
# print("DEBUG ==== pidlist = ", pidlist)
|
||||
for pid in pidlist:
|
||||
if isinstance(pid,(bytearray,bytes)):
|
||||
pid = pid.decode('utf-8')
|
||||
if len(pid)==10:
|
||||
if checksumPid(pid[0:-2]) != pid:
|
||||
print(u"Warning: PID {0} has incorrect checksum, should have been {1}".format(pid,checksumPid(pid[0:-2])))
|
||||
print("Warning: PID {0} has incorrect checksum, should have been {1}".format(pid,checksumPid(pid[0:-2])))
|
||||
goodpids.append(pid[0:-2])
|
||||
elif len(pid)==8:
|
||||
goodpids.append(pid)
|
||||
else:
|
||||
print(u"Warning: PID {0} has wrong number of digits".format(pid))
|
||||
print("Warning: PID {0} has wrong number of digits".format(pid))
|
||||
|
||||
# print("======= DEBUG good pids = ", goodpids)
|
||||
|
||||
if self.crypto_type == 1:
|
||||
t1_keyvec = 'QDCVEPMU675RUBSZ'
|
||||
if self.magic == 'TEXtREAd':
|
||||
t1_keyvec = b'QDCVEPMU675RUBSZ'
|
||||
if self.magic == b'TEXtREAd':
|
||||
bookkey_data = self.sect[0x0E:0x0E+16]
|
||||
elif self.mobi_version < 0:
|
||||
bookkey_data = self.sect[0x90:0x90+16]
|
||||
@@ -466,32 +478,32 @@ class MobiBook:
|
||||
# calculate the keys
|
||||
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16])
|
||||
if drm_count == 0:
|
||||
raise DrmException(u"Encryption not initialised. Must be opened with Mobipocket Reader first.")
|
||||
raise DrmException("Encryption not initialised. Must be opened with Mobipocket Reader first.")
|
||||
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
|
||||
if not found_key:
|
||||
raise DrmException(u"No key found in {0:d} keys tried.".format(len(goodpids)))
|
||||
raise DrmException("No key found in {0:d} PIDs tried.".format(len(goodpids)))
|
||||
# kill the drm keys
|
||||
self.patchSection(0, '\0' * drm_size, drm_ptr)
|
||||
self.patchSection(0, b'\0' * drm_size, drm_ptr)
|
||||
# kill the drm pointers
|
||||
self.patchSection(0, '\xff' * 4 + '\0' * 12, 0xA8)
|
||||
self.patchSection(0, b'\xff' * 4 + b'\0' * 12, 0xA8)
|
||||
|
||||
if pid=='00000000':
|
||||
print(u"File has default encryption, no specific key needed.")
|
||||
print("File has default encryption, no specific key needed.")
|
||||
else:
|
||||
print(u"File is encoded with PID {0}.".format(checksumPid(pid)))
|
||||
print("File is encoded with PID {0}.".format(checksumPid(pid)))
|
||||
|
||||
# clear the crypto type
|
||||
self.patchSection(0, "\0" * 2, 0xC)
|
||||
self.patchSection(0, b'\0' * 2, 0xC)
|
||||
|
||||
# decrypt sections
|
||||
print(u"Decrypting. Please wait . . .", end=' ')
|
||||
print("Decrypting. Please wait . . .", end=' ')
|
||||
mobidataList = []
|
||||
mobidataList.append(self.data_file[:self.sections[1][0]])
|
||||
for i in xrange(1, self.records+1):
|
||||
for i in range(1, self.records+1):
|
||||
data = self.loadSection(i)
|
||||
extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags)
|
||||
if i%100 == 0:
|
||||
print(u".", end=' ')
|
||||
print(".", end=' ')
|
||||
# print "record %d, extra_size %d" %(i,extra_size)
|
||||
decoded_data = PC1(found_key, data[0:len(data) - extra_size])
|
||||
if i==1:
|
||||
@@ -501,13 +513,14 @@ class MobiBook:
|
||||
mobidataList.append(data[-extra_size:])
|
||||
if self.num_sections > self.records+1:
|
||||
mobidataList.append(self.data_file[self.sections[self.records+1][0]:])
|
||||
self.mobi_data = "".join(mobidataList)
|
||||
print(u"done")
|
||||
self.mobi_data = b''.join(mobidataList)
|
||||
print("done")
|
||||
return
|
||||
|
||||
# pids in pidlist must be unicode
|
||||
def getUnencryptedBook(infile,pidlist):
|
||||
if not os.path.isfile(infile):
|
||||
raise DrmException(u"Input File Not Found.")
|
||||
raise DrmException("Input File Not Found.")
|
||||
book = MobiBook(infile)
|
||||
book.processBook(pidlist)
|
||||
return book.mobi_data
|
||||
@@ -517,10 +530,10 @@ def cli_main():
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if len(argv)<3 or len(argv)>4:
|
||||
print(u"MobiDeDrm v{0:s}.\nCopyright © 2008-2017 The Dark Reverser, Apprentice Harper et al.".format(__version__))
|
||||
print(u"Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks")
|
||||
print(u"Usage:")
|
||||
print(u" {0} <infile> <outfile> [<Comma separated list of PIDs to try>]".format(progname))
|
||||
print("MobiDeDrm v{0:s}.\nCopyright © 2008-2020 The Dark Reverser, Apprentice Harper et al.".format(__version__))
|
||||
print("Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks")
|
||||
print("Usage:")
|
||||
print(" {0} <infile> <outfile> [<Comma separated list of PIDs to try>]".format(progname))
|
||||
return 1
|
||||
else:
|
||||
infile = argv[1]
|
||||
@@ -531,9 +544,9 @@ def cli_main():
|
||||
pidlist = []
|
||||
try:
|
||||
stripped_file = getUnencryptedBook(infile, pidlist)
|
||||
file(outfile, 'wb').write(stripped_file)
|
||||
except DrmException, e:
|
||||
print(u"MobiDeDRM v{0} Error: {1:s}".format(__version__,e.args[0]))
|
||||
open(outfile, 'wb').write(stripped_file)
|
||||
except DrmException as e:
|
||||
print("MobiDeDRM v{0} Error: {1:s}".format(__version__,e.args[0]))
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ def load_libcrypto():
|
||||
return ob.raw
|
||||
def decrypt(self, data):
|
||||
if not data:
|
||||
return ''
|
||||
return b''
|
||||
i = 0
|
||||
result = []
|
||||
while i < len(data):
|
||||
@@ -84,6 +84,6 @@ def load_libcrypto():
|
||||
processed_block = self.desdecrypt(block)
|
||||
result.append(processed_block)
|
||||
i += 8
|
||||
return ''.join(result)
|
||||
return b''.join(result)
|
||||
|
||||
return DES
|
||||
|
||||
56
DeDRM_plugin/prefs.py
Normal file → Executable file
56
DeDRM_plugin/prefs.py
Normal file → Executable file
@@ -1,12 +1,12 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
|
||||
from __future__ import with_statement
|
||||
__license__ = 'GPL v3'
|
||||
|
||||
# Standard Python modules.
|
||||
import os, sys, re, hashlib
|
||||
import json
|
||||
import codecs, json
|
||||
import traceback
|
||||
|
||||
from calibre.utils.config import dynamic, config_dir, JSONConfig
|
||||
@@ -15,7 +15,7 @@ from calibre.constants import iswindows, isosx
|
||||
|
||||
class DeDRM_Prefs():
|
||||
def __init__(self):
|
||||
JSON_PATH = os.path.join(u"plugins", PLUGIN_NAME.strip().lower().replace(' ', '_') + '.json')
|
||||
JSON_PATH = os.path.join("plugins", PLUGIN_NAME.strip().lower().replace(' ', '_') + '.json')
|
||||
self.dedrmprefs = JSONConfig(JSON_PATH)
|
||||
|
||||
self.dedrmprefs.defaults['configured'] = False
|
||||
@@ -98,12 +98,12 @@ def convertprefs(always = False):
|
||||
try:
|
||||
name, ccn = keystring.split(',')
|
||||
# Generate Barnes & Noble EPUB user key from name and credit card number.
|
||||
keyname = u"{0}_{1}".format(name.strip(),ccn.strip()[-4:])
|
||||
keyname = "{0}_{1}".format(name.strip(),ccn.strip()[-4:])
|
||||
keyvalue = generate_key(name, ccn)
|
||||
userkeys.append([keyname,keyvalue])
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
print e.args[0]
|
||||
print(e.args[0])
|
||||
pass
|
||||
return userkeys
|
||||
|
||||
@@ -115,12 +115,12 @@ def convertprefs(always = False):
|
||||
try:
|
||||
name, cc = keystring.split(',')
|
||||
# Generate eReader user key from name and credit card number.
|
||||
keyname = u"{0}_{1}".format(name.strip(),cc.strip()[-4:])
|
||||
keyvalue = getuser_key(name,cc).encode('hex')
|
||||
keyname = "{0}_{1}".format(name.strip(),cc.strip()[-4:])
|
||||
keyvalue = codecs.encode(getuser_key(name,cc),'hex')
|
||||
userkeys.append([keyname,keyvalue])
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
print e.args[0]
|
||||
print(e.args[0])
|
||||
pass
|
||||
return userkeys
|
||||
|
||||
@@ -146,7 +146,7 @@ def convertprefs(always = False):
|
||||
key = os.path.splitext(filename)[0]
|
||||
value = open(fpath, 'rb').read()
|
||||
if encoding is not None:
|
||||
value = value.encode(encoding)
|
||||
value = codecs.encode(value,encoding)
|
||||
userkeys.append([key,value])
|
||||
except:
|
||||
traceback.print_exc()
|
||||
@@ -161,15 +161,15 @@ def convertprefs(always = False):
|
||||
return
|
||||
|
||||
|
||||
print u"{0} v{1}: Importing configuration data from old DeDRM plugins".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
print("{0} v{1}: Importing configuration data from old DeDRM plugins".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
|
||||
IGNOBLEPLUGINNAME = "Ignoble Epub DeDRM"
|
||||
EREADERPLUGINNAME = "eReader PDB 2 PML"
|
||||
OLDKINDLEPLUGINNAME = "K4PC, K4Mac, Kindle Mobi and Topaz DeDRM"
|
||||
|
||||
# get prefs from older tools
|
||||
kindleprefs = JSONConfig(os.path.join(u"plugins", u"K4MobiDeDRM"))
|
||||
ignobleprefs = JSONConfig(os.path.join(u"plugins", u"ignoble_epub_dedrm"))
|
||||
kindleprefs = JSONConfig(os.path.join("plugins", "K4MobiDeDRM"))
|
||||
ignobleprefs = JSONConfig(os.path.join("plugins", "ignoble_epub_dedrm"))
|
||||
|
||||
# Handle the old ignoble plugin's customization string by converting the
|
||||
# old string to stored keys... get that personal data out of plain sight.
|
||||
@@ -177,7 +177,7 @@ def convertprefs(always = False):
|
||||
sc = config['plugin_customization']
|
||||
val = sc.pop(IGNOBLEPLUGINNAME, None)
|
||||
if val is not None:
|
||||
print u"{0} v{1}: Converting old Ignoble plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
print("{0} v{1}: Converting old Ignoble plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
priorkeycount = len(dedrmprefs['bandnkeys'])
|
||||
userkeys = parseIgnobleString(str(val))
|
||||
for keypair in userkeys:
|
||||
@@ -185,7 +185,7 @@ def convertprefs(always = False):
|
||||
value = keypair[1]
|
||||
dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
|
||||
addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount
|
||||
print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from old Ignoble plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys")
|
||||
print("{0} v{1}: {2:d} Barnes and Noble {3} imported from old Ignoble plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, "key" if addedkeycount==1 else "keys"))
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
@@ -193,7 +193,7 @@ def convertprefs(always = False):
|
||||
# old string to stored keys... get that personal data out of plain sight.
|
||||
val = sc.pop(EREADERPLUGINNAME, None)
|
||||
if val is not None:
|
||||
print u"{0} v{1}: Converting old eReader plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
print("{0} v{1}: Converting old eReader plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
priorkeycount = len(dedrmprefs['ereaderkeys'])
|
||||
userkeys = parseeReaderString(str(val))
|
||||
for keypair in userkeys:
|
||||
@@ -201,14 +201,14 @@ def convertprefs(always = False):
|
||||
value = keypair[1]
|
||||
dedrmprefs.addnamedvaluetoprefs('ereaderkeys', name, value)
|
||||
addedkeycount = len(dedrmprefs['ereaderkeys'])-priorkeycount
|
||||
print u"{0} v{1}: {2:d} eReader {3} imported from old eReader plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys")
|
||||
print("{0} v{1}: {2:d} eReader {3} imported from old eReader plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, "key" if addedkeycount==1 else "keys"))
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
# get old Kindle plugin configuration string
|
||||
val = sc.pop(OLDKINDLEPLUGINNAME, None)
|
||||
if val is not None:
|
||||
print u"{0} v{1}: Converting old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
print("{0} v{1}: Converting old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
priorpidcount = len(dedrmprefs['pids'])
|
||||
priorserialcount = len(dedrmprefs['serials'])
|
||||
pids, serials = parseKindleString(val)
|
||||
@@ -218,7 +218,7 @@ def convertprefs(always = False):
|
||||
dedrmprefs.addvaluetoprefs('serials',serial)
|
||||
addedpidcount = len(dedrmprefs['pids']) - priorpidcount
|
||||
addedserialcount = len(dedrmprefs['serials']) - priorserialcount
|
||||
print u"{0} v{1}: {2:d} {3} and {4:d} {5} imported from old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, u"PID" if addedpidcount==1 else u"PIDs", addedserialcount, u"serial number" if addedserialcount==1 else u"serial numbers")
|
||||
print("{0} v{1}: {2:d} {3} and {4:d} {5} imported from old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, "PID" if addedpidcount==1 else "PIDs", addedserialcount, "serial number" if addedserialcount==1 else "serial numbers"))
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
@@ -234,7 +234,7 @@ def convertprefs(always = False):
|
||||
dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
|
||||
addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount
|
||||
if addedkeycount > 0:
|
||||
print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key file" if addedkeycount==1 else u"key files")
|
||||
print("{0} v{1}: {2:d} Barnes and Noble {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, "key file" if addedkeycount==1 else "key files"))
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
@@ -247,7 +247,7 @@ def convertprefs(always = False):
|
||||
dedrmprefs.addnamedvaluetoprefs('adeptkeys', name, value)
|
||||
addedkeycount = len(dedrmprefs['adeptkeys'])-priorkeycount
|
||||
if addedkeycount > 0:
|
||||
print u"{0} v{1}: {2:d} Adobe Adept {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"keyfile" if addedkeycount==1 else u"keyfiles")
|
||||
print("{0} v{1}: {2:d} Adobe Adept {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, "keyfile" if addedkeycount==1 else "keyfiles"))
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
@@ -260,7 +260,7 @@ def convertprefs(always = False):
|
||||
addedkeycount = len(dedrmprefs['bandnkeys']) - priorkeycount
|
||||
# no need to delete old prefs, since they contain no recoverable private data
|
||||
if addedkeycount > 0:
|
||||
print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from Ignoble plugin preferences.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys")
|
||||
print("{0} v{1}: {2:d} Barnes and Noble {3} imported from Ignoble plugin preferences.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, "key" if addedkeycount==1 else "keys"))
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
@@ -277,19 +277,19 @@ def convertprefs(always = False):
|
||||
dedrmprefs.addvaluetoprefs('serials',serial)
|
||||
addedpidcount = len(dedrmprefs['pids']) - priorpidcount
|
||||
if addedpidcount > 0:
|
||||
print u"{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, u"PID" if addedpidcount==1 else u"PIDs")
|
||||
print("{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, "PID" if addedpidcount==1 else "PIDs"))
|
||||
addedserialcount = len(dedrmprefs['serials']) - priorserialcount
|
||||
if addedserialcount > 0:
|
||||
print u"{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedserialcount, u"serial number" if addedserialcount==1 else u"serial numbers")
|
||||
print("{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedserialcount, "serial number" if addedserialcount==1 else "serial numbers"))
|
||||
try:
|
||||
if 'wineprefix' in kindleprefs and kindleprefs['wineprefix'] != "":
|
||||
dedrmprefs.set('adobewineprefix',kindleprefs['wineprefix'])
|
||||
dedrmprefs.set('kindlewineprefix',kindleprefs['wineprefix'])
|
||||
print u"{0} v{1}: WINEPREFIX ‘(2)’ imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, kindleprefs['wineprefix'])
|
||||
print("{0} v{1}: WINEPREFIX ‘(2)’ imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, kindleprefs['wineprefix']))
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs()
|
||||
print u"{0} v{1}: Finished setting up configuration data.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
print("{0} v{1}: Finished setting up configuration data.".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import ineptepub
|
||||
import ignobleepub
|
||||
import epubtest
|
||||
import zipfix
|
||||
import ineptpdf
|
||||
import erdr2pml
|
||||
import k4mobidedrm
|
||||
import traceback
|
||||
import calibre_plugins.dedrm.ineptepub
|
||||
import calibre_plugins.dedrm.ignobleepub
|
||||
import calibre_plugins.dedrm.epubtest
|
||||
import calibre_plugins.dedrm.zipfix
|
||||
import calibre_plugins.dedrm.ineptpdf
|
||||
import calibre_plugins.dedrm.erdr2pml
|
||||
import calibre_plugins.dedrm.k4mobidedrm
|
||||
|
||||
def decryptepub(infile, outdir, rscpath):
|
||||
errlog = ''
|
||||
@@ -46,7 +46,7 @@ def decryptepub(infile, outdir, rscpath):
|
||||
if rv == 0:
|
||||
print("Decrypted Adobe ePub with key file {0}".format(filename))
|
||||
break
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
errlog += traceback.format_exc()
|
||||
errlog += str(e)
|
||||
rv = 1
|
||||
@@ -66,7 +66,7 @@ def decryptepub(infile, outdir, rscpath):
|
||||
if rv == 0:
|
||||
print("Decrypted B&N ePub with key file {0}".format(filename))
|
||||
break
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
errlog += traceback.format_exc()
|
||||
errlog += str(e)
|
||||
rv = 1
|
||||
@@ -104,7 +104,7 @@ def decryptpdf(infile, outdir, rscpath):
|
||||
rv = ineptpdf.decryptBook(userkey, infile, outfile)
|
||||
if rv == 0:
|
||||
break
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
errlog += traceback.format_exc()
|
||||
errlog += str(e)
|
||||
rv = 1
|
||||
@@ -132,7 +132,7 @@ def decryptpdb(infile, outdir, rscpath):
|
||||
return 1
|
||||
try:
|
||||
rv = erdr2pml.decryptBook(infile, outpath, True, erdr2pml.getuser_key(name, cc8))
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
errlog += traceback.format_exc()
|
||||
errlog += str(e)
|
||||
rv = 1
|
||||
@@ -193,7 +193,7 @@ def decryptk4mobi(infile, outdir, rscpath):
|
||||
androidFiles.append(dpath)
|
||||
try:
|
||||
rv = k4mobidedrm.decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serialnums, pidnums)
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
errlog += traceback.format_exc()
|
||||
errlog += str(e)
|
||||
rv = 1
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkinter
|
||||
import tkinter.constants
|
||||
|
||||
# basic scrolled text widget
|
||||
class ScrolledText(Tkinter.Text):
|
||||
class ScrolledText(tkinter.Text):
|
||||
def __init__(self, master=None, **kw):
|
||||
self.frame = Tkinter.Frame(master)
|
||||
self.vbar = Tkinter.Scrollbar(self.frame)
|
||||
self.vbar.pack(side=Tkconstants.RIGHT, fill=Tkconstants.Y)
|
||||
self.frame = tkinter.Frame(master)
|
||||
self.vbar = tkinter.Scrollbar(self.frame)
|
||||
self.vbar.pack(side=tkinter.constants.RIGHT, fill=tkinter.constants.Y)
|
||||
kw.update({'yscrollcommand': self.vbar.set})
|
||||
Tkinter.Text.__init__(self, self.frame, **kw)
|
||||
self.pack(side=Tkconstants.LEFT, fill=Tkconstants.BOTH, expand=True)
|
||||
tkinter.Text.__init__(self, self.frame, **kw)
|
||||
self.pack(side=tkinter.constants.LEFT, fill=tkinter.constants.BOTH, expand=True)
|
||||
self.vbar['command'] = self.yview
|
||||
# Copy geometry methods of self.frame without overriding Text
|
||||
# methods = hack!
|
||||
text_meths = vars(Tkinter.Text).keys()
|
||||
methods = vars(Tkinter.Pack).keys() + vars(Tkinter.Grid).keys() + vars(Tkinter.Place).keys()
|
||||
text_meths = list(vars(tkinter.Text).keys())
|
||||
methods = list(vars(tkinter.Pack).keys()) + list(vars(tkinter.Grid).keys()) + list(vars(tkinter.Place).keys())
|
||||
methods = set(methods).difference(text_meths)
|
||||
for m in methods:
|
||||
if m[0] != '_' and m != 'config' and m != 'configure':
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||
|
||||
import sys
|
||||
@@ -19,7 +20,7 @@ class SimplePrefs(object):
|
||||
self.file2key[filename] = key
|
||||
self.target = target + 'Prefs'
|
||||
if sys.platform.startswith('win'):
|
||||
import _winreg as winreg
|
||||
import winreg
|
||||
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
|
||||
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
|
||||
prefdir = path + os.sep + self.target
|
||||
@@ -46,7 +47,7 @@ class SimplePrefs(object):
|
||||
try :
|
||||
data = file(filepath,'rb').read()
|
||||
self.prefs[key] = data
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
def getPreferences(self):
|
||||
@@ -71,7 +72,7 @@ class SimplePrefs(object):
|
||||
else:
|
||||
try:
|
||||
file(filepath,'wb').write(data)
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
pass
|
||||
self.prefs = newprefs
|
||||
return
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||
# For use with Topaz Scripts Version 2.6
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import csv
|
||||
import sys
|
||||
import os
|
||||
@@ -15,36 +15,36 @@ debug = False
|
||||
|
||||
class DocParser(object):
|
||||
def __init__(self, flatxml, fontsize, ph, pw):
|
||||
self.flatdoc = flatxml.split('\n')
|
||||
self.flatdoc = flatxml.split(b'\n')
|
||||
self.fontsize = int(fontsize)
|
||||
self.ph = int(ph) * 1.0
|
||||
self.pw = int(pw) * 1.0
|
||||
|
||||
stags = {
|
||||
'paragraph' : 'p',
|
||||
'graphic' : '.graphic'
|
||||
b'paragraph' : 'p',
|
||||
b'graphic' : '.graphic'
|
||||
}
|
||||
|
||||
attr_val_map = {
|
||||
'hang' : 'text-indent: ',
|
||||
'indent' : 'text-indent: ',
|
||||
'line-space' : 'line-height: ',
|
||||
'margin-bottom' : 'margin-bottom: ',
|
||||
'margin-left' : 'margin-left: ',
|
||||
'margin-right' : 'margin-right: ',
|
||||
'margin-top' : 'margin-top: ',
|
||||
'space-after' : 'padding-bottom: ',
|
||||
b'hang' : 'text-indent: ',
|
||||
b'indent' : 'text-indent: ',
|
||||
b'line-space' : 'line-height: ',
|
||||
b'margin-bottom' : 'margin-bottom: ',
|
||||
b'margin-left' : 'margin-left: ',
|
||||
b'margin-right' : 'margin-right: ',
|
||||
b'margin-top' : 'margin-top: ',
|
||||
b'space-after' : 'padding-bottom: ',
|
||||
}
|
||||
|
||||
attr_str_map = {
|
||||
'align-center' : 'text-align: center; margin-left: auto; margin-right: auto;',
|
||||
'align-left' : 'text-align: left;',
|
||||
'align-right' : 'text-align: right;',
|
||||
'align-justify' : 'text-align: justify;',
|
||||
'display-inline' : 'display: inline;',
|
||||
'pos-left' : 'text-align: left;',
|
||||
'pos-right' : 'text-align: right;',
|
||||
'pos-center' : 'text-align: center; margin-left: auto; margin-right: auto;',
|
||||
b'align-center' : 'text-align: center; margin-left: auto; margin-right: auto;',
|
||||
b'align-left' : 'text-align: left;',
|
||||
b'align-right' : 'text-align: right;',
|
||||
b'align-justify' : 'text-align: justify;',
|
||||
b'display-inline' : 'display: inline;',
|
||||
b'pos-left' : 'text-align: left;',
|
||||
b'pos-right' : 'text-align: right;',
|
||||
b'pos-center' : 'text-align: center; margin-left: auto; margin-right: auto;',
|
||||
}
|
||||
|
||||
|
||||
@@ -58,13 +58,15 @@ class DocParser(object):
|
||||
else:
|
||||
end = min(cnt,end)
|
||||
foundat = -1
|
||||
for j in xrange(pos, end):
|
||||
for j in range(pos, end):
|
||||
item = docList[j]
|
||||
if item.find('=') >= 0:
|
||||
(name, argres) = item.split('=',1)
|
||||
if item.find(b'=') >= 0:
|
||||
(name, argres) = item.split(b'=',1)
|
||||
else :
|
||||
name = item
|
||||
argres = ''
|
||||
argres = b''
|
||||
if (isinstance(tagpath,str)):
|
||||
tagpath = tagpath.encode('utf-8')
|
||||
if name.endswith(tagpath) :
|
||||
result = argres
|
||||
foundat = j
|
||||
@@ -76,7 +78,7 @@ class DocParser(object):
|
||||
def posinDoc(self, tagpath):
|
||||
startpos = []
|
||||
pos = 0
|
||||
res = ""
|
||||
res = b""
|
||||
while res != None :
|
||||
(foundpos, res) = self.findinDoc(tagpath, pos, -1)
|
||||
if res != None :
|
||||
@@ -87,11 +89,11 @@ class DocParser(object):
|
||||
# returns a vector of integers for the tagpath
|
||||
def getData(self, tagpath, pos, end, clean=False):
|
||||
if clean:
|
||||
digits_only = re.compile(r'''([0-9]+)''')
|
||||
digits_only = re.compile(rb'''([0-9]+)''')
|
||||
argres=[]
|
||||
(foundat, argt) = self.findinDoc(tagpath, pos, end)
|
||||
if (argt != None) and (len(argt) > 0) :
|
||||
argList = argt.split('|')
|
||||
argList = argt.split(b'|')
|
||||
for strval in argList:
|
||||
if clean:
|
||||
m = re.search(digits_only, strval)
|
||||
@@ -109,42 +111,42 @@ class DocParser(object):
|
||||
csspage += '.cl-justify { text-align: justify; }\n'
|
||||
|
||||
# generate a list of each <style> starting point in the stylesheet
|
||||
styleList= self.posinDoc('book.stylesheet.style')
|
||||
styleList= self.posinDoc(b'book.stylesheet.style')
|
||||
stylecnt = len(styleList)
|
||||
styleList.append(-1)
|
||||
|
||||
# process each style converting what you can
|
||||
|
||||
if debug: print(' ', 'Processing styles.')
|
||||
for j in xrange(stylecnt):
|
||||
for j in range(stylecnt):
|
||||
if debug: print(' ', 'Processing style %d' %(j))
|
||||
start = styleList[j]
|
||||
end = styleList[j+1]
|
||||
|
||||
(pos, tag) = self.findinDoc('style._tag',start,end)
|
||||
(pos, tag) = self.findinDoc(b'style._tag',start,end)
|
||||
if tag == None :
|
||||
(pos, tag) = self.findinDoc('style.type',start,end)
|
||||
(pos, tag) = self.findinDoc(b'style.type',start,end)
|
||||
|
||||
# Is this something we know how to convert to css
|
||||
if tag in self.stags :
|
||||
|
||||
# get the style class
|
||||
(pos, sclass) = self.findinDoc('style.class',start,end)
|
||||
(pos, sclass) = self.findinDoc(b'style.class',start,end)
|
||||
if sclass != None:
|
||||
sclass = sclass.replace(' ','-')
|
||||
sclass = '.cl-' + sclass.lower()
|
||||
sclass = sclass.replace(b' ',b'-')
|
||||
sclass = b'.cl-' + sclass.lower()
|
||||
else :
|
||||
sclass = ''
|
||||
sclass = b''
|
||||
|
||||
if debug: print('sclass', sclass)
|
||||
|
||||
# check for any "after class" specifiers
|
||||
(pos, aftclass) = self.findinDoc('style._after_class',start,end)
|
||||
(pos, aftclass) = self.findinDoc(b'style._after_class',start,end)
|
||||
if aftclass != None:
|
||||
aftclass = aftclass.replace(' ','-')
|
||||
aftclass = '.cl-' + aftclass.lower()
|
||||
aftclass = aftclass.replace(b' ',b'-')
|
||||
aftclass = b'.cl-' + aftclass.lower()
|
||||
else :
|
||||
aftclass = ''
|
||||
aftclass = b''
|
||||
|
||||
if debug: print('aftclass', aftclass)
|
||||
|
||||
@@ -152,34 +154,37 @@ class DocParser(object):
|
||||
|
||||
while True :
|
||||
|
||||
(pos1, attr) = self.findinDoc('style.rule.attr', start, end)
|
||||
(pos2, val) = self.findinDoc('style.rule.value', start, end)
|
||||
(pos1, attr) = self.findinDoc(b'style.rule.attr', start, end)
|
||||
(pos2, val) = self.findinDoc(b'style.rule.value', start, end)
|
||||
|
||||
if debug: print('attr', attr)
|
||||
if debug: print('val', val)
|
||||
|
||||
if attr == None : break
|
||||
|
||||
if (attr == 'display') or (attr == 'pos') or (attr == 'align'):
|
||||
if (attr == b'display') or (attr == b'pos') or (attr == b'align'):
|
||||
# handle text based attributess
|
||||
attr = attr + '-' + val
|
||||
attr = attr + b'-' + val
|
||||
if attr in self.attr_str_map :
|
||||
cssargs[attr] = (self.attr_str_map[attr], '')
|
||||
cssargs[attr] = (self.attr_str_map[attr], b'')
|
||||
else :
|
||||
# handle value based attributes
|
||||
if attr in self.attr_val_map :
|
||||
name = self.attr_val_map[attr]
|
||||
if attr in ('margin-bottom', 'margin-top', 'space-after') :
|
||||
if attr in (b'margin-bottom', b'margin-top', b'space-after') :
|
||||
scale = self.ph
|
||||
elif attr in ('margin-right', 'indent', 'margin-left', 'hang') :
|
||||
elif attr in (b'margin-right', b'indent', b'margin-left', b'hang') :
|
||||
scale = self.pw
|
||||
elif attr == 'line-space':
|
||||
elif attr == b'line-space':
|
||||
scale = self.fontsize * 2.0
|
||||
else:
|
||||
print("Scale not defined!")
|
||||
scale = 1.0
|
||||
|
||||
if val == "":
|
||||
val = 0
|
||||
|
||||
if not ((attr == 'hang') and (int(val) == 0)):
|
||||
if not ((attr == b'hang') and (int(val) == 0)):
|
||||
try:
|
||||
f = float(val)
|
||||
except:
|
||||
@@ -198,32 +203,32 @@ class DocParser(object):
|
||||
if debug: print('keeping style')
|
||||
# make sure line-space does not go below 100% or above 300% since
|
||||
# it can be wacky in some styles
|
||||
if 'line-space' in cssargs:
|
||||
seg = cssargs['line-space'][0]
|
||||
val = cssargs['line-space'][1]
|
||||
if b'line-space' in cssargs:
|
||||
seg = cssargs[b'line-space'][0]
|
||||
val = cssargs[b'line-space'][1]
|
||||
if val < 1.0: val = 1.0
|
||||
if val > 3.0: val = 3.0
|
||||
del cssargs['line-space']
|
||||
cssargs['line-space'] = (self.attr_val_map['line-space'], val)
|
||||
del cssargs[b'line-space']
|
||||
cssargs[b'line-space'] = (self.attr_val_map[b'line-space'], val)
|
||||
|
||||
|
||||
# handle modifications for css style hanging indents
|
||||
if 'hang' in cssargs:
|
||||
hseg = cssargs['hang'][0]
|
||||
hval = cssargs['hang'][1]
|
||||
del cssargs['hang']
|
||||
cssargs['hang'] = (self.attr_val_map['hang'], -hval)
|
||||
if b'hang' in cssargs:
|
||||
hseg = cssargs[b'hang'][0]
|
||||
hval = cssargs[b'hang'][1]
|
||||
del cssargs[b'hang']
|
||||
cssargs[b'hang'] = (self.attr_val_map[b'hang'], -hval)
|
||||
mval = 0
|
||||
mseg = 'margin-left: '
|
||||
mval = hval
|
||||
if 'margin-left' in cssargs:
|
||||
mseg = cssargs['margin-left'][0]
|
||||
mval = cssargs['margin-left'][1]
|
||||
if b'margin-left' in cssargs:
|
||||
mseg = cssargs[b'margin-left'][0]
|
||||
mval = cssargs[b'margin-left'][1]
|
||||
if mval < 0: mval = 0
|
||||
mval = hval + mval
|
||||
cssargs['margin-left'] = (mseg, mval)
|
||||
if 'indent' in cssargs:
|
||||
del cssargs['indent']
|
||||
cssargs[b'margin-left'] = (mseg, mval)
|
||||
if b'indent' in cssargs:
|
||||
del cssargs[b'indent']
|
||||
|
||||
cssline = sclass + ' { '
|
||||
for key in iter(cssargs):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# topazextract.py
|
||||
@@ -7,9 +7,9 @@
|
||||
# Changelog
|
||||
# 4.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 5.0 - Fixed potential unicode problem with command line interface
|
||||
# 6.0 - Added Python 3 compatibility for calibre 5.0
|
||||
|
||||
from __future__ import print_function
|
||||
__version__ = '5.0'
|
||||
__version__ = '6.0'
|
||||
|
||||
import sys
|
||||
import os, csv, getopt
|
||||
@@ -17,8 +17,14 @@ import zlib, zipfile, tempfile, shutil
|
||||
import traceback
|
||||
from struct import pack
|
||||
from struct import unpack
|
||||
from alfcrypto import Topaz_Cipher
|
||||
try:
|
||||
from calibre_plugins.dedrm.alfcrypto import Topaz_Cipher
|
||||
except:
|
||||
from alfcrypto import Topaz_Cipher
|
||||
|
||||
# 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
|
||||
@@ -26,10 +32,11 @@ class SafeUnbuffered:
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
if isinstance(data, str):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
self.stream.buffer.write(data)
|
||||
self.stream.buffer.flush()
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
@@ -64,15 +71,13 @@ def unicode_argv():
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
range(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"mobidedrm.py"]
|
||||
return ["mobidedrm.py"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = 'utf-8'
|
||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||
argvencoding = sys.stdin.encoding or "utf-8"
|
||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
||||
|
||||
#global switch
|
||||
debug = False
|
||||
@@ -92,7 +97,7 @@ class DrmException(Exception):
|
||||
# recursive zip creation support routine
|
||||
def zipUpDir(myzip, tdir, localname):
|
||||
currentdir = tdir
|
||||
if localname != u"":
|
||||
if localname != "":
|
||||
currentdir = os.path.join(currentdir,localname)
|
||||
list = os.listdir(currentdir)
|
||||
for file in list:
|
||||
@@ -168,21 +173,21 @@ def decryptRecord(data,PID):
|
||||
def decryptDkeyRecord(data,PID):
|
||||
record = decryptRecord(data,PID)
|
||||
fields = unpack('3sB8sB8s3s',record)
|
||||
if fields[0] != 'PID' or fields[5] != 'pid' :
|
||||
raise DrmException(u"Didn't find PID magic numbers in record")
|
||||
if fields[0] != b'PID' or fields[5] != b'pid' :
|
||||
raise DrmException("Didn't find PID magic numbers in record")
|
||||
elif fields[1] != 8 or fields[3] != 8 :
|
||||
raise DrmException(u"Record didn't contain correct length fields")
|
||||
raise DrmException("Record didn't contain correct length fields")
|
||||
elif fields[2] != PID :
|
||||
raise DrmException(u"Record didn't contain PID")
|
||||
raise DrmException("Record didn't contain PID")
|
||||
return fields[4]
|
||||
|
||||
# Decrypt all dkey records (contain the book PID)
|
||||
def decryptDkeyRecords(data,PID):
|
||||
nbKeyRecords = ord(data[0])
|
||||
nbKeyRecords = data[0]
|
||||
records = []
|
||||
data = data[1:]
|
||||
for i in range (0,nbKeyRecords):
|
||||
length = ord(data[0])
|
||||
length = data[0]
|
||||
try:
|
||||
key = decryptDkeyRecord(data[1:length+1],PID)
|
||||
records.append(key)
|
||||
@@ -190,13 +195,13 @@ def decryptDkeyRecords(data,PID):
|
||||
pass
|
||||
data = data[1+length:]
|
||||
if len(records) == 0:
|
||||
raise DrmException(u"BookKey Not Found")
|
||||
raise DrmException("BookKey Not Found")
|
||||
return records
|
||||
|
||||
|
||||
class TopazBook:
|
||||
def __init__(self, filename):
|
||||
self.fo = file(filename, 'rb')
|
||||
self.fo = open(filename, 'rb')
|
||||
self.outdir = tempfile.mkdtemp()
|
||||
# self.outdir = 'rawdat'
|
||||
self.bookPayloadOffset = 0
|
||||
@@ -204,8 +209,8 @@ class TopazBook:
|
||||
self.bookMetadata = {}
|
||||
self.bookKey = None
|
||||
magic = unpack('4s',self.fo.read(4))[0]
|
||||
if magic != 'TPZ0':
|
||||
raise DrmException(u"Parse Error : Invalid Header, not a Topaz file")
|
||||
if magic != b'TPZ0':
|
||||
raise DrmException("Parse Error : Invalid Header, not a Topaz file")
|
||||
self.parseTopazHeaders()
|
||||
self.parseMetadata()
|
||||
|
||||
@@ -223,7 +228,7 @@ class TopazBook:
|
||||
# Read and parse one header record at the current book file position and return the associated data
|
||||
# [[offset,decompressedLength,compressedLength],...]
|
||||
if ord(self.fo.read(1)) != 0x63:
|
||||
raise DrmException(u"Parse Error : Invalid Header")
|
||||
raise DrmException("Parse Error : Invalid Header")
|
||||
tag = bookReadString(self.fo)
|
||||
record = bookReadHeaderRecordData()
|
||||
return [tag,record]
|
||||
@@ -234,15 +239,15 @@ class TopazBook:
|
||||
if debug: print(result[0], ": ", result[1])
|
||||
self.bookHeaderRecords[result[0]] = result[1]
|
||||
if ord(self.fo.read(1)) != 0x64 :
|
||||
raise DrmException(u"Parse Error : Invalid Header")
|
||||
raise DrmException("Parse Error : Invalid Header")
|
||||
self.bookPayloadOffset = self.fo.tell()
|
||||
|
||||
def parseMetadata(self):
|
||||
# Parse the metadata record from the book payload and return a list of [key,values]
|
||||
self.fo.seek(self.bookPayloadOffset + self.bookHeaderRecords['metadata'][0][0])
|
||||
self.fo.seek(self.bookPayloadOffset + self.bookHeaderRecords[b'metadata'][0][0])
|
||||
tag = bookReadString(self.fo)
|
||||
if tag != 'metadata' :
|
||||
raise DrmException(u"Parse Error : Record Names Don't Match")
|
||||
if tag != b'metadata' :
|
||||
raise DrmException("Parse Error : Record Names Don't Match")
|
||||
flags = ord(self.fo.read(1))
|
||||
nbRecords = ord(self.fo.read(1))
|
||||
if debug: print("Metadata Records: %d" % nbRecords)
|
||||
@@ -255,18 +260,18 @@ class TopazBook:
|
||||
return self.bookMetadata
|
||||
|
||||
def getPIDMetaInfo(self):
|
||||
keysRecord = self.bookMetadata.get('keys','')
|
||||
keysRecordRecord = ''
|
||||
if keysRecord != '':
|
||||
keylst = keysRecord.split(',')
|
||||
keysRecord = self.bookMetadata.get(b'keys',b'')
|
||||
keysRecordRecord = b''
|
||||
if keysRecord != b'':
|
||||
keylst = keysRecord.split(b',')
|
||||
for keyval in keylst:
|
||||
keysRecordRecord += self.bookMetadata.get(keyval,'')
|
||||
keysRecordRecord += self.bookMetadata.get(keyval,b'')
|
||||
return keysRecord, keysRecordRecord
|
||||
|
||||
def getBookTitle(self):
|
||||
title = ''
|
||||
if 'Title' in self.bookMetadata:
|
||||
title = self.bookMetadata['Title']
|
||||
title = b''
|
||||
if b'Title' in self.bookMetadata:
|
||||
title = self.bookMetadata[b'Title']
|
||||
return title.decode('utf-8')
|
||||
|
||||
def setBookKey(self, key):
|
||||
@@ -318,13 +323,13 @@ class TopazBook:
|
||||
raw = 0
|
||||
fixedimage=True
|
||||
try:
|
||||
keydata = self.getBookPayloadRecord('dkey', 0)
|
||||
except DrmException, e:
|
||||
print(u"no dkey record found, book may not be encrypted")
|
||||
print(u"attempting to extrct files without a book key")
|
||||
keydata = self.getBookPayloadRecord(b'dkey', 0)
|
||||
except DrmException as e:
|
||||
print("no dkey record found, book may not be encrypted")
|
||||
print("attempting to extrct files without a book key")
|
||||
self.createBookDirectory()
|
||||
self.extractFiles()
|
||||
print(u"Successfully Extracted Topaz contents")
|
||||
print("Successfully Extracted Topaz contents")
|
||||
if inCalibre:
|
||||
from calibre_plugins.dedrm import genbook
|
||||
else:
|
||||
@@ -332,7 +337,7 @@ class TopazBook:
|
||||
|
||||
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
||||
if rv == 0:
|
||||
print(u"Book Successfully generated.")
|
||||
print("Book Successfully generated.")
|
||||
return rv
|
||||
|
||||
# try each pid to decode the file
|
||||
@@ -340,25 +345,25 @@ class TopazBook:
|
||||
for pid in pidlst:
|
||||
# use 8 digit pids here
|
||||
pid = pid[0:8]
|
||||
print(u"Trying: {0}".format(pid))
|
||||
print("Trying: {0}".format(pid))
|
||||
bookKeys = []
|
||||
data = keydata
|
||||
try:
|
||||
bookKeys+=decryptDkeyRecords(data,pid)
|
||||
except DrmException, e:
|
||||
except DrmException as e:
|
||||
pass
|
||||
else:
|
||||
bookKey = bookKeys[0]
|
||||
print(u"Book Key Found! ({0})".format(bookKey.encode('hex')))
|
||||
print("Book Key Found! ({0})".format(bookKey.hex()))
|
||||
break
|
||||
|
||||
if not bookKey:
|
||||
raise DrmException(u"No key found in {0:d} keys tried. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(len(pidlst)))
|
||||
raise DrmException("No key found in {0:d} keys tried. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(len(pidlst)))
|
||||
|
||||
self.setBookKey(bookKey)
|
||||
self.createBookDirectory()
|
||||
self.extractFiles()
|
||||
print(u"Successfully Extracted Topaz contents")
|
||||
self.extractFiles()
|
||||
print("Successfully Extracted Topaz contents")
|
||||
if inCalibre:
|
||||
from calibre_plugins.dedrm import genbook
|
||||
else:
|
||||
@@ -366,7 +371,7 @@ class TopazBook:
|
||||
|
||||
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
||||
if rv == 0:
|
||||
print(u"Book Successfully generated")
|
||||
print("Book Successfully generated")
|
||||
return rv
|
||||
|
||||
def createBookDirectory(self):
|
||||
@@ -374,16 +379,16 @@ class TopazBook:
|
||||
# create output directory structure
|
||||
if not os.path.exists(outdir):
|
||||
os.makedirs(outdir)
|
||||
destdir = os.path.join(outdir,u"img")
|
||||
destdir = os.path.join(outdir,"img")
|
||||
if not os.path.exists(destdir):
|
||||
os.makedirs(destdir)
|
||||
destdir = os.path.join(outdir,u"color_img")
|
||||
destdir = os.path.join(outdir,"color_img")
|
||||
if not os.path.exists(destdir):
|
||||
os.makedirs(destdir)
|
||||
destdir = os.path.join(outdir,u"page")
|
||||
destdir = os.path.join(outdir,"page")
|
||||
if not os.path.exists(destdir):
|
||||
os.makedirs(destdir)
|
||||
destdir = os.path.join(outdir,u"glyphs")
|
||||
destdir = os.path.join(outdir,"glyphs")
|
||||
if not os.path.exists(destdir):
|
||||
os.makedirs(destdir)
|
||||
|
||||
@@ -391,50 +396,50 @@ class TopazBook:
|
||||
outdir = self.outdir
|
||||
for headerRecord in self.bookHeaderRecords:
|
||||
name = headerRecord
|
||||
if name != 'dkey':
|
||||
ext = u".dat"
|
||||
if name == 'img': ext = u".jpg"
|
||||
if name == 'color' : ext = u".jpg"
|
||||
print(u"Processing Section: {0}\n. . .".format(name), end=' ')
|
||||
if name != b'dkey':
|
||||
ext = ".dat"
|
||||
if name == b'img': ext = ".jpg"
|
||||
if name == b'color' : ext = ".jpg"
|
||||
print("Processing Section: {0}\n. . .".format(name.decode('utf-8')), end=' ')
|
||||
for index in range (0,len(self.bookHeaderRecords[name])) :
|
||||
fname = u"{0}{1:04d}{2}".format(name,index,ext)
|
||||
fname = "{0}{1:04d}{2}".format(name.decode('utf-8'),index,ext)
|
||||
destdir = outdir
|
||||
if name == 'img':
|
||||
destdir = os.path.join(outdir,u"img")
|
||||
if name == 'color':
|
||||
destdir = os.path.join(outdir,u"color_img")
|
||||
if name == 'page':
|
||||
destdir = os.path.join(outdir,u"page")
|
||||
if name == 'glyphs':
|
||||
destdir = os.path.join(outdir,u"glyphs")
|
||||
if name == b'img':
|
||||
destdir = os.path.join(outdir,"img")
|
||||
if name == b'color':
|
||||
destdir = os.path.join(outdir,"color_img")
|
||||
if name == b'page':
|
||||
destdir = os.path.join(outdir,"page")
|
||||
if name == b'glyphs':
|
||||
destdir = os.path.join(outdir,"glyphs")
|
||||
outputFile = os.path.join(destdir,fname)
|
||||
print(u".", end=' ')
|
||||
print(".", end=' ')
|
||||
record = self.getBookPayloadRecord(name,index)
|
||||
if record != '':
|
||||
file(outputFile, 'wb').write(record)
|
||||
print(u" ")
|
||||
if record != b'':
|
||||
open(outputFile, 'wb').write(record)
|
||||
print(" ")
|
||||
|
||||
def getFile(self, zipname):
|
||||
htmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
|
||||
htmlzip.write(os.path.join(self.outdir,u"book.html"),u"book.html")
|
||||
htmlzip.write(os.path.join(self.outdir,u"book.opf"),u"book.opf")
|
||||
if os.path.isfile(os.path.join(self.outdir,u"cover.jpg")):
|
||||
htmlzip.write(os.path.join(self.outdir,u"cover.jpg"),u"cover.jpg")
|
||||
htmlzip.write(os.path.join(self.outdir,u"style.css"),u"style.css")
|
||||
zipUpDir(htmlzip, self.outdir, u"img")
|
||||
htmlzip.write(os.path.join(self.outdir,"book.html"),"book.html")
|
||||
htmlzip.write(os.path.join(self.outdir,"book.opf"),"book.opf")
|
||||
if os.path.isfile(os.path.join(self.outdir,"cover.jpg")):
|
||||
htmlzip.write(os.path.join(self.outdir,"cover.jpg"),"cover.jpg")
|
||||
htmlzip.write(os.path.join(self.outdir,"style.css"),"style.css")
|
||||
zipUpDir(htmlzip, self.outdir, "img")
|
||||
htmlzip.close()
|
||||
|
||||
def getBookType(self):
|
||||
return u"Topaz"
|
||||
return "Topaz"
|
||||
|
||||
def getBookExtension(self):
|
||||
return u".htmlz"
|
||||
return ".htmlz"
|
||||
|
||||
def getSVGZip(self, zipname):
|
||||
svgzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
|
||||
svgzip.write(os.path.join(self.outdir,u"index_svg.xhtml"),u"index_svg.xhtml")
|
||||
zipUpDir(svgzip, self.outdir, u"svg")
|
||||
zipUpDir(svgzip, self.outdir, u"img")
|
||||
svgzip.write(os.path.join(self.outdir,"index_svg.xhtml"),"index_svg.xhtml")
|
||||
zipUpDir(svgzip, self.outdir, "svg")
|
||||
zipUpDir(svgzip, self.outdir, "img")
|
||||
svgzip.close()
|
||||
|
||||
def cleanup(self):
|
||||
@@ -442,20 +447,20 @@ class TopazBook:
|
||||
shutil.rmtree(self.outdir, True)
|
||||
|
||||
def usage(progname):
|
||||
print(u"Removes DRM protection from Topaz ebooks and extracts the contents")
|
||||
print(u"Usage:")
|
||||
print(u" {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] <infile> <outdir>".format(progname))
|
||||
print("Removes DRM protection from Topaz ebooks and extracts the contents")
|
||||
print("Usage:")
|
||||
print(" {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] <infile> <outdir>".format(progname))
|
||||
|
||||
# Main
|
||||
def cli_main():
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print(u"TopazExtract v{0}.".format(__version__))
|
||||
print("TopazExtract v{0}.".format(__version__))
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], "k:p:s:x")
|
||||
except getopt.GetoptError, err:
|
||||
print(u"Error in options or arguments: {0}".format(err.args[0]))
|
||||
except getopt.GetoptError as err:
|
||||
print("Error in options or arguments: {0}".format(err.args[0]))
|
||||
usage(progname)
|
||||
return 1
|
||||
if len(args)<2:
|
||||
@@ -465,11 +470,11 @@ def cli_main():
|
||||
infile = args[0]
|
||||
outdir = args[1]
|
||||
if not os.path.isfile(infile):
|
||||
print(u"Input File {0} Does Not Exist.".format(infile))
|
||||
print("Input File {0} Does Not Exist.".format(infile))
|
||||
return 1
|
||||
|
||||
if not os.path.exists(outdir):
|
||||
print(u"Output Directory {0} Does Not Exist.".format(outdir))
|
||||
print("Output Directory {0} Does Not Exist.".format(outdir))
|
||||
return 1
|
||||
|
||||
kDatabaseFiles = []
|
||||
@@ -494,27 +499,27 @@ def cli_main():
|
||||
|
||||
tb = TopazBook(infile)
|
||||
title = tb.getBookTitle()
|
||||
print(u"Processing Book: {0}".format(title))
|
||||
print("Processing Book: {0}".format(title))
|
||||
md1, md2 = tb.getPIDMetaInfo()
|
||||
pids.extend(kgenpids.getPidList(md1, md2, serials, kDatabaseFiles))
|
||||
|
||||
try:
|
||||
print(u"Decrypting Book")
|
||||
print("Decrypting Book")
|
||||
tb.processBook(pids)
|
||||
|
||||
print(u" Creating HTML ZIP Archive")
|
||||
zipname = os.path.join(outdir, bookname + u"_nodrm.htmlz")
|
||||
print(" Creating HTML ZIP Archive")
|
||||
zipname = os.path.join(outdir, bookname + "_nodrm.htmlz")
|
||||
tb.getFile(zipname)
|
||||
|
||||
print(u" Creating SVG ZIP Archive")
|
||||
zipname = os.path.join(outdir, bookname + u"_SVG.zip")
|
||||
print(" Creating SVG ZIP Archive")
|
||||
zipname = os.path.join(outdir, bookname + "_SVG.zip")
|
||||
tb.getSVGZip(zipname)
|
||||
|
||||
# removing internal temporary directory of pieces
|
||||
tb.cleanup()
|
||||
|
||||
except DrmException, e:
|
||||
print(u"Decryption failed\n{0}".format(traceback.format_exc()))
|
||||
except DrmException as e:
|
||||
print("Decryption failed\n{0}".format(traceback.format_exc()))
|
||||
|
||||
try:
|
||||
tb.cleanup()
|
||||
@@ -522,8 +527,8 @@ def cli_main():
|
||||
pass
|
||||
return 1
|
||||
|
||||
except Exception, e:
|
||||
print(u"Decryption failed\m{0}".format(traceback.format_exc()))
|
||||
except Exception as e:
|
||||
print("Decryption failed\n{0}".format(traceback.format_exc()))
|
||||
try:
|
||||
tb.cleanup()
|
||||
except:
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
from ignoblekeygen import generate_key
|
||||
from calibre_plugins.dedrm.ignoblekeygen import generate_key
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
|
||||
@@ -21,8 +19,8 @@ DETAILED_MESSAGE = \
|
||||
|
||||
def uStrCmp (s1, s2, caseless=False):
|
||||
import unicodedata as ud
|
||||
str1 = s1 if isinstance(s1, unicode) else unicode(s1)
|
||||
str2 = s2 if isinstance(s2, unicode) else unicode(s2)
|
||||
str1 = s1 if isinstance(s1, str) else str(s1)
|
||||
str2 = s2 if isinstance(s2, str) else str(s2)
|
||||
if caseless:
|
||||
return ud.normalize('NFC', str1.lower()) == ud.normalize('NFC', str2.lower())
|
||||
else:
|
||||
|
||||
@@ -1,59 +1,95 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
from __future__ import print_function
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
|
||||
# Standard Python modules.
|
||||
import os, sys, re, hashlib, traceback
|
||||
from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
|
||||
|
||||
|
||||
class NoWinePython3Exception(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class WinePythonCLI:
|
||||
py3_test = "import sys; sys.exit(0 if (sys.version_info.major==3) else 1)"
|
||||
def __init__(self, wineprefix=""):
|
||||
import subprocess
|
||||
|
||||
if wineprefix != "":
|
||||
wineprefix = os.path.abspath(os.path.expanduser(os.path.expandvars(wineprefix)))
|
||||
|
||||
if wineprefix != "" and os.path.exists(wineprefix):
|
||||
self.wineprefix = wineprefix
|
||||
else:
|
||||
self.wineprefix = None
|
||||
|
||||
candidate_execs = [
|
||||
["wine", "py.exe", "-3"],
|
||||
["wine", "python3.exe"],
|
||||
["wine", "python.exe"],
|
||||
["wine", "C:\\Python27\\python.exe"], # Should likely be removed
|
||||
]
|
||||
for e in candidate_execs:
|
||||
self.python_exec = e
|
||||
try:
|
||||
self.check_call(["-c", self.py3_test])
|
||||
print("{0} v{1}: Python3 exec found as {2}".format(
|
||||
PLUGIN_NAME, PLUGIN_VERSION, " ".join(self.python_exec)
|
||||
))
|
||||
return None
|
||||
except subprocess.CalledProcessError as e:
|
||||
if e.returncode == 1:
|
||||
print("{0} v{1}: {2} is not python3".format(
|
||||
PLUGIN_NAME, PLUGIN_VERSION, " ".join(self.python_exec)
|
||||
))
|
||||
elif e.returncode == 53:
|
||||
print("{0} v{1}: {2} does not exist".format(
|
||||
PLUGIN_NAME, PLUGIN_VERSION, " ".join(self.python_exec)
|
||||
))
|
||||
raise NoWinePython3Exception("Could not find python3 executable on specified wine prefix")
|
||||
|
||||
|
||||
def check_call(self, cli_args):
|
||||
import subprocess
|
||||
|
||||
env_dict = os.environ
|
||||
env_dict["PYTHONPATH"] = ""
|
||||
if self.wineprefix is not None:
|
||||
env_dict["WINEPREFIX"] = self.wineprefix
|
||||
|
||||
subprocess.check_call(self.python_exec + cli_args, env=env_dict,
|
||||
stdin=None, stdout=sys.stdout,
|
||||
stderr=subprocess.STDOUT, close_fds=False,
|
||||
bufsize=1)
|
||||
|
||||
|
||||
def WineGetKeys(scriptpath, extension, wineprefix=""):
|
||||
import subprocess
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
|
||||
import subasyncio
|
||||
from subasyncio import Process
|
||||
|
||||
if extension == u".k4i":
|
||||
if extension == ".k4i":
|
||||
import json
|
||||
|
||||
basepath, script = os.path.split(scriptpath)
|
||||
print(u"{0} v{1}: Running {2} under Wine".format(PLUGIN_NAME, PLUGIN_VERSION, script))
|
||||
try:
|
||||
pyexec = WinePythonCLI(wineprefix)
|
||||
except NoWinePython3Exception:
|
||||
print('{0} v{1}: Unable to find python3 executable in WINEPREFIX="{2}"'.format(PLUGIN_NAME, PLUGIN_VERSION, wineprefix))
|
||||
return []
|
||||
|
||||
outdirpath = os.path.join(basepath, u"winekeysdir")
|
||||
basepath, script = os.path.split(scriptpath)
|
||||
print("{0} v{1}: Running {2} under Wine".format(PLUGIN_NAME, PLUGIN_VERSION, script))
|
||||
|
||||
outdirpath = os.path.join(basepath, "winekeysdir")
|
||||
if not os.path.exists(outdirpath):
|
||||
os.makedirs(outdirpath)
|
||||
|
||||
if wineprefix != "":
|
||||
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:
|
||||
cmdline = u"wine 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]))
|
||||
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]))
|
||||
result = pyexec.check_call([scriptpath, outdirpath])
|
||||
except Exception as e:
|
||||
print("{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 = []
|
||||
@@ -63,14 +99,14 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
|
||||
try:
|
||||
fpath = os.path.join(outdirpath, filename)
|
||||
with open(fpath, 'rb') as keyfile:
|
||||
if extension == u".k4i":
|
||||
if extension == ".k4i":
|
||||
new_key_value = json.loads(keyfile.read())
|
||||
else:
|
||||
new_key_value = keyfile.read()
|
||||
winekeys.append(new_key_value)
|
||||
except:
|
||||
print(u"{0} v{1}: Error loading file {2}".format(PLUGIN_NAME, PLUGIN_VERSION, filename))
|
||||
print("{0} v{1}: Error loading file {2}".format(PLUGIN_NAME, PLUGIN_VERSION, filename))
|
||||
traceback.print_exc()
|
||||
os.remove(fpath)
|
||||
print(u"{0} v{1}: Found and decrypted {2} {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(winekeys), u"key file" if len(winekeys) == 1 else u"key files"))
|
||||
print("{0} v{1}: Found and decrypted {2} {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(winekeys), "key file" if len(winekeys) == 1 else "key files"))
|
||||
return winekeys
|
||||
|
||||
184
DeDRM_plugin/zipfilerugged.py
Normal file → Executable file
184
DeDRM_plugin/zipfilerugged.py
Normal file → Executable file
@@ -1,11 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Read and write ZIP files.
|
||||
"""
|
||||
import struct, os, time, sys, shutil
|
||||
import binascii, cStringIO, stat
|
||||
import binascii, stat
|
||||
import io
|
||||
import re
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
try:
|
||||
import zlib # We may need its compression method
|
||||
crc32 = zlib.crc32
|
||||
@@ -45,8 +50,8 @@ ZIP_DEFLATED = 8
|
||||
|
||||
# The "end of central directory" structure, magic number, size, and indices
|
||||
# (section V.I in the format document)
|
||||
structEndArchive = "<4s4H2LH"
|
||||
stringEndArchive = "PK\005\006"
|
||||
structEndArchive = b"<4s4H2LH"
|
||||
stringEndArchive = b"PK\005\006"
|
||||
sizeEndCentDir = struct.calcsize(structEndArchive)
|
||||
|
||||
_ECD_SIGNATURE = 0
|
||||
@@ -64,8 +69,8 @@ _ECD_LOCATION = 9
|
||||
|
||||
# The "central directory" structure, magic number, size, and indices
|
||||
# of entries in the structure (section V.F in the format document)
|
||||
structCentralDir = "<4s4B4HL2L5H2L"
|
||||
stringCentralDir = "PK\001\002"
|
||||
structCentralDir = b"<4s4B4HL2L5H2L"
|
||||
stringCentralDir = b"PK\001\002"
|
||||
sizeCentralDir = struct.calcsize(structCentralDir)
|
||||
|
||||
# indexes of entries in the central directory structure
|
||||
@@ -91,8 +96,8 @@ _CD_LOCAL_HEADER_OFFSET = 18
|
||||
|
||||
# The "local file header" structure, magic number, size, and indices
|
||||
# (section V.A in the format document)
|
||||
structFileHeader = "<4s2B4HL2L2H"
|
||||
stringFileHeader = "PK\003\004"
|
||||
structFileHeader = b"<4s2B4HL2L2H"
|
||||
stringFileHeader = b"PK\003\004"
|
||||
sizeFileHeader = struct.calcsize(structFileHeader)
|
||||
|
||||
_FH_SIGNATURE = 0
|
||||
@@ -109,14 +114,14 @@ _FH_FILENAME_LENGTH = 10
|
||||
_FH_EXTRA_FIELD_LENGTH = 11
|
||||
|
||||
# The "Zip64 end of central directory locator" structure, magic number, and size
|
||||
structEndArchive64Locator = "<4sLQL"
|
||||
stringEndArchive64Locator = "PK\x06\x07"
|
||||
structEndArchive64Locator = b"<4sLQL"
|
||||
stringEndArchive64Locator = b"PK\x06\x07"
|
||||
sizeEndCentDir64Locator = struct.calcsize(structEndArchive64Locator)
|
||||
|
||||
# The "Zip64 end of central directory" record, magic number, size, and indices
|
||||
# (section V.G in the format document)
|
||||
structEndArchive64 = "<4sQ2H2L4Q"
|
||||
stringEndArchive64 = "PK\x06\x06"
|
||||
structEndArchive64 = b"<4sQ2H2L4Q"
|
||||
stringEndArchive64 = b"PK\x06\x06"
|
||||
sizeEndCentDir64 = struct.calcsize(structEndArchive64)
|
||||
|
||||
_CD64_SIGNATURE = 0
|
||||
@@ -275,21 +280,21 @@ class ZipInfo (object):
|
||||
|
||||
# Terminate the file name at the first null byte. Null bytes in file
|
||||
# names are used as tricks by viruses in archives.
|
||||
null_byte = filename.find(chr(0))
|
||||
null_byte = filename.find(b"\0")
|
||||
if null_byte >= 0:
|
||||
filename = filename[0:null_byte]
|
||||
# This is used to ensure paths in generated ZIP files always use
|
||||
# forward slashes as the directory separator, as required by the
|
||||
# ZIP format specification.
|
||||
if os.sep != "/" and os.sep in filename:
|
||||
filename = filename.replace(os.sep, "/")
|
||||
if os.sep != "/" and os.sep.encode('utf-8') in filename:
|
||||
filename = filename.replace(os.sep.encode('utf-8'), b"/")
|
||||
|
||||
self.filename = filename # Normalized file name
|
||||
self.date_time = date_time # year, month, day, hour, min, sec
|
||||
# Standard values:
|
||||
self.compress_type = ZIP_STORED # Type of compression for the file
|
||||
self.comment = "" # Comment for each file
|
||||
self.extra = "" # ZIP extra data
|
||||
self.comment = b"" # Comment for each file
|
||||
self.extra = b"" # ZIP extra data
|
||||
if sys.platform == 'win32':
|
||||
self.create_system = 0 # System which created ZIP archive
|
||||
else:
|
||||
@@ -343,23 +348,13 @@ class ZipInfo (object):
|
||||
return header + filename + extra
|
||||
|
||||
def _encodeFilenameFlags(self):
|
||||
if isinstance(self.filename, unicode):
|
||||
if isinstance(self.filename, bytes):
|
||||
return self.filename, self.flag_bits
|
||||
else:
|
||||
try:
|
||||
return self.filename.encode('ascii'), self.flag_bits
|
||||
except UnicodeEncodeError:
|
||||
return self.filename.encode('utf-8'), self.flag_bits | 0x800
|
||||
else:
|
||||
return self.filename, self.flag_bits
|
||||
|
||||
def _decodeFilename(self):
|
||||
if self.flag_bits & 0x800:
|
||||
try:
|
||||
#print "decoding filename",self.filename
|
||||
return self.filename.decode('utf-8')
|
||||
except:
|
||||
return self.filename
|
||||
else:
|
||||
return self.filename
|
||||
|
||||
def _decodeExtra(self):
|
||||
# Try to decode the extra field.
|
||||
@@ -377,20 +372,20 @@ class ZipInfo (object):
|
||||
elif ln == 0:
|
||||
counts = ()
|
||||
else:
|
||||
raise RuntimeError, "Corrupt extra field %s"%(ln,)
|
||||
raise RuntimeError("Corrupt extra field %s"%(ln,))
|
||||
|
||||
idx = 0
|
||||
|
||||
# ZIP64 extension (large files and/or large archives)
|
||||
if self.file_size in (0xffffffffffffffffL, 0xffffffffL):
|
||||
if self.file_size in (0xffffffffffffffff, 0xffffffff):
|
||||
self.file_size = counts[idx]
|
||||
idx += 1
|
||||
|
||||
if self.compress_size == 0xFFFFFFFFL:
|
||||
if self.compress_size == 0xFFFFFFFF:
|
||||
self.compress_size = counts[idx]
|
||||
idx += 1
|
||||
|
||||
if self.header_offset == 0xffffffffL:
|
||||
if self.header_offset == 0xffffffff:
|
||||
old = self.header_offset
|
||||
self.header_offset = counts[idx]
|
||||
idx+=1
|
||||
@@ -481,9 +476,9 @@ class ZipExtFile(io.BufferedIOBase):
|
||||
|
||||
if self._compress_type == ZIP_DEFLATED:
|
||||
self._decompressor = zlib.decompressobj(-15)
|
||||
self._unconsumed = ''
|
||||
self._unconsumed = b''
|
||||
|
||||
self._readbuffer = ''
|
||||
self._readbuffer = b''
|
||||
self._offset = 0
|
||||
|
||||
self._universal = 'U' in mode
|
||||
@@ -514,10 +509,10 @@ class ZipExtFile(io.BufferedIOBase):
|
||||
if not self._universal:
|
||||
return io.BufferedIOBase.readline(self, limit)
|
||||
|
||||
line = ''
|
||||
line = b''
|
||||
while limit < 0 or len(line) < limit:
|
||||
readahead = self.peek(2)
|
||||
if readahead == '':
|
||||
if readahead == b'':
|
||||
return line
|
||||
|
||||
#
|
||||
@@ -564,7 +559,7 @@ class ZipExtFile(io.BufferedIOBase):
|
||||
If the argument is omitted, None, or negative, data is read and returned until EOF is reached..
|
||||
"""
|
||||
|
||||
buf = ''
|
||||
buf = b''
|
||||
while n < 0 or n is None or n > len(buf):
|
||||
data = self.read1(n)
|
||||
if len(data) == 0:
|
||||
@@ -594,7 +589,7 @@ class ZipExtFile(io.BufferedIOBase):
|
||||
self._compress_left -= len(data)
|
||||
|
||||
if data and self._decrypter is not None:
|
||||
data = ''.join(map(self._decrypter, data))
|
||||
data = b''.join(map(self._decrypter, data))
|
||||
|
||||
if self._compress_type == ZIP_STORED:
|
||||
self._readbuffer = self._readbuffer[self._offset:] + data
|
||||
@@ -651,10 +646,10 @@ class ZipFile:
|
||||
pass
|
||||
elif compression == ZIP_DEFLATED:
|
||||
if not zlib:
|
||||
raise RuntimeError,\
|
||||
"Compression requires the (missing) zlib module"
|
||||
raise RuntimeError(
|
||||
"Compression requires the (missing) zlib module")
|
||||
else:
|
||||
raise RuntimeError, "That compression method is not supported"
|
||||
raise RuntimeError("That compression method is not supported")
|
||||
|
||||
self._allowZip64 = allowZip64
|
||||
self._didModify = False
|
||||
@@ -664,10 +659,10 @@ class ZipFile:
|
||||
self.compression = compression # Method of compression
|
||||
self.mode = key = mode.replace('b', '')[0]
|
||||
self.pwd = None
|
||||
self.comment = ''
|
||||
self.comment = b''
|
||||
|
||||
# Check if we were passed a file-like object
|
||||
if isinstance(file, basestring):
|
||||
if isinstance(file, str):
|
||||
self._filePassed = 0
|
||||
self.filename = file
|
||||
modeDict = {'r' : 'rb', 'w': 'wb', 'a' : 'r+b'}
|
||||
@@ -699,7 +694,7 @@ class ZipFile:
|
||||
if not self._filePassed:
|
||||
self.fp.close()
|
||||
self.fp = None
|
||||
raise RuntimeError, 'Mode must be "r", "w" or "a"'
|
||||
raise RuntimeError('Mode must be "r", "w" or "a"')
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
@@ -723,9 +718,9 @@ class ZipFile:
|
||||
fp = self.fp
|
||||
endrec = _EndRecData(fp)
|
||||
if not endrec:
|
||||
raise BadZipfile, "File is not a zip file"
|
||||
raise BadZipfile("File is not a zip file")
|
||||
if self.debug > 1:
|
||||
print endrec
|
||||
print(endrec)
|
||||
size_cd = endrec[_ECD_SIZE] # bytes in central directory
|
||||
offset_cd = endrec[_ECD_OFFSET] # offset of central directory
|
||||
self.comment = endrec[_ECD_COMMENT] # archive comment
|
||||
@@ -738,20 +733,20 @@ class ZipFile:
|
||||
|
||||
if self.debug > 2:
|
||||
inferred = concat + offset_cd
|
||||
print "given, inferred, offset", offset_cd, inferred, concat
|
||||
print("given, inferred, offset", offset_cd, inferred, concat)
|
||||
# self.start_dir: Position of start of central directory
|
||||
self.start_dir = offset_cd + concat
|
||||
fp.seek(self.start_dir, 0)
|
||||
data = fp.read(size_cd)
|
||||
fp = cStringIO.StringIO(data)
|
||||
fp = BytesIO(data)
|
||||
total = 0
|
||||
while total < size_cd:
|
||||
centdir = fp.read(sizeCentralDir)
|
||||
if centdir[0:4] != stringCentralDir:
|
||||
raise BadZipfile, "Bad magic number for central directory"
|
||||
raise BadZipfile("Bad magic number for central directory")
|
||||
centdir = struct.unpack(structCentralDir, centdir)
|
||||
if self.debug > 2:
|
||||
print centdir
|
||||
print(centdir)
|
||||
filename = fp.read(centdir[_CD_FILENAME_LENGTH])
|
||||
# Create ZipInfo instance to store file information
|
||||
x = ZipInfo(filename)
|
||||
@@ -769,7 +764,6 @@ class ZipFile:
|
||||
|
||||
x._decodeExtra()
|
||||
x.header_offset = x.header_offset + concat
|
||||
x.filename = x._decodeFilename()
|
||||
self.filelist.append(x)
|
||||
self.NameToInfo[x.filename] = x
|
||||
|
||||
@@ -779,7 +773,7 @@ class ZipFile:
|
||||
+ centdir[_CD_COMMENT_LENGTH])
|
||||
|
||||
if self.debug > 2:
|
||||
print "total", total
|
||||
print("total", total)
|
||||
|
||||
|
||||
def namelist(self):
|
||||
@@ -796,10 +790,10 @@ class ZipFile:
|
||||
|
||||
def printdir(self):
|
||||
"""Print a table of contents for the zip file."""
|
||||
print "%-46s %19s %12s" % ("File Name", "Modified ", "Size")
|
||||
print("%-46s %19s %12s" % ("File Name", "Modified ", "Size"))
|
||||
for zinfo in self.filelist:
|
||||
date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time[:6]
|
||||
print "%-46s %s %12d" % (zinfo.filename, date, zinfo.file_size)
|
||||
print("%-46s %s %12d" % (zinfo.filename, date, zinfo.file_size))
|
||||
|
||||
def testzip(self):
|
||||
"""Read all the files and check the CRC."""
|
||||
@@ -833,11 +827,11 @@ class ZipFile:
|
||||
|
||||
def open(self, name, mode="r", pwd=None):
|
||||
"""Return file-like object for 'name'."""
|
||||
if mode not in ("r", "U", "rU"):
|
||||
raise RuntimeError, 'open() requires mode "r", "U", or "rU"'
|
||||
if mode not in ("r", "", "rU"):
|
||||
raise RuntimeError('open() requires mode "r", "", or "rU"')
|
||||
if not self.fp:
|
||||
raise RuntimeError, \
|
||||
"Attempt to read ZIP archive that was already closed"
|
||||
raise RuntimeError(
|
||||
"Attempt to read ZIP archive that was already closed")
|
||||
|
||||
# Only open a new file for instances where we were not
|
||||
# given a file object in the constructor
|
||||
@@ -859,7 +853,7 @@ class ZipFile:
|
||||
# Skip the file header:
|
||||
fheader = zef_file.read(sizeFileHeader)
|
||||
if fheader[0:4] != stringFileHeader:
|
||||
raise BadZipfile, "Bad magic number for file header"
|
||||
raise BadZipfile("Bad magic number for file header")
|
||||
|
||||
fheader = struct.unpack(structFileHeader, fheader)
|
||||
fname = zef_file.read(fheader[_FH_FILENAME_LENGTH])
|
||||
@@ -867,9 +861,9 @@ class ZipFile:
|
||||
zef_file.read(fheader[_FH_EXTRA_FIELD_LENGTH])
|
||||
|
||||
if fname != zinfo.orig_filename:
|
||||
raise BadZipfile, \
|
||||
raise BadZipfile(
|
||||
'File name in directory "%s" and header "%s" differ.' % (
|
||||
zinfo.orig_filename, fname)
|
||||
zinfo.orig_filename, fname))
|
||||
|
||||
# check for encrypted flag & handle password
|
||||
is_encrypted = zinfo.flag_bits & 0x1
|
||||
@@ -878,8 +872,8 @@ class ZipFile:
|
||||
if not pwd:
|
||||
pwd = self.pwd
|
||||
if not pwd:
|
||||
raise RuntimeError, "File %s is encrypted, " \
|
||||
"password required for extraction" % name
|
||||
raise RuntimeError("File %s is encrypted, " \
|
||||
"password required for extraction" % name)
|
||||
|
||||
zd = _ZipDecrypter(pwd)
|
||||
# The first 12 bytes in the cypher stream is an encryption header
|
||||
@@ -956,7 +950,7 @@ class ZipFile:
|
||||
return targetpath
|
||||
|
||||
source = self.open(member, pwd=pwd)
|
||||
target = file(targetpath, "wb")
|
||||
target = open(targetpath, "wb")
|
||||
shutil.copyfileobj(source, target)
|
||||
source.close()
|
||||
target.close()
|
||||
@@ -967,18 +961,18 @@ class ZipFile:
|
||||
"""Check for errors before writing a file to the archive."""
|
||||
if zinfo.filename in self.NameToInfo:
|
||||
if self.debug: # Warning for duplicate names
|
||||
print "Duplicate name:", zinfo.filename
|
||||
print("Duplicate name:", zinfo.filename)
|
||||
if self.mode not in ("w", "a"):
|
||||
raise RuntimeError, 'write() requires mode "w" or "a"'
|
||||
raise RuntimeError('write() requires mode "w" or "a"')
|
||||
if not self.fp:
|
||||
raise RuntimeError, \
|
||||
"Attempt to write ZIP archive that was already closed"
|
||||
raise RuntimeError(
|
||||
"Attempt to write ZIP archive that was already closed")
|
||||
if zinfo.compress_type == ZIP_DEFLATED and not zlib:
|
||||
raise RuntimeError, \
|
||||
"Compression requires the (missing) zlib module"
|
||||
raise RuntimeError(
|
||||
"Compression requires the (missing) zlib module")
|
||||
if zinfo.compress_type not in (ZIP_STORED, ZIP_DEFLATED):
|
||||
raise RuntimeError, \
|
||||
"That compression method is not supported"
|
||||
raise RuntimeError(
|
||||
"That compression method is not supported")
|
||||
if zinfo.file_size > ZIP64_LIMIT:
|
||||
if not self._allowZip64:
|
||||
raise LargeZipFile("Filesize would require ZIP64 extensions")
|
||||
@@ -1006,7 +1000,7 @@ class ZipFile:
|
||||
if isdir:
|
||||
arcname += '/'
|
||||
zinfo = ZipInfo(arcname, date_time)
|
||||
zinfo.external_attr = (st[0] & 0xFFFF) << 16L # Unix attributes
|
||||
zinfo.external_attr = (st[0] & 0xFFFF) << 16 # Unix attributes
|
||||
if compress_type is None:
|
||||
zinfo.compress_type = self.compression
|
||||
else:
|
||||
@@ -1076,7 +1070,7 @@ class ZipFile:
|
||||
date_time=time.localtime(time.time())[:6])
|
||||
|
||||
zinfo.compress_type = self.compression
|
||||
zinfo.external_attr = 0600 << 16
|
||||
zinfo.external_attr = 0x0600 << 16
|
||||
else:
|
||||
zinfo = zinfo_or_arcname
|
||||
|
||||
@@ -1141,7 +1135,7 @@ class ZipFile:
|
||||
|
||||
if zinfo.header_offset > ZIP64_LIMIT:
|
||||
extra.append(zinfo.header_offset)
|
||||
header_offset = 0xffffffffL
|
||||
header_offset = 0xffffffff
|
||||
else:
|
||||
header_offset = zinfo.header_offset
|
||||
|
||||
@@ -1169,14 +1163,14 @@ class ZipFile:
|
||||
0, zinfo.internal_attr, zinfo.external_attr,
|
||||
header_offset)
|
||||
except DeprecationWarning:
|
||||
print >>sys.stderr, (structCentralDir,
|
||||
print(structCentralDir,
|
||||
stringCentralDir, create_version,
|
||||
zinfo.create_system, extract_version, zinfo.reserved,
|
||||
zinfo.flag_bits, zinfo.compress_type, dostime, dosdate,
|
||||
zinfo.CRC, compress_size, file_size,
|
||||
len(zinfo.filename), len(extra_data), len(zinfo.comment),
|
||||
0, zinfo.internal_attr, zinfo.external_attr,
|
||||
header_offset)
|
||||
header_offset, sys.stderr)
|
||||
raise
|
||||
self.fp.write(centdir)
|
||||
self.fp.write(filename)
|
||||
@@ -1250,10 +1244,10 @@ class PyZipFile(ZipFile):
|
||||
else:
|
||||
basename = name
|
||||
if self.debug:
|
||||
print "Adding package in", pathname, "as", basename
|
||||
print("Adding package in", pathname, "as", basename)
|
||||
fname, arcname = self._get_codename(initname[0:-3], basename)
|
||||
if self.debug:
|
||||
print "Adding", arcname
|
||||
print("Adding", arcname)
|
||||
self.write(fname, arcname)
|
||||
dirlist = os.listdir(pathname)
|
||||
dirlist.remove("__init__.py")
|
||||
@@ -1269,12 +1263,12 @@ class PyZipFile(ZipFile):
|
||||
fname, arcname = self._get_codename(path[0:-3],
|
||||
basename)
|
||||
if self.debug:
|
||||
print "Adding", arcname
|
||||
print("Adding", arcname)
|
||||
self.write(fname, arcname)
|
||||
else:
|
||||
# This is NOT a package directory, add its files at top level
|
||||
if self.debug:
|
||||
print "Adding files from directory", pathname
|
||||
print("Adding files from directory", pathname)
|
||||
for filename in os.listdir(pathname):
|
||||
path = os.path.join(pathname, filename)
|
||||
root, ext = os.path.splitext(filename)
|
||||
@@ -1282,15 +1276,15 @@ class PyZipFile(ZipFile):
|
||||
fname, arcname = self._get_codename(path[0:-3],
|
||||
basename)
|
||||
if self.debug:
|
||||
print "Adding", arcname
|
||||
print("Adding", arcname)
|
||||
self.write(fname, arcname)
|
||||
else:
|
||||
if pathname[-3:] != ".py":
|
||||
raise RuntimeError, \
|
||||
'Files added with writepy() must end with ".py"'
|
||||
raise RuntimeError(
|
||||
'Files added with writepy() must end with ".py"')
|
||||
fname, arcname = self._get_codename(pathname[0:-3], basename)
|
||||
if self.debug:
|
||||
print "Adding file", arcname
|
||||
print("Adding file", arcname)
|
||||
self.write(fname, arcname)
|
||||
|
||||
def _get_codename(self, pathname, basename):
|
||||
@@ -1310,11 +1304,11 @@ class PyZipFile(ZipFile):
|
||||
os.stat(file_pyc).st_mtime < os.stat(file_py).st_mtime:
|
||||
import py_compile
|
||||
if self.debug:
|
||||
print "Compiling", file_py
|
||||
print("Compiling", file_py)
|
||||
try:
|
||||
py_compile.compile(file_py, file_pyc, None, True)
|
||||
except py_compile.PyCompileError,err:
|
||||
print err.msg
|
||||
except py_compile.PyCompileError as err:
|
||||
print(err.msg)
|
||||
fname = file_pyc
|
||||
else:
|
||||
fname = file_pyc
|
||||
@@ -1337,12 +1331,12 @@ def main(args = None):
|
||||
args = sys.argv[1:]
|
||||
|
||||
if not args or args[0] not in ('-l', '-c', '-e', '-t'):
|
||||
print USAGE
|
||||
print(USAGE)
|
||||
sys.exit(1)
|
||||
|
||||
if args[0] == '-l':
|
||||
if len(args) != 2:
|
||||
print USAGE
|
||||
print(USAGE)
|
||||
sys.exit(1)
|
||||
zf = ZipFile(args[1], 'r')
|
||||
zf.printdir()
|
||||
@@ -1350,15 +1344,15 @@ def main(args = None):
|
||||
|
||||
elif args[0] == '-t':
|
||||
if len(args) != 2:
|
||||
print USAGE
|
||||
print(USAGE)
|
||||
sys.exit(1)
|
||||
zf = ZipFile(args[1], 'r')
|
||||
zf.testzip()
|
||||
print "Done testing"
|
||||
print("Done testing")
|
||||
|
||||
elif args[0] == '-e':
|
||||
if len(args) != 3:
|
||||
print USAGE
|
||||
print(USAGE)
|
||||
sys.exit(1)
|
||||
|
||||
zf = ZipFile(args[1], 'r')
|
||||
@@ -1378,7 +1372,7 @@ def main(args = None):
|
||||
|
||||
elif args[0] == '-c':
|
||||
if len(args) < 3:
|
||||
print USAGE
|
||||
print(USAGE)
|
||||
sys.exit(1)
|
||||
|
||||
def addToZip(zf, path, zippath):
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# zipfix.py, version 1.1
|
||||
# Copyright © 2010-2013 by some_updates, DiapDealer and Apprentice Alf
|
||||
# zipfix.py
|
||||
# Copyright © 2010-2020 by Apprentice Harper et al.
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
@@ -10,18 +10,22 @@
|
||||
# Revision history:
|
||||
# 1.0 - Initial release
|
||||
# 1.1 - Updated to handle zip file metadata correctly
|
||||
# 2.0 - Python 3 for calibre 5.0
|
||||
|
||||
"""
|
||||
Re-write zip (or ePub) fixing problems with file names (and mimetype entry).
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "1.1"
|
||||
|
||||
import sys
|
||||
import zlib
|
||||
import zipfilerugged
|
||||
try:
|
||||
import zipfilerugged
|
||||
except:
|
||||
import calibre_plugins.dedrm.zipfilerugged as zipfilerugged
|
||||
import os
|
||||
import os.path
|
||||
import getopt
|
||||
@@ -49,7 +53,7 @@ class fixZip:
|
||||
self.inzip = zipfilerugged.ZipFile(zinput,'r')
|
||||
self.outzip = zipfilerugged.ZipFile(zoutput,'w')
|
||||
# open the input zip for reading only as a raw file
|
||||
self.bzf = file(zinput,'rb')
|
||||
self.bzf = open(zinput,'rb')
|
||||
|
||||
def getlocalname(self, zi):
|
||||
local_header_offset = zi.header_offset
|
||||
@@ -62,21 +66,21 @@ class fixZip:
|
||||
|
||||
def uncompress(self, cmpdata):
|
||||
dc = zlib.decompressobj(-15)
|
||||
data = ''
|
||||
data = b''
|
||||
while len(cmpdata) > 0:
|
||||
if len(cmpdata) > _MAX_SIZE :
|
||||
newdata = cmpdata[0:_MAX_SIZE]
|
||||
cmpdata = cmpdata[_MAX_SIZE:]
|
||||
else:
|
||||
newdata = cmpdata
|
||||
cmpdata = ''
|
||||
cmpdata = b''
|
||||
newdata = dc.decompress(newdata)
|
||||
unprocessed = dc.unconsumed_tail
|
||||
if len(unprocessed) == 0:
|
||||
newdata += dc.flush()
|
||||
data += newdata
|
||||
cmpdata += unprocessed
|
||||
unprocessed = ''
|
||||
unprocessed = b''
|
||||
return data
|
||||
|
||||
def getfiledata(self, zi):
|
||||
@@ -115,11 +119,11 @@ class fixZip:
|
||||
# if epub write mimetype file first, with no compression
|
||||
if self.ztype == 'epub':
|
||||
# first get a ZipInfo with current time and no compression
|
||||
mimeinfo = ZipInfo('mimetype',compress_type=zipfilerugged.ZIP_STORED)
|
||||
mimeinfo = ZipInfo(b'mimetype',compress_type=zipfilerugged.ZIP_STORED)
|
||||
mimeinfo.internal_attr = 1 # text file
|
||||
try:
|
||||
# if the mimetype is present, get its info, including time-stamp
|
||||
oldmimeinfo = self.inzip.getinfo('mimetype')
|
||||
oldmimeinfo = self.inzip.getinfo(b'mimetype')
|
||||
# copy across useful fields
|
||||
mimeinfo.date_time = oldmimeinfo.date_time
|
||||
mimeinfo.comment = oldmimeinfo.comment
|
||||
@@ -129,11 +133,11 @@ class fixZip:
|
||||
mimeinfo.create_system = oldmimeinfo.create_system
|
||||
except:
|
||||
pass
|
||||
self.outzip.writestr(mimeinfo, _MIMETYPE)
|
||||
self.outzip.writestr(mimeinfo, _MIMETYPE.encode('ascii'))
|
||||
|
||||
# write the rest of the files
|
||||
for zinfo in self.inzip.infolist():
|
||||
if zinfo.filename != "mimetype" or self.ztype != 'epub':
|
||||
if zinfo.filename != b"mimetype" or self.ztype != 'epub':
|
||||
data = None
|
||||
try:
|
||||
data = self.inzip.read(zinfo.filename)
|
||||
@@ -171,7 +175,7 @@ def repairBook(infile, outfile):
|
||||
fr = fixZip(infile, outfile)
|
||||
fr.fix()
|
||||
return 0
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
print("Error Occurred ", e)
|
||||
return 2
|
||||
|
||||
|
||||
8
FAQs.md
8
FAQs.md
@@ -18,7 +18,7 @@ Just download and use these tools, that's all! Uh, almost. There are a few, uh,
|
||||
|
||||
But otherwise, if your ebook is from Amazon, Kobo, Barnes & Noble or any of the ebook stores selling ebooks compatible with Adobe Digital Editions 2.0.1, you should be able to remove the DRM that's been applied to your ebooks.
|
||||
|
||||
### A Recent Change to Kindle for PC/Kindle for Mac
|
||||
### Recent Changes to Kindle for PC/Kindle for Mac
|
||||
Starting with version 1.19, Kindle for PC/Mac uses Amazon's new KFX format which isn't quite as good a source for conversion to ePub as the older KF8 (& MOBI) formats. There are two options to get the older formats. Either stick with version 1.17 or earlier, or modify the executable by changing a file name (PC) or disabling a component of the application (Mac).
|
||||
|
||||
Version 1.17 of Kindle is no longer available directly from Amazon, so you will need to search for the proper file name and find it on a third party site. The name is `KindleForPC-installer-1.17.44170.exe` for PC and `KindleForMac-44182.dmg` for Mac.
|
||||
@@ -34,7 +34,7 @@ Verify the one of the following cryptographic hash values, using software of you
|
||||
* SHA-1: 7AB9A86B954CB23D622BD79E3257F8E2182D791C
|
||||
* SHA-256: 28DC21246A9C7CDEDD2D6F0F4082E6BF7EF9DB9CE9D485548E 8A9E1D19EAE2AC.
|
||||
|
||||
You will need to go to the preferences and uncheck the auto update checkbox. Then download and install 1.17 over the top of the newer installation. You'll also need to delete the KFX folders from your My Kindle Content folder.
|
||||
You will need to go to the preferences and uncheck the auto update checkbox. Then download and install 1.17 over the top of the newer installation. You'll also need to delete the KFX folders from your My Kindle Content folder. You may also need to take further action to prevent an auto update. The simplest wayis to find the 'updates' folder and replace it with a file. See [this thread] (http://www.mobileread.com/forums/showthread.php?t=283371) at MobileRead for a Script to do this on a PC. On a Mac you can find the folder at ~/Library/Application Support/Kindle/ just delete the folder 'updates' and save a blank text file called 'updates' in its place.
|
||||
|
||||
Another possible solution is to use 1.19 or later, but disable KFX by renaming or disabling a necessary component of the application. This may or may not work on versions after 1.25. In a command window, enter the following commands when Kindle for PC/Mac is not running:
|
||||
|
||||
@@ -51,7 +51,7 @@ Mac Note: If the chmod command fails with a permission error try again using `su
|
||||
After restarting the Kindle program any books previously downloaded in KFX format will no longer open. You will need to remove them from your device and re-download them. All future downloads will use the older Kindle formats instead of KFX although they will continue to be placed in one individual subdirectory per book. Note that books soudl be downoad by right-click and 'Download', not by just opening the book. Recent (1.25+) versions of Kindle for Mac/PC may convert KF8 files to a new format that is not supported by these tools when the book is opened for reading.
|
||||
|
||||
#### Decrypting KFX
|
||||
Thanks to work by several people, the tools can now decrypt KFX format ebooks from Kindle for Mac/PC. In addition to the DeDRM plugin, calibre users will also need to install jhowell's KFX Input plugin which is available through the standard plugin menu in calibre, or directly from [his plugin thread](https://www.mobileread.com/forums/showthread.php?t=291290) on Mobileread.
|
||||
Thanks to work by several people, the tools can now decrypt KFX format ebooks from Kindle for Mac/PC version 1.26 or earlier (version later than 1.26 use a new encryption scheme for KFX files). In addition to the DeDRM plugin, calibre users will also need to install jhowell's KFX Input plugin which is available through the standard plugin menu in calibre, or directly from [his plugin thread](https://www.mobileread.com/forums/showthread.php?t=291290) on Mobileread.
|
||||
|
||||
#### Thanks
|
||||
Thanks to jhowell for his investigations into KFX format and the KFX Input plugin. Some of these instructions are from [his thread on the subject](https://www.mobileread.com/forums/showthread.php?t=283371) at MobileRead.
|
||||
@@ -68,7 +68,7 @@ Install calibre. Install the DeDRM\_plugin in calibre. Install the Obok\_plugin
|
||||
# Installing the Tools
|
||||
## The calibre plugin
|
||||
### I am trying to install the calibre plugin, but calibre says "ERROR: Unhandled exception: InvalidPlugin: The plugin in u’[path]DeDRM\_tools\_6.5.3.zip’ is invalid. It does not contain a top-level \_\_init\_\_.py file"
|
||||
You are trying to add the tools archive (e.g. `DeDRM_tools_6.5.3.zip`) instead of the plugin. The tools archive is not the plugin. It is a collection of DRM removal tools which includes the plugin. You must unzip the archive, and install the calibre plugin `DeDRM_plugin.zip` from a folder called `DeDRM_calibre_plugin` in the unzipped archive.
|
||||
You are trying to add the tools archive (e.g. `DeDRM_tools_6.8.0.zip`) instead of the plugin. The tools archive is not the plugin. It is a collection of DRM removal tools which includes the plugin. You must unzip the archive, and install the calibre plugin `DeDRM_plugin.zip` from a folder called `DeDRM_calibre_plugin` in the unzipped archive.
|
||||
|
||||
### I’ve unzipped the tools archive, but I can’t find the calibre plugin when I try to add them to calibre. I use Windows.
|
||||
You should select the zip file that is in the `DeDRM_calibre_plugin` folder, not any files inside the plugin’s zip archive. Make sure you are selecting from the folder that you created when you unzipped the tools archive and not selecting a file inside the still-zipped tools archive.
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
_license__ = 'GPL v3'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
import codecs
|
||||
import os, traceback, zipfile
|
||||
|
||||
try:
|
||||
from PyQt5.Qt import QToolButton, QUrl
|
||||
except ImportError:
|
||||
from PyQt4.Qt import QToolButton, QUrl
|
||||
|
||||
|
||||
from calibre.gui2 import open_url, question_dialog
|
||||
from calibre.gui2.actions import InterfaceAction
|
||||
from calibre.utils.config import config_dir
|
||||
@@ -24,7 +25,7 @@ from calibre.ebooks.metadata.meta import get_metadata
|
||||
from calibre_plugins.obok_dedrm.dialogs import (SelectionDialog, DecryptAddProgressDialog,
|
||||
AddEpubFormatsProgressDialog, ResultsSummaryDialog)
|
||||
from calibre_plugins.obok_dedrm.config import plugin_prefs as cfg
|
||||
from calibre_plugins.obok_dedrm.__init__ import (PLUGIN_NAME, PLUGIN_SAFE_NAME,
|
||||
from calibre_plugins.obok_dedrm.__init__ import (PLUGIN_NAME, PLUGIN_SAFE_NAME,
|
||||
PLUGIN_VERSION, PLUGIN_DESCRIPTION, HELPFILE_NAME)
|
||||
from calibre_plugins.obok_dedrm.utilities import (
|
||||
get_icon, set_plugin_icon_resources, showErrorDlg, format_plural,
|
||||
@@ -53,7 +54,7 @@ class InterfacePluginAction(InterfaceAction):
|
||||
def genesis(self):
|
||||
icon_resources = self.load_resources(PLUGIN_ICONS)
|
||||
set_plugin_icon_resources(PLUGIN_NAME, icon_resources)
|
||||
|
||||
|
||||
self.qaction.setIcon(get_icon(PLUGIN_ICONS[0]))
|
||||
self.qaction.triggered.connect(self.launchObok)
|
||||
self.gui.keyboard.finalize()
|
||||
@@ -106,10 +107,10 @@ class InterfacePluginAction(InterfaceAction):
|
||||
# Get a list of Kobo titles
|
||||
books = self.build_book_list()
|
||||
if len(books) < 1:
|
||||
msg = _('<p>No books found in Kobo Library\nAre you sure it\'s installed\configured\synchronized?')
|
||||
msg = _('<p>No books found in Kobo Library\nAre you sure it\'s installed/configured/synchronized?')
|
||||
showErrorDlg(msg, None)
|
||||
return
|
||||
|
||||
|
||||
# Check to see if a key can be retrieved using the legacy obok method.
|
||||
legacy_key = legacy_obok().get_legacy_cookie_id
|
||||
if legacy_key is not None:
|
||||
@@ -154,7 +155,7 @@ class InterfacePluginAction(InterfaceAction):
|
||||
# Close Kobo Library object
|
||||
self.library.close()
|
||||
|
||||
# If we have decrypted books to work with, feed the list of decrypted books details
|
||||
# If we have decrypted books to work with, feed the list of decrypted books details
|
||||
# and the callback function (self.add_new_books) to the ProgressDialog dispatcher.
|
||||
if len(self.books_to_add):
|
||||
d = DecryptAddProgressDialog(self.gui, self.books_to_add, self.add_new_books, self.db, 'calibre',
|
||||
@@ -196,7 +197,7 @@ class InterfacePluginAction(InterfaceAction):
|
||||
# We will write the help file out every time, in case the user upgrades the plugin zip
|
||||
# and there is a newer help file contained within it.
|
||||
file_path = os.path.join(config_dir, 'plugins', HELPFILE_NAME)
|
||||
file_data = self.load_resources(HELPFILE_NAME)[HELPFILE_NAME]
|
||||
file_data = self.load_resources(HELPFILE_NAME)[HELPFILE_NAME].decode('utf-8')
|
||||
with open(file_path,'w') as f:
|
||||
f.write(file_data)
|
||||
return file_path
|
||||
@@ -212,7 +213,7 @@ class InterfacePluginAction(InterfaceAction):
|
||||
def get_decrypted_kobo_books(self, book):
|
||||
'''
|
||||
This method is a call-back function used by DecryptAddProgressDialog in dialogs.py to decrypt Kobo books
|
||||
|
||||
|
||||
:param book: A KoboBook object that is to be decrypted.
|
||||
'''
|
||||
print (_('{0} - Decrypting {1}').format(PLUGIN_NAME + ' v' + PLUGIN_VERSION, book.title))
|
||||
@@ -233,7 +234,7 @@ class InterfacePluginAction(InterfaceAction):
|
||||
'''
|
||||
This method is a call-back function used by DecryptAddProgressDialog in dialogs.py to add books to calibre
|
||||
(It's set up to handle multiple books, but will only be fed books one at a time by DecryptAddProgressDialog)
|
||||
|
||||
|
||||
:param books_to_add: List of calibre bookmaps (created in get_decrypted_kobo_books)
|
||||
'''
|
||||
added = self.db.add_books(books_to_add, add_duplicates=False, run_hooks=False)
|
||||
@@ -253,7 +254,7 @@ class InterfacePluginAction(InterfaceAction):
|
||||
def add_epub_format(self, book_id, mi, path):
|
||||
'''
|
||||
This method is a call-back function used by AddEpubFormatsProgressDialog in dialogs.py
|
||||
|
||||
|
||||
:param book_id: calibre ID of the book to add the encrypted epub to.
|
||||
:param mi: calibre metadata object
|
||||
:param path: path to the decrypted epub (temp file)
|
||||
@@ -281,7 +282,7 @@ class InterfacePluginAction(InterfaceAction):
|
||||
self.formats_to_add.append((home_id, mi, tmp_file))
|
||||
else:
|
||||
self.no_home_for_book.append(mi)
|
||||
# If we found homes for decrypted epubs in existing calibre entries, feed the list of decrypted book
|
||||
# If we found homes for decrypted epubs in existing calibre entries, feed the list of decrypted book
|
||||
# details and the callback function (self.add_epub_format) to the ProgressDialog dispatcher.
|
||||
if self.formats_to_add:
|
||||
d = AddEpubFormatsProgressDialog(self.gui, self.formats_to_add, self.add_epub_format)
|
||||
@@ -306,10 +307,10 @@ class InterfacePluginAction(InterfaceAction):
|
||||
sd = ResultsSummaryDialog(self.gui, caption, msg, log)
|
||||
sd.exec_()
|
||||
return
|
||||
|
||||
|
||||
def ask_about_inserting_epubs(self):
|
||||
'''
|
||||
Build question dialog with details about kobo books
|
||||
Build question dialog with details about kobo books
|
||||
that couldn't be added to calibre as new books.
|
||||
'''
|
||||
''' Terisa: Improve the message
|
||||
@@ -327,13 +328,13 @@ class InterfacePluginAction(InterfaceAction):
|
||||
msg = _('<p><b>{0}</b> -- not added because of {1} in your library.<br /><br />').format(self.duplicate_book_list[0][0].title, self.duplicate_book_list[0][2])
|
||||
msg += _('Would you like to try and add the EPUB format to an available calibre duplicate?<br /><br />')
|
||||
msg += _('NOTE: no pre-existing EPUB will be overwritten.')
|
||||
|
||||
|
||||
return question_dialog(self.gui, caption, msg, det_msg)
|
||||
|
||||
def find_a_home(self, ids):
|
||||
'''
|
||||
Find the ID of the first EPUB-Free duplicate available
|
||||
|
||||
|
||||
:param ids: List of calibre IDs that might serve as a home.
|
||||
'''
|
||||
for id in ids:
|
||||
@@ -373,7 +374,7 @@ class InterfacePluginAction(InterfaceAction):
|
||||
zin = zipfile.ZipFile(book.filename, 'r')
|
||||
#print ('Kobo library filename: {0}'.format(book.filename))
|
||||
for userkey in self.userkeys:
|
||||
print (_('Trying key: '), userkey.encode('hex_codec'))
|
||||
print (_('Trying key: '), codecs.encode(userkey, 'hex'))
|
||||
check = True
|
||||
try:
|
||||
fileout = PersistentTemporaryFile('.epub', dir=self.tdir)
|
||||
@@ -455,7 +456,7 @@ class InterfacePluginAction(InterfaceAction):
|
||||
if cancelled_count > 0:
|
||||
log += _('<p><b>Book imports cancelled by user:</b> {}</p>\n').format(cancelled_count)
|
||||
return (msg, log)
|
||||
log += _('<p><b>New EPUB formats inserted in existing calibre books:</b> {0}</p>\n').format(len(self.successful_format_adds))
|
||||
log += _('<p><b>New EPUB formats inserted in existing calibre books:</b> {0}</p>\n').format(len(self.successful_format_adds))
|
||||
if self.successful_format_adds:
|
||||
log += '<ul>\n'
|
||||
for id, mi in self.successful_format_adds:
|
||||
@@ -474,7 +475,7 @@ class InterfacePluginAction(InterfaceAction):
|
||||
log += _('<p><b>Format imports cancelled by user:</b> {}</p>\n').format(cancelled_count)
|
||||
return (msg, log)
|
||||
else:
|
||||
|
||||
|
||||
# Single book ... don't get fancy.
|
||||
if self.ids_of_new_books:
|
||||
title = self.ids_of_new_books[0][1].title
|
||||
@@ -494,4 +495,4 @@ class InterfacePluginAction(InterfaceAction):
|
||||
reason = _('of unknown reasons. Gosh I\'m embarrassed!')
|
||||
msg = _('<p>{0} not added because {1}').format(title, reason)
|
||||
return (msg, log)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2012, David Forrester <davidfor@internode.on.net>'
|
||||
@@ -9,13 +8,7 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, time, re, sys
|
||||
from datetime import datetime
|
||||
try:
|
||||
from PyQt5.Qt import (Qt, QIcon, QPixmap, QLabel, QDialog, QHBoxLayout, QProgressBar,
|
||||
QTableWidgetItem, QFont, QLineEdit, QComboBox,
|
||||
QVBoxLayout, QDialogButtonBox, QStyledItemDelegate, QDateTime,
|
||||
QRegExpValidator, QRegExp, QDate, QDateEdit)
|
||||
except ImportError:
|
||||
from PyQt4.Qt import (Qt, QIcon, QPixmap, QLabel, QDialog, QHBoxLayout, QProgressBar,
|
||||
from PyQt5.Qt import (Qt, QIcon, QPixmap, QLabel, QDialog, QHBoxLayout, QProgressBar,
|
||||
QTableWidgetItem, QFont, QLineEdit, QComboBox,
|
||||
QVBoxLayout, QDialogButtonBox, QStyledItemDelegate, QDateTime,
|
||||
QRegExpValidator, QRegExp, QDate, QDateEdit)
|
||||
@@ -427,7 +420,7 @@ class KeyValueComboBox(QComboBox):
|
||||
|
||||
def selected_key(self):
|
||||
for key, value in self.values.iteritems():
|
||||
if value == unicode(self.currentText()).strip():
|
||||
if value == self.currentText().strip():
|
||||
return key
|
||||
|
||||
|
||||
@@ -450,7 +443,7 @@ class KeyComboBox(QComboBox):
|
||||
|
||||
def selected_key(self):
|
||||
for key, value in self.values.iteritems():
|
||||
if key == unicode(self.currentText()).strip():
|
||||
if key == self.currentText().strip():
|
||||
return key
|
||||
|
||||
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
try:
|
||||
from PyQt5.Qt import (Qt, QGroupBox, QListWidget, QLineEdit, QDialogButtonBox, QWidget, QLabel, QDialog, QVBoxLayout, QAbstractItemView, QIcon, QHBoxLayout, QComboBox, QListWidgetItem, QFileDialog)
|
||||
except ImportError:
|
||||
from PyQt4.Qt import (Qt, QGroupBox, QListWidget, QLineEdit, QDialogButtonBox, QWidget, QLabel, QDialog, QVBoxLayout, QAbstractItemView, QIcon, QHBoxLayout, QComboBox, QListWidgetItem, QFileDialog)
|
||||
|
||||
try:
|
||||
from PyQt5 import Qt as QtGui
|
||||
except ImportError:
|
||||
from PyQt4 import QtGui
|
||||
from PyQt5.Qt import (Qt, QGroupBox, QListWidget, QLineEdit, QDialogButtonBox, QWidget, QLabel, QDialog, QVBoxLayout, QAbstractItemView, QIcon, QHBoxLayout, QComboBox, QListWidgetItem, QFileDialog)
|
||||
from PyQt5 import Qt as QtGui
|
||||
|
||||
from calibre.gui2 import (error_dialog, question_dialog, info_dialog, open_url)
|
||||
from calibre.utils.config import JSONConfig, config_dir
|
||||
@@ -50,32 +44,32 @@ class ConfigWidget(QWidget):
|
||||
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.setToolTip(_("Click to manage Kobo serial numbers for Kobo ebooks"))
|
||||
self.serials_button.setText("Kobo devices serials")
|
||||
self.serials_button.clicked.connect(self.edit_serials)
|
||||
layout.addWidget(self.serials_button)
|
||||
|
||||
self.kobo_directory_button = QtGui.QPushButton(self)
|
||||
self.kobo_directory_button.setToolTip(_(u"Click to specify the Kobo directory"))
|
||||
self.kobo_directory_button.setText(u"Kobo directory")
|
||||
self.kobo_directory_button.setToolTip(_("Click to specify the Kobo directory"))
|
||||
self.kobo_directory_button.setText("Kobo directory")
|
||||
self.kobo_directory_button.clicked.connect(self.edit_kobo_directory)
|
||||
layout.addWidget(self.kobo_directory_button)
|
||||
|
||||
|
||||
def edit_serials(self):
|
||||
d = ManageKeysDialog(self,u"Kobo device serial number",self.tmpserials, AddSerialDialog)
|
||||
d = ManageKeysDialog(self,"Kobo device serial number",self.tmpserials, AddSerialDialog)
|
||||
d.exec_()
|
||||
|
||||
|
||||
def edit_kobo_directory(self):
|
||||
tmpkobodirectory = QFileDialog.getExistingDirectory(self, u"Select Kobo directory", self.kobodirectory or "/home", QFileDialog.ShowDirsOnly)
|
||||
tmpkobodirectory = QFileDialog.getExistingDirectory(self, "Select Kobo directory", self.kobodirectory or "/home", QFileDialog.ShowDirsOnly)
|
||||
|
||||
if tmpkobodirectory != u"" and tmpkobodirectory is not None:
|
||||
self.kobodirectory = tmpkobodirectory
|
||||
|
||||
|
||||
def save_settings(self):
|
||||
plugin_prefs['finding_homes_for_formats'] = unicode(self.find_homes.currentText())
|
||||
plugin_prefs['finding_homes_for_formats'] = self.find_homes.currentText()
|
||||
plugin_prefs['kobo_serials'] = self.tmpserials
|
||||
plugin_prefs['kobo_directory'] = self.kobodirectory
|
||||
|
||||
@@ -91,7 +85,7 @@ class ManageKeysDialog(QDialog):
|
||||
self.plugin_keys = plugin_keys
|
||||
self.create_key = create_key
|
||||
self.keyfile_ext = keyfile_ext
|
||||
self.json_file = (keyfile_ext == u"k4i")
|
||||
self.json_file = (keyfile_ext == "k4i")
|
||||
|
||||
self.setWindowTitle("{0} {1}: Manage {2}s".format(PLUGIN_NAME, PLUGIN_VERSION, self.key_type_name))
|
||||
|
||||
@@ -99,13 +93,13 @@ class ManageKeysDialog(QDialog):
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
keys_group_box = QGroupBox(_(u"{0}s".format(self.key_type_name)), self)
|
||||
keys_group_box = QGroupBox(_("{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.setToolTip("{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)
|
||||
@@ -114,12 +108,12 @@ class ManageKeysDialog(QDialog):
|
||||
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.setToolTip("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.setToolTip(_("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)
|
||||
@@ -155,7 +149,7 @@ class ManageKeysDialog(QDialog):
|
||||
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)
|
||||
"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)
|
||||
@@ -165,8 +159,8 @@ class ManageKeysDialog(QDialog):
|
||||
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):
|
||||
keyname = self.listy.currentItem().text()
|
||||
if not question_dialog(self, "{0} {1}: Confirm Delete".format(PLUGIN_NAME, PLUGIN_VERSION), "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)
|
||||
|
||||
@@ -177,7 +171,7 @@ 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))
|
||||
self.setWindowTitle("{0} {1}: Add New eInk Kobo Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
@@ -188,9 +182,9 @@ class AddSerialDialog(QDialog):
|
||||
|
||||
key_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(key_group)
|
||||
key_group.addWidget(QLabel(u"EInk Kobo Serial Number:", self))
|
||||
key_group.addWidget(QLabel("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.")
|
||||
self.key_ledit.setToolTip("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)
|
||||
@@ -202,17 +196,17 @@ class AddSerialDialog(QDialog):
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
return unicode(self.key_ledit.text()).strip()
|
||||
return self.key_ledit.text().strip()
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
return unicode(self.key_ledit.text()).strip()
|
||||
return 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."
|
||||
errmsg = "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))
|
||||
errmsg = "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)
|
||||
|
||||
@@ -37,14 +37,14 @@ class legacy_obok(object):
|
||||
|
||||
def __oldcookiedeviceid(self):
|
||||
'''Optionally attempt to get a device id using the old cookie method.
|
||||
Must have _winreg installed on Windows machines for successful key retrieval.'''
|
||||
Must have winreg installed on Windows machines for successful key retrieval.'''
|
||||
wsuid = ''
|
||||
pwsdid = ''
|
||||
try:
|
||||
if sys.platform.startswith('win'):
|
||||
import _winreg
|
||||
regkey_browser = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, 'Software\\Kobo\\Kobo Desktop Edition\\Browser')
|
||||
cookies = _winreg.QueryValueEx(regkey_browser, 'cookies')
|
||||
import winreg
|
||||
regkey_browser = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Software\\Kobo\\Kobo Desktop Edition\\Browser')
|
||||
cookies = winreg.QueryValueEx(regkey_browser, 'cookies')
|
||||
bytearrays = cookies[0]
|
||||
elif sys.platform.startswith('darwin'):
|
||||
prefs = os.path.join(os.environ['HOME'], 'Library/Preferences/com.kobo.Kobo Desktop Edition.plist')
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Version 4.0.0 September 2020
|
||||
# Python 3.0
|
||||
#
|
||||
# Version 3.2.5 December 2016
|
||||
# Improve detection of good text decryption.
|
||||
#
|
||||
@@ -152,8 +155,8 @@
|
||||
"""Manage all Kobo books, either encrypted or DRM-free."""
|
||||
from __future__ import print_function
|
||||
|
||||
__version__ = '3.2.4'
|
||||
__about__ = u"Obok v{0}\nCopyright © 2012-2016 Physisticated et al.".format(__version__)
|
||||
__version__ = '4.0.0'
|
||||
__about__ = "Obok v{0}\nCopyright © 2012-2020 Physisticated et al.".format(__version__)
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -173,10 +176,10 @@ import tempfile
|
||||
can_parse_xml = True
|
||||
try:
|
||||
from xml.etree import ElementTree as ET
|
||||
# print u"using xml.etree for xml parsing"
|
||||
# print "using xml.etree for xml parsing"
|
||||
except ImportError:
|
||||
can_parse_xml = False
|
||||
# print u"Cannot find xml.etree, disabling extraction of serial numbers"
|
||||
# print "Cannot find xml.etree, disabling extraction of serial numbers"
|
||||
|
||||
# List of all known hash keys
|
||||
KOBO_HASH_KEYS = ['88b3a2e13', 'XzUhGYdFp', 'NoCanLook','QJhwzAtXL']
|
||||
@@ -231,7 +234,7 @@ def _load_crypto_libcrypto():
|
||||
raise ENCRYPTIONError(_('Failed to initialize AES key'))
|
||||
|
||||
def decrypt(self, data):
|
||||
clear = ''
|
||||
clear = b''
|
||||
for i in range(0, len(data), 16):
|
||||
out = create_string_buffer(16)
|
||||
rv = AES_ecb_encrypt(data[i:i+16], out, self._key, 0)
|
||||
@@ -276,10 +279,10 @@ class SafeUnbuffered:
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
if isinstance(data,str):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
self.stream.buffer.write(data)
|
||||
self.stream.buffer.flush()
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
@@ -309,9 +312,9 @@ class KoboLibrary(object):
|
||||
# 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")
|
||||
self.kobodir = os.path.join(device_path, ".kobo")
|
||||
# devices use KoboReader.sqlite
|
||||
kobodb = os.path.join(self.kobodir, u"KoboReader.sqlite")
|
||||
kobodb = os.path.join(self.kobodir, "KoboReader.sqlite")
|
||||
if (not(os.path.isfile(kobodb))):
|
||||
# device path seems to be wrong, unset it
|
||||
device_path = u""
|
||||
@@ -323,22 +326,22 @@ class KoboLibrary(object):
|
||||
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)
|
||||
# print "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)
|
||||
# print "trying to load {0}".format(devicexml)
|
||||
if (os.path.exists(devicexml)):
|
||||
# print u"trying to parse {0}".format(devicexml)
|
||||
# print "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)
|
||||
# print "found serial {0}".format(serial)
|
||||
serials.append(serial)
|
||||
break
|
||||
else:
|
||||
# print u"cannot get serials from device."
|
||||
# print "cannot get serials from device."
|
||||
device_path = u""
|
||||
self.kobodir = u""
|
||||
kobodb = u""
|
||||
@@ -350,23 +353,23 @@ class KoboLibrary(object):
|
||||
|
||||
if (self.kobodir == u""):
|
||||
if sys.platform.startswith('win'):
|
||||
import _winreg as winreg
|
||||
import 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%")
|
||||
self.kobodir = winreg.ExpandEnvironmentStrings("%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")
|
||||
self.kobodir = os.path.join(winreg.ExpandEnvironmentStrings("%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'], u"Library", u"Application Support", u"Kobo", u"Kobo Desktop Edition")
|
||||
self.kobodir = os.path.join(os.environ['HOME'], "Library", "Application Support", "Kobo", "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")
|
||||
# self.kobodir = os.path.join(linux_path, "Local Settings", "Application Data", "Kobo", "Kobo Desktop Edition")
|
||||
# desktop versions use Kobo.sqlite
|
||||
kobodb = os.path.join(self.kobodir, u"Kobo.sqlite")
|
||||
kobodb = os.path.join(self.kobodir, "Kobo.sqlite")
|
||||
# check for existence of file
|
||||
if (not(os.path.isfile(kobodb))):
|
||||
# give up here, we haven't found anything useful
|
||||
@@ -374,14 +377,14 @@ class KoboLibrary(object):
|
||||
kobodb = u""
|
||||
|
||||
if (self.kobodir != u""):
|
||||
self.bookdir = os.path.join(self.kobodir, u"kepub")
|
||||
self.bookdir = os.path.join(self.kobodir, "kepub")
|
||||
# make a copy of the database in a temporary file
|
||||
# so we can ensure it's not using WAL logging which sqlite3 can't do.
|
||||
self.newdb = tempfile.NamedTemporaryFile(mode='wb', delete=False)
|
||||
print(self.newdb.name)
|
||||
olddb = open(kobodb, 'rb')
|
||||
self.newdb.write(olddb.read(18))
|
||||
self.newdb.write('\x01\x01')
|
||||
self.newdb.write(b'\x01\x01')
|
||||
olddb.read(2)
|
||||
self.newdb.write(olddb.read())
|
||||
olddb.close()
|
||||
@@ -434,15 +437,15 @@ class KoboLibrary(object):
|
||||
|
||||
def __bookfile (self, volumeid):
|
||||
"""The filename needed to open a given book."""
|
||||
return os.path.join(self.kobodir, u"kepub", volumeid)
|
||||
return os.path.join(self.kobodir, "kepub", volumeid)
|
||||
|
||||
def __getmacaddrs (self):
|
||||
"""The list of all MAC addresses on this machine."""
|
||||
macaddrs = []
|
||||
if sys.platform.startswith('win'):
|
||||
c = re.compile('\s(' + '[0-9a-f]{2}-' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
||||
(p_in, p_out, p_err) = os.popen3('ipconfig /all')
|
||||
for line in p_out:
|
||||
output = subprocess.Popen('ipconfig /all', shell=True, stdout=subprocess.PIPE, text=True).stdout
|
||||
for line in output:
|
||||
m = c.search(line)
|
||||
if m:
|
||||
macaddrs.append(re.sub("-", ":", m.group(1)).upper())
|
||||
@@ -451,7 +454,7 @@ class KoboLibrary(object):
|
||||
output = subprocess.check_output('/sbin/ifconfig -a', shell=True)
|
||||
matches = c.findall(output)
|
||||
for m in matches:
|
||||
# print u"m:{0}".format(m[0])
|
||||
# print "m:{0}".format(m[0])
|
||||
macaddrs.append(m[0].upper())
|
||||
else:
|
||||
# probably linux
|
||||
@@ -488,14 +491,14 @@ class KoboLibrary(object):
|
||||
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()
|
||||
deviceid = hashlib.sha256((hash + macaddr).encode('ascii')).hexdigest()
|
||||
for userid in userids:
|
||||
userkey = hashlib.sha256(deviceid + userid).hexdigest()
|
||||
userkey = hashlib.sha256((deviceid + userid).encode('ascii')).hexdigest()
|
||||
userkeys.append(binascii.a2b_hex(userkey[32:]))
|
||||
return userkeys
|
||||
|
||||
@@ -556,7 +559,7 @@ class KoboBook(object):
|
||||
# Convert relative URIs
|
||||
href = item.attrib['href']
|
||||
if not c.match(href):
|
||||
href = string.join((basedir, href), '')
|
||||
href = ''.join((basedir, href))
|
||||
|
||||
# Update books we've found from the DB.
|
||||
if href in self._encryptedfiles:
|
||||
@@ -604,59 +607,59 @@ class KoboFile(object):
|
||||
# assume utf-8 with no BOM
|
||||
textoffset = 0
|
||||
stride = 1
|
||||
print(u"Checking text:{0}:".format(contents[:10]))
|
||||
print("Checking text:{0}:".format(contents[:10]))
|
||||
# check for byte order mark
|
||||
if contents[:3]=="\xef\xbb\xbf":
|
||||
if contents[:3]==b"\xef\xbb\xbf":
|
||||
# seems to be utf-8 with BOM
|
||||
print(u"Could be utf-8 with BOM")
|
||||
print("Could be utf-8 with BOM")
|
||||
textoffset = 3
|
||||
elif contents[:2]=="\xfe\xff":
|
||||
elif contents[:2]==b"\xfe\xff":
|
||||
# seems to be utf-16BE
|
||||
print(u"Could be utf-16BE")
|
||||
print("Could be utf-16BE")
|
||||
textoffset = 3
|
||||
stride = 2
|
||||
elif contents[:2]=="\xff\xfe":
|
||||
elif contents[:2]==b"\xff\xfe":
|
||||
# seems to be utf-16LE
|
||||
print(u"Could be utf-16LE")
|
||||
print("Could be utf-16LE")
|
||||
textoffset = 2
|
||||
stride = 2
|
||||
else:
|
||||
print(u"Perhaps utf-8 without BOM")
|
||||
|
||||
print("Perhaps utf-8 without BOM")
|
||||
|
||||
# now check that the first few characters are in the ASCII range
|
||||
for i in xrange(textoffset,textoffset+5*stride,stride):
|
||||
if ord(contents[i])<32 or ord(contents[i])>127:
|
||||
for i in range(textoffset,textoffset+5*stride,stride):
|
||||
if contents[i]<32 or contents[i]>127:
|
||||
# Non-ascii, so decryption probably failed
|
||||
print(u"Bad character at {0}, value {1}".format(i,ord(contents[i])))
|
||||
print("Bad character at {0}, value {1}".format(i,contents[i]))
|
||||
raise ValueError
|
||||
print(u"Seems to be good text")
|
||||
print("Seems to be good text")
|
||||
return True
|
||||
if contents[:5]=="<?xml" or contents[:8]=="\xef\xbb\xbf<?xml":
|
||||
if contents[:5]==b"<?xml" or contents[:8]==b"\xef\xbb\xbf<?xml":
|
||||
# utf-8
|
||||
return True
|
||||
elif contents[:14]=="\xfe\xff\x00<\x00?\x00x\x00m\x00l":
|
||||
elif contents[:14]==b"\xfe\xff\x00<\x00?\x00x\x00m\x00l":
|
||||
# utf-16BE
|
||||
return True
|
||||
elif contents[:14]=="\xff\xfe<\x00?\x00x\x00m\x00l\x00":
|
||||
elif contents[:14]==b"\xff\xfe<\x00?\x00x\x00m\x00l\x00":
|
||||
# utf-16LE
|
||||
return True
|
||||
elif contents[:9]=="<!DOCTYPE" or contents[:12]=="\xef\xbb\xbf<!DOCTYPE":
|
||||
elif contents[:9]==b"<!DOCTYPE" or contents[:12]==b"\xef\xbb\xbf<!DOCTYPE":
|
||||
# utf-8 of weird <!DOCTYPE start
|
||||
return True
|
||||
elif contents[:22]=="\xfe\xff\x00<\x00!\x00D\x00O\x00C\x00T\x00Y\x00P\x00E":
|
||||
elif contents[:22]==b"\xfe\xff\x00<\x00!\x00D\x00O\x00C\x00T\x00Y\x00P\x00E":
|
||||
# utf-16BE of weird <!DOCTYPE start
|
||||
return True
|
||||
elif contents[:22]=="\xff\xfe<\x00!\x00D\x00O\x00C\x00T\x00Y\x00P\x00E\x00":
|
||||
elif contents[:22]==b"\xff\xfe<\x00!\x00D\x00O\x00C\x00T\x00Y\x00P\x00E\x00":
|
||||
# utf-16LE of weird <!DOCTYPE start
|
||||
return True
|
||||
else:
|
||||
print(u"Bad XML: {0}".format(contents[:8]))
|
||||
print("Bad XML: {0}".format(contents[:8]))
|
||||
raise ValueError
|
||||
elif self.mimetype == 'image/jpeg':
|
||||
if contents[:3] == '\xff\xd8\xff':
|
||||
if contents[:3] == b'\xff\xd8\xff':
|
||||
return True
|
||||
else:
|
||||
print(u"Bad JPEG: {0}".format(contents[:3].encode('hex')))
|
||||
print("Bad JPEG: {0}".format(contents[:3].hex()))
|
||||
raise ValueError()
|
||||
return False
|
||||
|
||||
@@ -679,18 +682,18 @@ class KoboFile(object):
|
||||
return contents
|
||||
|
||||
def decrypt_book(book, lib):
|
||||
print(u"Converting {0}".format(book.title))
|
||||
print("Converting {0}".format(book.title))
|
||||
zin = zipfile.ZipFile(book.filename, "r")
|
||||
# make filename out of Unicode alphanumeric and whitespace equivalents from title
|
||||
outname = u"{0}.epub".format(re.sub('[^\s\w]', '_', book.title, 0, re.UNICODE))
|
||||
outname = "{0}.epub".format(re.sub('[^\s\w]', '_', book.title, 0, re.UNICODE))
|
||||
if (book.type == 'drm-free'):
|
||||
print(u"DRM-free book, conversion is not needed")
|
||||
print("DRM-free book, conversion is not needed")
|
||||
shutil.copyfile(book.filename, outname)
|
||||
print(u"Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
|
||||
print("Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
|
||||
return 0
|
||||
result = 1
|
||||
for userkey in lib.userkeys:
|
||||
print(u"Trying key: {0}".format(userkey.encode('hex_codec')))
|
||||
print("Trying key: {0}".format(userkey.hex()))
|
||||
try:
|
||||
zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
|
||||
for filename in zin.namelist():
|
||||
@@ -702,12 +705,12 @@ def decrypt_book(book, lib):
|
||||
file.check(contents)
|
||||
zout.writestr(filename, contents)
|
||||
zout.close()
|
||||
print(u"Decryption succeeded.")
|
||||
print(u"Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
|
||||
print("Decryption succeeded.")
|
||||
print("Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
|
||||
result = 0
|
||||
break
|
||||
except ValueError:
|
||||
print(u"Decryption failed.")
|
||||
print("Decryption failed.")
|
||||
zout.close()
|
||||
os.remove(outname)
|
||||
zin.close()
|
||||
@@ -716,7 +719,7 @@ def decrypt_book(book, lib):
|
||||
|
||||
def cli_main():
|
||||
description = __about__
|
||||
epilog = u"Parsing of arguments failed."
|
||||
epilog = "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")
|
||||
parser.add_argument('--all', action='store_true', help="flag for converting all books on device")
|
||||
@@ -732,25 +735,25 @@ def cli_main():
|
||||
books = lib.books
|
||||
else:
|
||||
for i, book in enumerate(lib.books):
|
||||
print(u"{0}: {1}".format(i + 1, book.title))
|
||||
print(u"Or 'all'")
|
||||
print("{0}: {1}".format(i + 1, book.title))
|
||||
print("Or 'all'")
|
||||
|
||||
choice = raw_input(u"Convert book number... ")
|
||||
if choice == u'all':
|
||||
choice = input("Convert book number... ")
|
||||
if choice == "all":
|
||||
books = list(lib.books)
|
||||
else:
|
||||
try:
|
||||
num = int(choice)
|
||||
books = [lib.books[num - 1]]
|
||||
except (ValueError, IndexError):
|
||||
print(u"Invalid choice. Exiting...")
|
||||
print("Invalid choice. Exiting...")
|
||||
exit()
|
||||
|
||||
results = [decrypt_book(book, lib) for book in books]
|
||||
lib.close()
|
||||
overall_result = all(result != 0 for result in results)
|
||||
if overall_result != 0:
|
||||
print(u"Could not decrypt book with any of the keys found.")
|
||||
print("Could not decrypt book with any of the keys found.")
|
||||
return overall_result
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-11-17 12:51+0100\n"
|
||||
"PO-Revision-Date: 2020-02-02 09:18+0100\n"
|
||||
"PO-Revision-Date: 2020-12-25 13:38+0100\n"
|
||||
"Language: sv\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -16,14 +16,14 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"X-Generator: Poedit 2.2.4\n"
|
||||
"X-Generator: Poedit 2.4.2\n"
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:80
|
||||
msgid ""
|
||||
"<p>No books found in Kobo Library\n"
|
||||
"Are you sure it's installed\\configured\\synchronized?"
|
||||
msgstr ""
|
||||
"<p>Inga böcker finns i Kobo-bibliotek\n"
|
||||
"<p>Inga böcker hittades i Kobo-bibliotek\n"
|
||||
"Är du säker på att den är installerad\\konfigurerad\\synkroniserad?"
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:87
|
||||
@@ -36,17 +36,17 @@ msgstr "Problem med att hämta nycklar med nyare obok-metod."
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:97
|
||||
msgid "Found {0} possible keys to try."
|
||||
msgstr "Hittade {0} möjliga nycklar att pröva med."
|
||||
msgstr "Hittade {0} möjliga nycklar att försöka med."
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:99
|
||||
msgid "<p>No userkeys found to decrypt books with. No point in proceeding."
|
||||
msgstr ""
|
||||
"<p>Inga användarnycklar hittades för att dekryptera böcker med. Det är ingen "
|
||||
"idé att fortsätta."
|
||||
"<p>Inga användarnycklar hittades för att dekryptera böcker med. Ingen idé "
|
||||
"att fortsätta."
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:115
|
||||
msgid "{} - Decryption canceled by user."
|
||||
msgstr "{} - Dekryptering avbryts av användaren."
|
||||
msgstr "{} - Dekryptering avbröts av användaren."
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:135
|
||||
msgid "{} - \"Add books\" canceled by user."
|
||||
@@ -87,14 +87,14 @@ msgstr "dubblett upptäcktes"
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:233
|
||||
msgid "{0} - Successfully added EPUB format to existing {1}"
|
||||
msgstr "{0} - Lade till EPUB-format till befintliga {1}"
|
||||
msgstr "{0} - EPUB-format har lagts till i befintliga {1}"
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:236
|
||||
msgid ""
|
||||
"{0} - Error adding EPUB format to existing {1}. This really shouldn't happen."
|
||||
msgstr ""
|
||||
"{0} - Fel vid tillägg av EPUB-format till befintligt {1}. Det här borde inte "
|
||||
"hända."
|
||||
"{0} - Fel vid tilläggning av EPUB-format till befintligt {1}. Detta borde "
|
||||
"verkligen inte hända."
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:259
|
||||
msgid "{} - \"Insert formats\" canceled by user."
|
||||
@@ -103,15 +103,15 @@ msgstr "{} - \"Infoga format\" avbröts av användaren."
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:291
|
||||
msgid ""
|
||||
"<p><b>{0}</b> EPUB{2} successfully added to library.<br /><br /><b>{1}</b> "
|
||||
msgstr "<p><b>{0}</b> EPUB{2} lades till bibliotek.<br /><br /><b>{1}</b> "
|
||||
msgstr "<p><b>{0}</b> EPUB{2} har lagts till i bibliotek.<br /><br /><b>{1}</b> "
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:292
|
||||
msgid ""
|
||||
"not added because books with the same title/author were detected.<br /><br /"
|
||||
">Would you like to try and add the EPUB format{0}"
|
||||
msgstr ""
|
||||
"inte tillagd eftersom böcker med samma titel/författare upptäcktes.<br/><br /"
|
||||
">Vill du försöka lägga till EPUB-formatet{0}"
|
||||
"lades inte till eftersom böcker med samma titel/författare upptäcktes.<br/"
|
||||
"><br />Vill du försöka lägga till EPUB-formatet{0}"
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:293
|
||||
msgid ""
|
||||
@@ -126,45 +126,46 @@ msgid ""
|
||||
"{0} -- not added because of {1} in your library.\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
"{0} -- inte tillagd på grund av {1} i ditt bibliotek.\n"
|
||||
"{0} -- lades inte till på grund av {1} i ditt bibliotek.\n"
|
||||
"\n"
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:297
|
||||
msgid "<p><b>{0}</b> -- not added because of {1} in your library.<br /><br />"
|
||||
msgstr ""
|
||||
"<p><b>{0}</b> -- inte tillagd på grund av {1} i ditt bibliotek.<br /><br />"
|
||||
"<p><b>{0}</b> -- lades inte till på grund av {1} i ditt bibliotek.<br /><br /"
|
||||
">"
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:298
|
||||
msgid ""
|
||||
"Would you like to try and add the EPUB format to an available calibre "
|
||||
"duplicate?<br /><br />"
|
||||
msgstr ""
|
||||
"Vill du försöka lägga till EPUB-formatet till en tillgänglig calibre-"
|
||||
"dubblett?<br /><br />"
|
||||
"Vill du försöka lägga till EPUB-format till en tillgänglig calibre-dubblett?"
|
||||
"<br /><br />"
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:299
|
||||
msgid "NOTE: no pre-existing EPUB will be overwritten."
|
||||
msgstr "OBS: ingen befintlig EPUB kommer att skrivas över."
|
||||
msgstr "OBS: inga befintliga EPUB:er kommer att skrivas över."
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:346
|
||||
msgid "Trying key: "
|
||||
msgstr "Prövar nyckel: "
|
||||
msgstr "Försöker med nyckel: "
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:378
|
||||
msgid "Decryption failed, trying next key."
|
||||
msgstr "Det gick inte att dekryptera, prövar nästa nyckel."
|
||||
msgstr "Det gick inte att dekryptera, försöker med nästa nyckel."
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:382
|
||||
msgid "Unknown Error decrypting, trying next key.."
|
||||
msgstr "Okänt fel dekryptering, prövar nästa nyckel.."
|
||||
msgstr "Okänt fel vid dekryptering, försöker med nästa nyckel.."
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:395
|
||||
msgid ""
|
||||
"<p>All selected Kobo books added as new calibre books or inserted into "
|
||||
"existing calibre ebooks.<br /><br />No issues."
|
||||
msgstr ""
|
||||
"<p>Alla valda Kobo-böcker läggs till som nya calibre-böcker eller infogas i "
|
||||
"befintliga calibre-e-böcker.<br /><br />Inga problem."
|
||||
"<p>Alla valda Kobo-böcker har lagts till som nya calibre-böcker eller "
|
||||
"infogats i befintliga calibre-e-böcker.<br /><br />Inga problem."
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:399
|
||||
msgid "<p>{0} successfully added."
|
||||
@@ -192,7 +193,7 @@ msgstr "<p><b>Nya böcker skapade:</b> {}</p>\n"
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:418
|
||||
msgid "<p><b>Duplicates that weren't added:</b> {}</p>\n"
|
||||
msgstr "<p><b>Dubbletter som inte tillsattes:</b> {}</p>\n"
|
||||
msgstr "<p><b>Dubbletter som inte har lagts till:</b> {}</p>\n"
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:426
|
||||
msgid "<p><b>Book imports cancelled by user:</b> {}</p>\n"
|
||||
@@ -221,7 +222,7 @@ msgstr ""
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:444
|
||||
msgid "<p><b>Format imports cancelled by user:</b> {}</p>\n"
|
||||
msgstr "<p><b>Format-import avbröts av användaren:</b> {}</p>\n"
|
||||
msgstr "<p><b>Formatimporten avbröts av användaren:</b> {}</p>\n"
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:458
|
||||
msgid "Unknown Book Title"
|
||||
@@ -237,7 +238,10 @@ msgid ""
|
||||
"entries HAD an EPUB format already."
|
||||
msgstr ""
|
||||
"användaren VALDE att inte infoga det nya EPUB-formatet, eller alla "
|
||||
"befintliga calibre-poster hade redan ett EPUB-format."
|
||||
"befintliga calibre-poster hade redan ett EPUB-format.\n"
|
||||
"\n"
|
||||
"användaren valde att inte infoga det nya EPUB-formatet, eller alla "
|
||||
"befintliga kaliberposter hade redan ett EPUB-format."
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:464
|
||||
msgid "of unknown reasons. Gosh I'm embarrassed!"
|
||||
@@ -245,7 +249,7 @@ msgstr "av okända skäl. Jag skäms!"
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:465
|
||||
msgid "<p>{0} not added because {1}"
|
||||
msgstr "<p>{0} inte tillagd eftersom {1}"
|
||||
msgstr "<p>{0} lades inte till på grund av {1}"
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:226
|
||||
msgid "Help"
|
||||
@@ -254,31 +258,31 @@ msgstr "Hjälp"
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:235
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\utilities.py:214
|
||||
msgid "Restart required"
|
||||
msgstr "Omstart krävs"
|
||||
msgstr "Kräver omstart"
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:236
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\utilities.py:215
|
||||
msgid ""
|
||||
"Title image not found - you must restart Calibre before using this plugin!"
|
||||
msgstr ""
|
||||
"Titelbild hittades inte - du måste starta calibre innan du använder denna "
|
||||
"Titelbild hittades inte - du måste starta om calibre innan du använder denna "
|
||||
"insticksmodul!"
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:322
|
||||
msgid "Undefined"
|
||||
msgstr "Obestämd"
|
||||
msgstr "Odefinierad"
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:30
|
||||
msgid "When should Obok try to insert EPUBs into existing calibre entries?"
|
||||
msgstr "När ska Obok försöka infoga EPUB:er i befintliga calibre-böcker?"
|
||||
msgstr "När ska Obok försöka infoga EPUB:er i befintliga calibre-poster?"
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:33
|
||||
msgid ""
|
||||
"<p>Default behavior when duplicates are detected. None of the choices will "
|
||||
"cause calibre ebooks to be overwritten"
|
||||
msgstr ""
|
||||
"<p>Standardbeteende när dubbletter upptäcks. Inget av alternativen kommer "
|
||||
"att orsaka calibre-e-böcker att skrivas över"
|
||||
"<p>Standardbeteende när dubbletter upptäcks. Inget av alternativen kommer att "
|
||||
"göra att calibre-e-böcker skrivs över"
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:35
|
||||
msgid "Ask"
|
||||
@@ -323,7 +327,7 @@ msgstr "Välj alla böcker med DRM."
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:95
|
||||
msgid "All DRM free"
|
||||
msgstr "Alla DRM fria"
|
||||
msgstr "Alla utan DRM"
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:96
|
||||
msgid "Select all books without DRM."
|
||||
@@ -355,12 +359,12 @@ msgstr "Tar bort DRM från Kobo-kepubs och lägger till dem i biblioteket."
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\obok\obok.py:162
|
||||
msgid "AES improper key used"
|
||||
msgstr "AES felaktig nyckel används"
|
||||
msgstr "Felaktig AES-nyckel används"
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\obok\obok.py:167
|
||||
msgid "Failed to initialize AES key"
|
||||
msgstr "Det gick inte att initiera AES-nyckel"
|
||||
msgstr "Misslyckades att initiera AES-nyckel"
|
||||
|
||||
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\obok\obok.py:175
|
||||
msgid "AES decryption failed"
|
||||
msgstr "AES dekryptering misslyckades"
|
||||
msgstr "AES-dekryptering misslyckades"
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
import os, struct, time
|
||||
from StringIO import StringIO
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
from traceback import print_exc
|
||||
|
||||
try:
|
||||
from PyQt5.Qt import (Qt, QDialog, QPixmap, QIcon, QLabel, QHBoxLayout, QFont, QTableWidgetItem)
|
||||
except ImportError:
|
||||
from PyQt4.Qt import (Qt, QDialog, QPixmap, QIcon, QLabel, QHBoxLayout, QFont, QTableWidgetItem)
|
||||
|
||||
from PyQt5.Qt import (Qt, QDialog, QPixmap, QIcon, QLabel, QHBoxLayout, QFont, QTableWidgetItem)
|
||||
|
||||
from calibre.utils.config import config_dir
|
||||
from calibre.constants import iswindows, DEBUG
|
||||
from calibre import prints
|
||||
from calibre.gui2 import (error_dialog, gprefs)
|
||||
from calibre.gui2.actions import menu_action_unique_name
|
||||
|
||||
from calibre_plugins.obok_dedrm.__init__ import (PLUGIN_NAME,
|
||||
from calibre_plugins.obok_dedrm.__init__ import (PLUGIN_NAME,
|
||||
PLUGIN_SAFE_NAME, PLUGIN_VERSION, PLUGIN_DESCRIPTION)
|
||||
|
||||
plugin_ID = None
|
||||
@@ -39,7 +39,7 @@ else:
|
||||
def convert_qvariant(x):
|
||||
vt = x.type()
|
||||
if vt == x.String:
|
||||
return unicode(x.toString())
|
||||
return x.toString()
|
||||
if vt == x.List:
|
||||
return [convert_qvariant(i) for i in x.toList()]
|
||||
return x.toPyObject()
|
||||
@@ -62,7 +62,7 @@ except NameError:
|
||||
def format_plural(number, possessive=False):
|
||||
'''
|
||||
Cosmetic ditty to provide the proper string formatting variable to handle singular/plural situations
|
||||
|
||||
|
||||
:param: number: variable that represents the count/len of something
|
||||
'''
|
||||
if not possessive:
|
||||
@@ -141,7 +141,7 @@ def showErrorDlg(errmsg, parent, trcbk=False):
|
||||
'''
|
||||
if trcbk:
|
||||
error= ''
|
||||
f=StringIO()
|
||||
f=StringIO()
|
||||
print_exc(file=f)
|
||||
error_mess = f.getvalue().splitlines()
|
||||
for line in error_mess:
|
||||
|
||||
@@ -129,7 +129,7 @@ if iswindows:
|
||||
c_long, c_ulong
|
||||
|
||||
from ctypes.wintypes import LPVOID, DWORD, BOOL
|
||||
import _winreg as winreg
|
||||
import winreg
|
||||
|
||||
def _load_crypto_libcrypto():
|
||||
from ctypes.util import find_library
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from __future__ import with_statement
|
||||
|
||||
# ignoblekey.py
|
||||
# Copyright © 2015 Apprentice Alf and Apprentice Harper
|
||||
# Copyright © 2015-2020 Apprentice Harper et al.
|
||||
|
||||
# Based on kindlekey.py, Copyright © 2010-2013 by some_updates and Apprentice Alf
|
||||
|
||||
@@ -14,13 +14,14 @@ from __future__ import with_statement
|
||||
# Revision history:
|
||||
# 1.0 - Initial release
|
||||
# 1.1 - remove duplicates and return last key as single key
|
||||
# 2.0 - Python 3
|
||||
|
||||
"""
|
||||
Get Barnes & Noble EPUB user key from nook Studio log file
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "1.1"
|
||||
__version__ = "2.0"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -97,7 +98,7 @@ def getNookLogFiles():
|
||||
logFiles = []
|
||||
found = False
|
||||
if iswindows:
|
||||
import _winreg as winreg
|
||||
import winreg
|
||||
|
||||
# some 64 bit machines do not have the proper registry key for some reason
|
||||
# or the python interface to the 32 vs 64 bit registry is broken
|
||||
@@ -318,7 +319,7 @@ def gui_main():
|
||||
keyfileout.write(key)
|
||||
success = True
|
||||
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
||||
except DrmException, e:
|
||||
except DrmException as e:
|
||||
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
|
||||
except Exception:
|
||||
root.wm_state('normal')
|
||||
|
||||
@@ -177,7 +177,7 @@ if iswindows:
|
||||
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
|
||||
string_at, Structure, c_void_p, cast
|
||||
|
||||
import _winreg as winreg
|
||||
import winreg
|
||||
MAX_PATH = 255
|
||||
kernel32 = windll.kernel32
|
||||
advapi32 = windll.advapi32
|
||||
|
||||
@@ -153,7 +153,7 @@
|
||||
from __future__ import print_function
|
||||
|
||||
__version__ = '3.2.4'
|
||||
__about__ = u"Obok v{0}\nCopyright © 2012-2016 Physisticated et al.".format(__version__)
|
||||
__about__ = "Obok v{0}\nCopyright © 2012-2016 Physisticated et al.".format(__version__)
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -173,10 +173,10 @@ import tempfile
|
||||
can_parse_xml = True
|
||||
try:
|
||||
from xml.etree import ElementTree as ET
|
||||
# print u"using xml.etree for xml parsing"
|
||||
# print "using xml.etree for xml parsing"
|
||||
except ImportError:
|
||||
can_parse_xml = False
|
||||
# print u"Cannot find xml.etree, disabling extraction of serial numbers"
|
||||
# print "Cannot find xml.etree, disabling extraction of serial numbers"
|
||||
|
||||
# List of all known hash keys
|
||||
KOBO_HASH_KEYS = ['88b3a2e13', 'XzUhGYdFp', 'NoCanLook','QJhwzAtXL']
|
||||
@@ -309,9 +309,9 @@ class KoboLibrary(object):
|
||||
# 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")
|
||||
self.kobodir = os.path.join(device_path, ".kobo")
|
||||
# devices use KoboReader.sqlite
|
||||
kobodb = os.path.join(self.kobodir, u"KoboReader.sqlite")
|
||||
kobodb = os.path.join(self.kobodir, "KoboReader.sqlite")
|
||||
if (not(os.path.isfile(kobodb))):
|
||||
# device path seems to be wrong, unset it
|
||||
device_path = u""
|
||||
@@ -323,22 +323,22 @@ class KoboLibrary(object):
|
||||
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)
|
||||
# print "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)
|
||||
# print "trying to load {0}".format(devicexml)
|
||||
if (os.path.exists(devicexml)):
|
||||
# print u"trying to parse {0}".format(devicexml)
|
||||
# print "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)
|
||||
# print "found serial {0}".format(serial)
|
||||
serials.append(serial)
|
||||
break
|
||||
else:
|
||||
# print u"cannot get serials from device."
|
||||
# print "cannot get serials from device."
|
||||
device_path = u""
|
||||
self.kobodir = u""
|
||||
kobodb = u""
|
||||
@@ -346,23 +346,23 @@ class KoboLibrary(object):
|
||||
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
|
||||
import 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%")
|
||||
self.kobodir = winreg.ExpandEnvironmentStrings("%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")
|
||||
self.kobodir = os.path.join(winreg.ExpandEnvironmentStrings("%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'], u"Library", u"Application Support", u"Kobo", u"Kobo Desktop Edition")
|
||||
self.kobodir = os.path.join(os.environ['HOME'], "Library", "Application Support", "Kobo", "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")
|
||||
# self.kobodir = os.path.join(linux_path, "Local Settings", "Application Data", "Kobo", "Kobo Desktop Edition")
|
||||
# desktop versions use Kobo.sqlite
|
||||
kobodb = os.path.join(self.kobodir, u"Kobo.sqlite")
|
||||
kobodb = os.path.join(self.kobodir, "Kobo.sqlite")
|
||||
# check for existence of file
|
||||
if (not(os.path.isfile(kobodb))):
|
||||
# give up here, we haven't found anything useful
|
||||
@@ -371,7 +371,7 @@ class KoboLibrary(object):
|
||||
|
||||
|
||||
if (self.kobodir != u""):
|
||||
self.bookdir = os.path.join(self.kobodir, u"kepub")
|
||||
self.bookdir = os.path.join(self.kobodir, "kepub")
|
||||
# make a copy of the database in a temporary file
|
||||
# so we can ensure it's not using WAL logging which sqlite3 can't do.
|
||||
self.newdb = tempfile.NamedTemporaryFile(mode='wb', delete=False)
|
||||
@@ -431,7 +431,7 @@ class KoboLibrary(object):
|
||||
|
||||
def __bookfile (self, volumeid):
|
||||
"""The filename needed to open a given book."""
|
||||
return os.path.join(self.kobodir, u"kepub", volumeid)
|
||||
return os.path.join(self.kobodir, "kepub", volumeid)
|
||||
|
||||
def __getmacaddrs (self):
|
||||
"""The list of all MAC addresses on this machine."""
|
||||
@@ -448,7 +448,7 @@ class KoboLibrary(object):
|
||||
output = subprocess.check_output('/sbin/ifconfig -a', shell=True)
|
||||
matches = c.findall(output)
|
||||
for m in matches:
|
||||
# print u"m:{0}".format(m[0])
|
||||
# print "m:{0}".format(m[0])
|
||||
macaddrs.append(m[0].upper())
|
||||
elif sys.platform.startswith('linux'):
|
||||
p_out = subprocess.check_output("ip -br link show | awk '{print $3}'", shell=True)
|
||||
@@ -596,32 +596,32 @@ class KoboFile(object):
|
||||
# assume utf-8 with no BOM
|
||||
textoffset = 0
|
||||
stride = 1
|
||||
print(u"Checking text:{0}:".format(contents[:10]))
|
||||
print("Checking text:{0}:".format(contents[:10]))
|
||||
# check for byte order mark
|
||||
if contents[:3]=="\xef\xbb\xbf":
|
||||
# seems to be utf-8 with BOM
|
||||
print(u"Could be utf-8 with BOM")
|
||||
print("Could be utf-8 with BOM")
|
||||
textoffset = 3
|
||||
elif contents[:2]=="\xfe\xff":
|
||||
# seems to be utf-16BE
|
||||
print(u"Could be utf-16BE")
|
||||
print("Could be utf-16BE")
|
||||
textoffset = 3
|
||||
stride = 2
|
||||
elif contents[:2]=="\xff\xfe":
|
||||
# seems to be utf-16LE
|
||||
print(u"Could be utf-16LE")
|
||||
print("Could be utf-16LE")
|
||||
textoffset = 2
|
||||
stride = 2
|
||||
else:
|
||||
print(u"Perhaps utf-8 without BOM")
|
||||
print("Perhaps utf-8 without BOM")
|
||||
|
||||
# now check that the first few characters are in the ASCII range
|
||||
for i in xrange(textoffset,textoffset+5*stride,stride):
|
||||
if ord(contents[i])<32 or ord(contents[i])>127:
|
||||
# Non-ascii, so decryption probably failed
|
||||
print(u"Bad character at {0}, value {1}".format(i,ord(contents[i])))
|
||||
print("Bad character at {0}, value {1}".format(i,ord(contents[i])))
|
||||
raise ValueError
|
||||
print(u"Seems to be good text")
|
||||
print("Seems to be good text")
|
||||
return True
|
||||
if contents[:5]=="<?xml" or contents[:8]=="\xef\xbb\xbf<?xml":
|
||||
# utf-8
|
||||
@@ -642,13 +642,13 @@ class KoboFile(object):
|
||||
# utf-16LE of weird <!DOCTYPE start
|
||||
return True
|
||||
else:
|
||||
print(u"Bad XML: {0}".format(contents[:8]))
|
||||
print("Bad XML: {0}".format(contents[:8]))
|
||||
raise ValueError
|
||||
elif self.mimetype == 'image/jpeg':
|
||||
if contents[:3] == '\xff\xd8\xff':
|
||||
return True
|
||||
else:
|
||||
print(u"Bad JPEG: {0}".format(contents[:3].encode('hex')))
|
||||
print("Bad JPEG: {0}".format(contents[:3].encode('hex')))
|
||||
raise ValueError()
|
||||
return False
|
||||
|
||||
@@ -671,18 +671,18 @@ class KoboFile(object):
|
||||
return contents
|
||||
|
||||
def decrypt_book(book, lib):
|
||||
print(u"Converting {0}".format(book.title))
|
||||
print("Converting {0}".format(book.title))
|
||||
zin = zipfile.ZipFile(book.filename, "r")
|
||||
# make filename out of Unicode alphanumeric and whitespace equivalents from title
|
||||
outname = u"{0}.epub".format(re.sub('[^\s\w]', '_', book.title, 0, re.UNICODE))
|
||||
outname = "{0}.epub".format(re.sub('[^\s\w]', '_', book.title, 0, re.UNICODE))
|
||||
if (book.type == 'drm-free'):
|
||||
print(u"DRM-free book, conversion is not needed")
|
||||
print("DRM-free book, conversion is not needed")
|
||||
shutil.copyfile(book.filename, outname)
|
||||
print(u"Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
|
||||
print("Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
|
||||
return 0
|
||||
result = 1
|
||||
for userkey in lib.userkeys:
|
||||
print(u"Trying key: {0}".format(userkey.encode('hex_codec')))
|
||||
print("Trying key: {0}".format(userkey.encode('hex_codec')))
|
||||
try:
|
||||
zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
|
||||
for filename in zin.namelist():
|
||||
@@ -694,12 +694,12 @@ def decrypt_book(book, lib):
|
||||
file.check(contents)
|
||||
zout.writestr(filename, contents)
|
||||
zout.close()
|
||||
print(u"Decryption succeeded.")
|
||||
print(u"Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
|
||||
print("Decryption succeeded.")
|
||||
print("Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
|
||||
result = 0
|
||||
break
|
||||
except ValueError:
|
||||
print(u"Decryption failed.")
|
||||
print("Decryption failed.")
|
||||
zout.close()
|
||||
os.remove(outname)
|
||||
zin.close()
|
||||
@@ -708,7 +708,7 @@ def decrypt_book(book, lib):
|
||||
|
||||
def cli_main():
|
||||
description = __about__
|
||||
epilog = u"Parsing of arguments failed."
|
||||
epilog = "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")
|
||||
parser.add_argument('--all', action='store_true', help="flag for converting all books on device")
|
||||
@@ -724,25 +724,25 @@ def cli_main():
|
||||
books = lib.books
|
||||
else:
|
||||
for i, book in enumerate(lib.books):
|
||||
print(u"{0}: {1}".format(i + 1, book.title))
|
||||
print(u"Or 'all'")
|
||||
print("{0}: {1}".format(i + 1, book.title))
|
||||
print("Or 'all'")
|
||||
|
||||
choice = raw_input(u"Convert book number... ")
|
||||
if choice == u'all':
|
||||
choice = raw_input("Convert book number... ")
|
||||
if choice == "all":
|
||||
books = list(lib.books)
|
||||
else:
|
||||
try:
|
||||
num = int(choice)
|
||||
books = [lib.books[num - 1]]
|
||||
except (ValueError, IndexError):
|
||||
print(u"Invalid choice. Exiting...")
|
||||
print("Invalid choice. Exiting...")
|
||||
exit()
|
||||
|
||||
results = [decrypt_book(book, lib) for book in books]
|
||||
lib.close()
|
||||
overall_result = all(result != 0 for result in results)
|
||||
if overall_result != 0:
|
||||
print(u"Could not decrypt book with any of the keys found.")
|
||||
print("Could not decrypt book with any of the keys found.")
|
||||
return overall_result
|
||||
|
||||
|
||||
|
||||
@@ -2290,7 +2290,7 @@ class PDFDocument(object):
|
||||
import win32api
|
||||
import win32security
|
||||
import win32file
|
||||
import _winreg as winreg
|
||||
import winreg
|
||||
except:
|
||||
raise ADEPTError('PyWin Extension (Win32API module) needed.\n'+\
|
||||
'Download from http://sourceforge.net/projects/pywin32/files/ ')
|
||||
|
||||
17
README.md
17
README.md
@@ -1,18 +1,27 @@
|
||||
# [Guide] How to remove DRM
|
||||
Refer to [Wiki Page](https://github.com/apprenticeharper/DeDRM_tools/wiki/Exactly-how-to-remove-DRM)
|
||||
|
||||
# DeDRM_tools
|
||||
DeDRM tools for ebooks
|
||||
|
||||
This is a repository of all the scripts and other tools for removing DRM from ebooks that I could find, committed in date order as best as I could manage. (Except for the Requiem tools for Apple's iBooks, and Convert LIT for Microsoft's .lit ebooks.)
|
||||
This is a repository that tracks all the scripts and other tools for removing DRM from ebooks that I could find, committed in date order as best as I could manage. (Except for the Requiem tools for Apple's iBooks, and Convert LIT for Microsoft's .lit ebooks.) This includes the tools from a time before Apprentice Alf had a blog, and continues through to when Apprentice Harper (with help) took over maintenance of the tools.
|
||||
|
||||
Mostly it tracks the tools released by Apprentice Alf, athough it also includes the individual tools and their histories from before Alf had a blog.
|
||||
The individual scripts are now released as two plugins for calibre: DeDRM and Obok.
|
||||
The DeDRM plugin handles books that use Amazon DRM, Adobe Digital Editions DRM (version 1), Barnes & Noble DRM, and some historical formats.
|
||||
The Obok plugin handles Kobo DRM.
|
||||
|
||||
Users with calibre 5.x or later should use release 7.0.0b2 or later of the tools.
|
||||
Users with calibe 4.x or earlier should use release 6.8.0 of the tools.
|
||||
|
||||
Users should download the latest zip archive.
|
||||
Developers might be interested in forking the repository, as it contains unzipped versions of those tools that are zipped to make the changes over time easier to follow.
|
||||
|
||||
For the latest Amazon KFX format, users of the calibre plugin should also install the KFX Input plugin from the standard calibre plugin menu. It's also available from the MobileRead thread here: https://www.mobileread.com/forums/showthread.php?t=291290
|
||||
|
||||
Note that DRM can only be removed from KFX format files downloaded with Kindle for PC/Mac 1.26 or earlier. Amazon changes the DRM for KFX files in Kindle for PC/Mac 1.27 and later.
|
||||
|
||||
I welcome contributions from others to improve these tools, from expanding the range of books handled, improving key retrieval, to just general bug fixes, speed improvements and UI enhancements.
|
||||
|
||||
I urge people to read the FAQs. But to cover the most common: Use ADE 2.0.1 to be sure not to get the new DRM scheme that these tools can't handle. Do remember to unzip the downloaded archive to get the plugin. You can't load the whole archive into calibre.
|
||||
I urge people to read the FAQs. But to cover the most common: Use ADE 2.0.1 to be sure not to get the new DRM scheme that these tools can't handle. Do remember to unzip the downloaded archive to get the plugin (beta versions may be just the plugin don't unzip that). You can't load the whole tools archive into calibre.
|
||||
|
||||
My special thanks to all those developers who have done the hard work of reverse engineering to provide the initial tools.
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ The tools are provided in the form of plugins for calibre. Calibre is an open so
|
||||
|
||||
DeDRM plugin for calibre (Mac OS X, Windows)
|
||||
-------------------------------------------------------
|
||||
calibe 5.x and later are now written in Python 3, and plugins must also use Python 3. If you have calibre 5, you must use version 7.x or later of the plugins. For calibre 4.x and earlier, use version 6.8.0 of the plugins.
|
||||
|
||||
The DeDRM plugin for calibre removes DRM from your Kindle and Adobe DRM ebooks when they are imported to calibre. Just install the DeDRM plugin (DeDRM_plugin.zip), following the instructions and configuration directions provided in the ReadMe file and the help links in the plugin's configuration dialogs.
|
||||
|
||||
Once installed and configured, you can simply add a DRM book to calibre and a DRM-free version will be imported into the calibre database. Note that DRM removal only occurs on IMPORT not on CONVERSION or at any other time. If you have already imported DRMed books you'll need to remove the books from calibre and re-import them.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# code: utf-8
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
'''
|
||||
A wrapper script to generate zip files for GitHub releases.
|
||||
@@ -13,25 +13,12 @@ import os
|
||||
import shutil
|
||||
|
||||
|
||||
DEDRM_SRC_DIR = 'DeDRM_Plugin'
|
||||
DEDRM_README= 'DeDRM_Plugin_ReadMe.txt'
|
||||
DEDRM_SRC_DIR = 'DeDRM_plugin'
|
||||
DEDRM_README= 'DeDRM_plugin_ReadMe.txt'
|
||||
OBOK_SRC_DIR = 'Obok_plugin'
|
||||
OBOK_README = 'Obok_plugin_ReadMe.txt'
|
||||
OBOK_README = 'obok_plugin_ReadMe.txt'
|
||||
RELEASE_DIR = 'release'
|
||||
|
||||
def make_calibre_plugin():
|
||||
|
||||
shutil.make_archive(core_dir, 'zip', core_dir)
|
||||
shutil.rmtree(core_dir)
|
||||
|
||||
|
||||
def make_obok_plugin():
|
||||
obok_plugin_dir = os.path.join(SHELLS_BASE, 'Obok_calibre_plugin')
|
||||
core_dir = os.path.join(obok_plugin_dir, 'obok_plugin')
|
||||
|
||||
shutil.copytree(OBOK_SRC_DIR, core_dir)
|
||||
shutil.make_archive(core_dir, 'zip')
|
||||
shutil.rmtree(core_dir)
|
||||
|
||||
def make_release(version):
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user